Sometimes when I see my logging code I wonder if I am doing it right. There might be no definitive answer to that, but I have the following concerns:
Library Classes
I have several library classes which might log some INFO
messages. Fatal Errors are reported as exceptions. Currently I have a static logger instance in my classes with the class name as the logging name. (Log4j's: Logger.getLogger(MyClass.class)
)
Is this the right way? Maybe the user of this library class doesn't want any messages from my implementation or wants to redirect them to an application specific log. Should I allow the user to set a logger from the "outside world"? How do you handle such cases?
General logs
In some applications my classes might want to write a log message to a specific log not identified by the class' name. (I.e.: HTTP Request log
) What is the best way to do such a thing? A lookup service comes to mind...
A Logger object is used to log messages for a specific system or application component. Loggers are normally named, using a hierarchical dot-separated namespace. Logger names can be arbitrary strings, but they should normally be based on the package name or class name of the logged component, such as java.net or javax.
To use a different Layout with java. util. logging , set the Appender 's formatter property to the Layout of your choice. To do this in code, you can create a new Handler and use its setFormatter method, then assign the Handler to the Logger using logger.
Java Logging FormattersSimpleFormatter: This formatter generates text messages with basic information. ConsoleHandler uses this formatter class to print log messages to console. XMLFormatter: This formatter generates XML message for the log, FileHandler uses XMLFormatter as a default formatter.
INFO is a message level for informational messages. OFF is a special level that can be used to turn off logging. SEVERE is a message level indicating a serious failure. WARNING is a message level indicating a potential problem.
Your conventions are pretty standard and quite fine (imho).
The one thing to watch is memory fragmentation from excessive unnedded debug calls so, with Log4J (and most other Java logging frameworks), you end up with something like this:
if (log.isDebugEnabled()) {
log.debug("...");
}
because constructing that log message (that you probably aren't using) could be expensive, especially if done thousands or millions of times.
Your INFO level logging shouldn't be too "chatty" (and from what you say, it sounds like it isn't). INFO messages should be generally meaningful and significant, like the application being started and stopped. Things that you might want to know if you encounter a problem. Debug/fine level logging is more used for when you actually have a problem you're trying to diagnose. Debug/fine logging is typically only turned on when needed. Info is typically on all the time.
If someone doesn't want specific INFO messages from your classes, they are of course free to change your log4j configuration to not get them. Log4j is beautifully straightforward in this department (as opposed to Java 1.4 logging).
As for your HTTP thing, I've generally not found that to be an issue with Java logging because typically a single class is responsible for what you're interested in so you only need to put it in one place. In the (rare in my experience) when you want common log messages across seemingly unrelated classes, just put in some token that can easily be grepped for.
The following is the set of guidelines I follow in all my projects to ensure good performance. I have come to form this set of guidelines based on the inputs from various sources in the internet.
As on today, I believe Log4j 2 is by far the best option for logging in Java.
The benchmarks are available here. The practice that I follow to get the best performance is as follows:
12:01:00,127 INFO FILE_NAME=file1.txt - Processing starts 12:01:00,127 DEBUG FILE_NAME=file1.txt, CUSTOMER_ID=756 12:01:00,129 INFO FILE_NAME=file1.txt - Processing ends
private static final Marker sqlMarker = MarkerManager.getMarker("SQL"); private void method1() { logger.debug(sqlMarker, "SELECT * FROM EMPLOYEE"); }
int i=5, j=10; logger.info("Sample output {}, {}", ()->i, ()->j);
Do not use String concatenation. Use parameterized message as shown above
Use dynamic reloading of logging configuration so that the application automatically reloads the changes in the logging configuration without the need of application restart
Do not use printStackTrace()
or System.out.println()
The application should shut down the logger before exiting:
LogManager.shutdown();
<?xml version="1.0" encoding="UTF-8"?> <Configuration monitorinterval="300" status="info" strict="true"> <Properties> <Property name="filePath">${env:LOG_ROOT}/SAMPLE</Property> <Property name="filename">${env:LOG_ROOT}/SAMPLE/sample </Property> <property name="logSize">10 MB</property> </Properties> <Appenders> <RollingFile name="RollingFileRegular" fileName="${filename}.log" filePattern="${filePath}/sample-%d{yyyy-dd-MM}-%i.log"> <Filters> <MarkerFilter marker="SQL" onMatch="DENY" onMismatch="NEUTRAL" /> </Filters> <PatternLayout> <Pattern>%d{HH:mm:ss,SSS} %m%n </Pattern> </PatternLayout> <Policies> <TimeBasedTriggeringPolicy interval="1" modulate="true" /> <SizeBasedTriggeringPolicy size="${logSize}" /> </Policies> </RollingFile> <RollingFile name="RollingFileError" fileName="${filename}_error.log" filePattern="${filePath}/sample_error-%d{yyyy-dd-MM}-%i.log" immediateFlush="true"> <PatternLayout> <Pattern>%d{HH:mm:ss,SSS} %p %c{1.}[%L] [%t] %m%n </Pattern> </PatternLayout> <Policies> <TimeBasedTriggeringPolicy interval="1" modulate="true" /> <SizeBasedTriggeringPolicy size="${logSize}" /> </Policies> </RollingFile> </Appenders> <Loggers> <AsyncLogger name="com" level="trace"> <AppenderRef ref="RollingFileRegular"/> </AsyncLogger> <Root includeLocation="true" level="trace"> <AppenderRef ref="RollingFileError" level="error" /> </Root> </Loggers> </Configuration>
<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.8.1</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.8.1</version> </dependency> <dependency> <groupId>com.lmax</groupId> <artifactId>disruptor</artifactId> <version>3.3.6</version> </dependency> <!-- (Optional)To be used when working with the applications using Log4j 1.x --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-1.2-api</artifactId> <version>2.8.1</version> </dependency>
In @cletus' answer, he wrote of the problem of
if (log.isDebugEnabled()) {
log.debug("val is " + value);
}
which might be overcome by using SLF4J. It provides a formatting help
log.debug("val is {}", value);
where the message is only constructed if the level is debug.
So, nowaday, using SL4J and its companion logger, Logback, is advised for performance and stability reasons.
With respect to instantiating loggers, I have had some success using an Eclipse Java Template for setting up my loggers:
private static Logger log = Logger.getLogger(${enclosing_type}.class);
This avoids the problem of a JVM mucking about with your stack trace, and reduces (trivially, perhaps) the overhead from creating the stack trace in the first place.
The great thing about using a template like this is that you can share it with your team if you want to set a consistent standard across the board for loggers.
It looks like IntelliJ supports the same concept for a template variable representing the name of the enclosing type. I don't see a way to do it easily in NetBeans.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With