Tech Ads
Back to Article List
Originally published May 2004 [ Publisher Link ]
Java Application Logging
Application logging is one of those software tasks to which developers often give very little attention when designing an application. Although software patterns and anti-patterns are common practice in enterprise development, the subject of logging and debugging is rarely addressed, even though it is also an important part of the software development lifecycle. In this article we will explore the various APIs you can use to log your Java applications.
Possibly the crudest yet simplest Java logging facility is to use the System.out and System.err instances available in every Java class:
try { // Business Logic } catch (IOException exc) { System.err.println("Error connecting to external resource : " + exc); } catch (SQLException exc) { System.err.println("Database Error : " + exc); } catch (Exception exc) { System.err.println("Error : " + exc); } finally { System.out.println("Successfully completed Business Logic!"); } |
One of the biggest drawbacks of using this mechanism lies in its very simplicity. You only have two channels, Standard output or Standard error. Piping all debugging messages into single points results in a lack of error granularity. Add to that the fact that once an application goes into production, many of the logging messages may be either redundant or unnecessary, causing production logs to grow extremely large.
Among the main advantages of avoiding this rustic method and instead using a specific API for logging are the ability to precisely control error channels, log rotation, and facilities to activate and deactivate debugging levels at run time.
The 1.4 Java platform introduced a special logging package named Java Logging API. Under the namespace java.util.logging it offers several facilities for enhanced logging and debugging. The actual architecture of this API is very comprehensive, as you would expect from a standardized package in the JDK.
However, its same robustness in the realms of filtering and custom log formatting, among other things, make it a dense subject to master. Nonetheless, just so you can grasp what can be achieved with this logging API, the following snippet demonstrates a more comprehensive debugging process for the same try/catch block presented earlier:
// NOTE : The following classes are used importing // the java.util.logging package // Define a top level logger Logger logger = Logger.getLogger("com.osmosislatina"); // Define two distinct logs FileHandler logfileDev = new FileHandler("development.log"); FileHandler logfileNOC = new FileHandler("NOC.log"); // Set the message level to be sent on each log logfileDev.setLevel(Level.FINEST); logfileNOC.setLevel(Level.SEVERE); // Associate the Logger with the actual File Handler logger.addHandler(logfileDev); logger.addHandler(logfileNOC); try { // Business Logic } catch (IOException exc) { logger.log(Level.SEVERE,"Error connecting to external resource",exc); } catch (SQLException exc) { logger.log(Level.SEVERE,"Database Error",exc); } catch (Exception exc) { logger.log(Level.WARNING,"Error",exc); } finally { logger.info("Successfully completed Business Logic!"); } |
We first define a Logger class, which is the cornerstone of the Java Logging API. We then call its getLogger method to define a namespace for this particular instance. Later we declare two FileHandler instances that will represent actual logs on the filesystem. After, we use the setLevel method in the FileHandler class to define the message granularity to be sent to each log. The development.log file will contain every message from the FINEST level and greater, while the NOC.log will contain only messages marked as SEVERE. Ending these declarations we associate each FileHandler instance with the global Logger class.
As far as the try/catch block is concerned, notice that we now use the special method log available through the Logger class. This takes three fields: The level of the log message, a descriptive text, and the actual stack trace. The method info -- enclosed in the final section -- automatically defines a message with severity INFO. The Logging API defines seven levels, ranging from the lowest priority, FINEST, to the highest, SEVERE. Also available are an ALL level to activate all message types and an OFF option to deactivate all logging.
Upon execution of the previous code, two log files will be generated in the working directory. One of them -- development.log -- will contain every messages generated by the Logger class, while the NOC.log will contain messages marked as SEVERE, and because this is the highest level message it will include only messages of this type. Upon inspection of the generated logs, you will also notice that their format is XML-based, which is the logging API's default behaviour.
This example barely scratches the full capabilities of the Logging API, but with it, you probably realize its power for maintaining an orderly debugging process. Next up we will explore another alternative to logging your Java application through an Open Source framework.
Log4J is an Apache Software Foundation project created before Sun incorporated the standard Java Logging API into its JDK. Since it has been around awhile, it has a strong presence among a large community of Java developers. As with other open source development, its architecture has grown in an organic manner, incorporating features on a per-need basis, but with more than four years of existence, it's pretty much on par with the features available from its JDK counterpart.
You can test Log4J's capabilities with the following code, which is in the same context of the try/catch block illustrated with the other APIs:
// NOTE : The following classes are used importing // the org.apache.log4j package // Define a top level logger Logger logger = Logger.getLogger("com.osmosislatina"); // Define two distinct logs FileAppender logfileDev = new FileAppender(new PatternLayout(),"development.log",true); FileAppender logfileNOC = new FileAppender(new PatternLayout(),"NOC.log",true); // Set the message threshold to be sent on each log logfileDev.setThreshold(Level.ALL); logfileNOC.setThreshold(Level.FATAL); // Associate the Logger with the actual File Handler logger.addAppender(logfileDev); logger.addAppender(logfileNOC); try { // Business Logic } catch (IOException exc) { logger.log(Level.FATAL,"Error connecting to external resource",exc); } catch (SQLException exc) { logger.log(Level.FATAL,"Database Error",exc); } catch (Exception exc) { logger.log(Level.WARN,"Error",exc); } finally { logger.info("Successfully completed Business Logic!"); } |
Log4J also uses the Logger class terminology to define its root hierarchy. After we define its instance, we also generate two FileAppender objects which will represent the actual logs to be generated in the file system. The constructor of this particular class takes the following fields: a PatternLayout instance, which is used to assign a specific format to the log, the actual name of the log, and a boolean value used to indicate appending of all values sent to output.
The setThreshold belonging to the FileAppender class is used to restrict the level of messages being sent to a log. In our case, the file named development.log will receive all types of messages due to its setting ALL, while the NOC.log will receive only messages marked as FATAL. Log4J has five types of messages, ranging from the most severe, FATAL, to INFO. Also available are the OFF and ALL options to activate and deactivate all logging.
Log4J offers a compelling alternative to the Java Logging API, especially when it comes to extensibility, since an open source framework offers you the flexibility to make custom changes.
Jakarta Commons is another project by the Apache Software Foundation. Its main focus is to provide a thin wrapper between logging APIs like Avalon Logkit and the previously mentioned Log4J and JDK logging facilities. This particular API forms part of the greater Jakarta Commons library, which encompasses other widely used Java utilities that in general aim to provide a central repository of reusable components.
The Jakarta Commons logging API is not a substitute for either of the previously mentioned logging mechanisms, but a simple abstraction which allows you to plug into a specific logging implementation. If you are not a core developer for a Jakarta project like Struts or Tomcat, it is very unlikely you will encounter this API, but it is worth mentioning since it is in wide use on the aforementioned projects.
We just covered the most common logging facilities available to a Java developer, all of which allow you to take a more streamlined approach to debugging your code and establishing robust monitoring techniques once your applications go into production.