How to configure JDK logging easily
It’s no secret. I am not a fan of using a third-party framework where the functionality exists in your runtime environment. There are quite a few logging frameworks available for Java applications and most developers will have used at least one at some point. Log4J, SLF4J, Commons Logging are three examples. Log4J provides a full-featured logging framework whereas the other two provide an API for logging, which enables the actual implementation used to be chosen at runtime (see factory-factory-factory).
The JDK has provided the logging framework since Java 1.4. However, the uptake of native JDK logging is still pretty low in the Java community. There are a few reasons why this may be the case:
- The JDK version is not easy to configure. I hope to make it a little easier in this post.
- Other frameworks existed before the JDK implementation. Log4J released its first version in 1999 whereas Java 1.4, with the java.util.logging package, did not emerge until 2002.
- Standardisation: whilst the JDK implementation provides much flexibility, it does not have ready made implementations of some features (e.g. logging to a database), meaning that if one team in an organisation creates this feature, reusing it code somewhere else requires the team to support it (version and release it etc).
Using java.util.logging
Using the default configuration of the JDK logging utilities is easy. Simply get a logger instance for your class and start logging messages to it:
package foo.bar; import java.util.logging.Logger; public class Foo { private final Logger logger = Logger.getLogger(getClass().getName()); void logSomething(String value) { logger.info(String.format("Hello %s", value)); } }
However, a method call to logSomething("test")
produces the somewhat untidy output below:
Jan 2, 2011 1:19:48 AM foo.bar.Foo logSomething INFO: Hello test
This isn’t ideal. Also, if we log anything at the FINE, FINER or FINEST levels, this will not appear at all in the default configuration.
Changing the configuration
In configuring your JDK logging setup, you have two options. Either a configuration file (to which you must specify the absolute path as a system property) or a configuration class. I have found the simplest and most reusable way to configure JDK logging is to use a configuration class. Here is a simple example:
Configuration class:
package foo.bar; import java.util.logging.Formatter; import java.util.logging.SimpleFormatter; import java.util.logging.Handler; import java.util.logging.ConsoleHandler; import java.util.logging.LogManager; import java.util.logging.Logger; public class LogConfiguration { private final LogManager logManager; private final Logger rootLogger; private final Handler defaultHandler = new ConsoleHandler(); private final Formatter defaultFormatter = new SimpleFormatter(); public DefaultLoggingConfiguration() { super(); this.logManager = LogManager.getLogManager(); this.rootLogger = Logger.getLogger(""); configure( ); } final void configure() { defaultHandler.setFormatter(defaultFormatter); defaultHandler.setLevel(Level.ALL); rootLogger.setLevel(Level.ALL); rootLogger.addHandler(defaultHandler); logManager.addLogger(rootLogger); } }
Now to use your configuration class, simply add the following system property when you launch the application that does logging:
-Djava.util.logging.config.class=foo.bar.LogConfiguration
The key to the above configuration class is the line which gets the root logger, to which you can add a handler, to which in turn you can add a formatter. The root logger is obtained by requesting a logger named with the empty string:
Logger rootLogger = Logger.getLogger("");
This is by no means an exhaustive example but this simple setup will allow you to configure the things that you generally need to change (log level, log output format) without too much effort.
Changing the log level
To change the log level, simple adjust the line defaultHandler.setLevel(Level.ALL);
to the desired Level.
Altering the format
To change the output format, simply implement your own Formatter – overriding theformat(LogRecord record)
method. Then assign defaultFormatter
in yourLogConfiguration
class to your Formatter instance. Something like this will give more of a Log4J feel to the output:
package foo.bar; public class LoggingFormatter extends Formatter { private static final String LINE_SEP = System.getProperty("line.separator"); private static final String FIELD_SEP = " "; private ThreadLocal dateFormat = new ThreadLocal() { protected DateFormat initialValue() { return new SimpleDateFormat("dd/MM/yyyy HH:mm:ss.SSS"); } }; public final String format(LogRecord record) { StringBuilder logEntry = new StringBuilder(); logEntry.append(dateFormat.get().format(new Date(record.getMillis()))); logEntry.append(FIELD_SEP); logEntry.append(getShortClassName(record.getLoggerName())); logEntry.append(FIELD_SEP); logEntry.append(record.getLevel().getName()); logEntry.append(FIELD_SEP); logEntry.append(record.getMessage()); logEntry.append(LINE_SEP); return logEntry.toString(); } final String getShortClassName(String fullName) { return fullName.substring(fullName.lastIndexOf(".") + 1); } }
Then the corresponding change to the configuration class to use the above Formatter:
private final Formatter defaultFormatter = new LoggingFormatter();
This will make your two lines of logging output shown above appear as follows:
02/01/2011 1:19:48 AM Foo INFO Hello test
Happy logging.