Logging in Java Applications Logging provides a way to capture information about the operation of an application. Once captured, the information can be used for many purposes, but it is particularly useful for debugging, troubleshooting, and auditing. This article takes a look at logging in Java applications, presenting information on logging software, benefits, costs, and basic techniques. LOGGING SOFTWARE There are many logging packages available for Java applications. Here are a few: Java Logging API -- Part of Java 2 Standard Edition Version 1.4. The Java Logging API supports dynamic configuration, hierachical loggers, multiple logging levels, and multiple output formats (plain text and XML). Log4j -- An open source logging framework from the Apache Jakarta project. Log4j supports dynamic configuration, hierarchical loggers, multiple logging levels, and multiple output formats (plain text, HTML, XML, Unix syslog, Windows NT Event Log, and others). It was designed and built with an emphasis on speed and has been ported to C, C++, C#, Ruby, and Eiffel. Logging Toolkit for Java -- A logging framework from the IBM alphaworks project. Logging Toolkit for Java supports multiple loggers, filters, handlers, formatters, and multiple output devices. Protomatter Syslog -- A logging framework that is part of Protomatter, an open source collection of Java utility classes. Syslog is roughly based on the Unix syslog facility and supports channels, multiple levels, and BEA WebLogic. It is also compatible with the Java Logging API. Java Logging Framework -- A simple logging framework from The Object Guy. Java Logging Framework supports multiple loggers, filters, message formatting, and multiple output devices. 1
Basic Logging Program import java.io.*; import java.util.logging.*; public class BasicLogging { public static void main(string[] args) { // Get a logger; the logger is automatically created if // it doesn't already exist Logger logger = Logger.getLogger("loggertest.BasicLogging"); // Log a few message at different severity levels logger.severe("my severe message"); logger.warning("my warning message"); logger.info("my info message"); logger.config("my config message"); logger.fine("my fine message"); logger.finer("my finer message"); logger.finest("my finest message"); OUTPUT: Nov 30, 2009 4:17:13 PM loggertest.basiclogging main SEVERE: my severe message Nov 30, 2009 4:17:13 PM loggertest.basiclogging main WARNING: my warning message Nov 30, 2009 4:17:13 PM loggertest.basiclogging main INFO: my info message 2
EVALUATING A LOGGING PACKAGE Configuration: How is logging configured, programmatically or with a configuration file? The latter is better, because you don't have to write or change code to change the configuration. Is dynamic configuration supported? Dynamic configuration is better than static configuration, because configuration changes can take effect without restarting the application. Loggers: Does the package support multiple loggers? For example, can you have one logger for database messages and one logger for GUI messages? Having multiple loggers makes logging more flexible, particularly for large systems. Does the package support heirachical loggers? This capability allows logging at arbitrarily fine granularity with ease of configuration. Levels: Does the package support multiple logging levels, e.g. DEBUG, WARN, ERROR? Having multiple levels provides a mechanism for controlling the level of detail that is captured. For example, if DEBUG is more detailed than ERROR, and you configure the package to output only ERROR messages, DEBUG messages will not be captured. Filters: Does the package support filters? Using either levels or arbitrary categories, filters provide a way to control which messages go to which output devices. Output Formatting: What output formats does the package support? Plain text is the most general purpose format, but XML, HTML, and Unix syslog format can be useful. Does the package support message formatting (layout)? Being able to specify the layout of log messages -- date/time format, fields shown, etc. -- can make logs more readable. 3
Output Devices: Does the package support multiple output devices? Minimally, the package should support output to files, but output to the console, sockets, JMS, and email can be useful. Speed: Is the package fast? Logging adds overhead to an application, so speed is important. LOGGING BENEFITS Here are some of the benefits of using logging in an application: Logging can generate detailed information about the operation of an application. Once added to an application, logging requires no human intervention. Application logs can be saved and studied at a later time. If sufficiently detailed and properly formatted, application logs can provide audit trails. By capturing errors that may not be reported to users, logging can help support staff with troubleshooting. By capturing very detailed and programmer-specified messages, logging can help programmers with debugging. Logging can be a debugging tool where debuggers are not available, which is often the case with multi-threaded or distributed applications. Logging stays with the application and can be used anytime the application is run. LOGGING COSTS Logging is beneficial, but it does not come without costs: Logging adds runtime overhead, from generating log messages and from device I/O. Logging adds programming overhead, because extra code has to be written to generate the log messages. Logging increases the size of code. If logs are too verbose or badly formatted, extracting information from them can be difficult. Logging statements can decrease the legibility of code. 4
If log messages are not maintained with the surrounding code, they can cause confusion and can become a maintenance issue. (I have seen code with log messages that had no relation to what the code was doing. It was very confusing.) If not added during initial development, adding logging can require a lot of work modifying code. Writing Log Records to a Log File To make a logger write log records to a file, you need to add a file handler to the logger. try { // Create a file handler that write log record to a file called my.log FileHandler handler = new FileHandler("my.log"); // Add to the desired logger Logger logger = Logger.getLogger("com.mycompany"); logger.addhandler(handler); catch (IOException e) { By default, a file handler overwrites the contents of the log file each time it is created. This example creates a file handler that appends. try { // Create an appending file handler boolean append = true; FileHandler handler = new FileHandler("my.log", append); // Add to the desired logger Logger logger = Logger.getLogger("com.mycompany"); logger.addhandler(handler); catch (IOException e) { Setting the Log Level of a Logger The log level of a logger controls the severity of messages that it will log. In particular, a log record whose severity is greater than or equal to the logger's log level is logged. A log level can be null, in which case the level is inherited from the logger's parent. Note: Logger handlers also have a log level. A log record must first pass the logger's log level before it is compared to the handler's log level. // Get a logger Logger logger = Logger.getLogger("com.mycompany"); // Set the level to a particular level logger.setlevel(level.info); // Set the level to that of its parent logger.setlevel(null); 5
// Turn off all logging logger.setlevel(level.off); // Turn on all logging logger.setlevel(level.all); 6
Java TM Logging Overview 1.0 Java TM Logging Overview The logging APIs are described in detail in the J2SE API Specification. The goal of this document is to provide an overview of key elements. 1.1 Overview of Control Flow Applications make logging calls on Logger objects. Loggers are organized in a hierarchical namespace and child Loggers may inherit some logging properties from their parents in the namespace. Applications make logging calls on Logger objects. These Logger objects allocate LogRecord objects which are passed to Handler objects for publication. Both Loggers and Handlers may use logging Levels and (optionally) Filters to decide if they are interested in a particular LogRecord. When it is necessary to publish a LogRecord externally, a Handler can (optionally) use a Formatter to localize and format the message before publishing it to an I/O stream. Each Logger keeps track of a set of output Handlers. By default all Loggers also send their output to their parent Logger. But Loggers may also be configured to ignore Handlers higher up the tree. Some Handlers may direct output to other Handlers. For example, the MemoryHandler maintains an internal ring buffer of LogRecords and on trigger events it publishes its LogRecords through a target Handler. In such cases, any formatting is done by the last Handler in the chain. 7
1.2 Log Levels Each log message has an associated log Level. The Level gives a rough guide to the importance and urgency of a log message. Log level objects encapsulate an integer value, with higher values indicating higher priorities. The Level class defines seven standard log levels, ranging from FINEST (the lowest priority, with the lowest value) to SEVERE (the highest priority, with the highest value). 1.3 Loggers As stated earlier, client code sends log requests to Logger objects. Each logger keeps track of a log level that it is interested in, and discards log requests that are below this level. Loggers are normally named entities, using dot-separated names such as "java.awt". The namespace is hierarchical and is managed by the LogManager. The namespace should typically be aligned with the Java packaging namespace, but is not required to follow it slavishly. For example, a Logger called "java.awt" might handle logging requests for classes in the java.awt package, but it might also handle logging for classes in sun.awt that support the client-visible abstractions defined in the java.awt package. In addition to named Loggers, it is also possible to create anonymous Loggers that don't appear in the shared namespace. See section 1.14. Loggers keep track of their parent loggers in the logging namespace. A logger's parent is its nearest extant ancestor in the logging namespace. The root Logger (named "") has no parent. Anonymous loggers are all given the root logger as their parent. Loggers may inherit various attributes from their parents in the logger namespace. In particular, a logger may inherit: 8
Logging level. If a Logger's level is set to be null then the Logger will use an effective Level that will be obtained by walking up the parent tree and using the first non-null Level. Handlers. By default a Logger will log any output messages to its parent's handlers, and so on recursively up the tree. Resource bundle names. If a logger has a null resource bundle name, then it will inherit any resource bundle name defined for its parent, and so on recursively up the tree. 1.4 Logging Methods The Logger class provides a large set of convenience methods for generating log messages. For convenience, there are methods for each logging level, named after the logging level name. Thus rather than calling "logger.log(constants.warning,..." a developer can simply call the convenience method "logger.warning(..." There are two different styles of logging methods, to meet the needs of different communities of users. First, there are methods that take an explicit source class name and source method name. These methods are intended for developers who want to be able to quickly locate the source of any given logging message. An example of this style is: void warning(string sourceclass, String sourcemethod, String msg); Second, there are a set of methods that do not take explicit source class or source method names. These are intended for developers who want easy-to-use logging and do not require detailed source information. void warning(string msg); For this second set of methods, the Logging framework will make a "best effort" to determine which class and method called into the logging framework and will add this information into the LogRecord. However, it is important to realize that this automatically inferred information may only be approximate. The latest generation of virtual machines perform extensive optimizations when JITing and may entirely remove stack frames, making it impossible to reliably locate the calling class and method. 1.5 Handlers J2SE provides the following Handlers: 9
StreamHandler: A simple handler for writing formatted records to an OutputStream. ConsoleHandler: A simple handler for writing formatted records to System.err FileHandler: A handler that writes formatted log records either to a single file, or to a set of rotating log files. SocketHandler: A handler that writes formatted log records to remote TCP ports. MemoryHandler: A handler that buffers log records in memory. It is fairly straightforward to develop new Handlers. Developers requiring specific functionality can either develop a Handler from scratch or subclass one of the provided Handlers. 1.6 Formatters J2SE also includes two standard Formatters: SimpleFormatter: Writes brief "human-readable" summaries of log records. XMLFormatter: Writes detailed XML-structured information. As with Handlers, it is fairly straightforward to develop new Formatters. 1.7 The LogManager There is a global LogManager object that keeps track of global logging information. This includes: A hierarchical namespace of named Loggers. A set of logging control properties read from the configuration file. See section 1.8. There is a single LogManager object that can be retrieved using the static LogManager.getLogManager method. This is created during LogManager initialization, based on a system property. This property allows container applications (such as EJB containers) to substitute their own subclass of LogManager in place of the default class. 10
Writing to a File. Default format is XML. package loggertest; import java.io.*; import java.util.logging.*; public class LogToFile { public static void main(string[] args) { Logger logger = Logger.getLogger("com.mycompany"); logger.setlevel(level.finer); try { // Create a file handler that write log record to a file called my.log FileHandler handler = new FileHandler("my.log"); // Add handler to the desired logger logger.addhandler(handler); catch (IOException e) { // Log a few message at different severity levels logger.severe("aaa my severe message"); logger.warning("my warning message"); logger.info("my info message"); logger.config("my config message"); logger.fine("my fine message"); logger.finer("my finer message"); logger.finest("my finest message"); File Output: <?xml version="1.0" encoding="windows-1252" standalone="no"?> <!DOCTYPE log SYSTEM "logger.dtd"> <log> <record> <date>2009-12-01t14:39:30</date> <millis>1259699970189</millis> <sequence>0</sequence> <logger>com.mycompany</logger> <level>severe</level> <class>loggertest.logtofile</class> <method>main</method> <thread>10</thread> <message>aaa my severe message</message> </record> <record> <date>2009-12-01t14:39:30</date> <millis>1259699970207</millis> <sequence>1</sequence> <logger>com.mycompany</logger> 11
<level>warning</level> <class>loggertest.logtofile</class> <method>main</method> <thread>10</thread> <message>my warning message</message> </record> <record> <date>2009-12-01t14:39:30</date> <millis>1259699970207</millis> <sequence>2</sequence> <logger>com.mycompany</logger> <level>info</level> <class>loggertest.logtofile</class> <method>main</method> <thread>10</thread> <message>my info message</message> </record> <record> <date>2009-12-01t14:39:30</date> <millis>1259699970208</millis> <sequence>3</sequence> <logger>com.mycompany</logger> <level>config</level> <class>loggertest.logtofile</class> <method>main</method> <thread>10</thread> <message>my config message</message> </record> <record> <date>2009-12-01t14:39:30</date> <millis>1259699970208</millis> <sequence>4</sequence> <logger>com.mycompany</logger> <level>fine</level> <class>loggertest.logtofile</class> <method>main</method> <thread>10</thread> <message>my fine message</message> </record> <record> <date>2009-12-01t14:39:30</date> <millis>1259699970208</millis> <sequence>5</sequence> <logger>com.mycompany</logger> <level>finer</level> <class>loggertest.logtofile</class> <method>main</method> <thread>10</thread> <message>my finer message</message> </record> </log> 12