15.4 Using the log4j Package

You may have heard or read about the log4j library from other sources, but in case you haven't, let's briefly discuss the library's history here. Like Struts, log4j is an open source project that is part of the Jakarta set of projects. It's essentially a set of Java classes and interfaces that provides logging functionality to multiple types of output destinations. It has been around for several years and is constantly being refined and tuned for all types of Java development. In fact, log4j has been so successful that it has been ported to several other very popular languages, including C, C++, Python, and even .NET.

At the time of this writing, log4j has released Version 1.2.5, which is its 22nd release. The next major version, 1.3, is in the works, but it won't be released for a while. Version 1.2 is backward compatible with earlier versions, so if you are using 1.1.3, this material will still be relevant for you.

According to the creators of log4j, it was built with two central concepts in mind: speed and flexibility. One of the distinctive features of the logging framework is its notion of inheritance in categories, or loggers as they are now called. log4j supports a parent/child relationship between the configured loggers in the environment. For example, if we configured a logger for all the classes in the com.oreilly.struts package and another logger for all the classes in the com.oreilly.struts.storefront package, the first logger would be the parent of the second. This hierarchical structure gives us the flexibility to control what log messages are written out based on such things as the package structure.

You don't need to go this far if your requirements don't call for it. If you want, you can configure a single root logger for your entire environment. You can configure log4j for your specific needs, and you can change its behavior whenever you like by simply editing an external configuration file—you don't have to change the application's source code.

A discussion of log4j could fill a small book. I'll assume that you are familiar with the basic concepts and will cover only the essentials of how to integrate log4j with the Struts framework here. If you haven't yet become familiar with log4j, this is a good time to do so. For a more detailed discussion, you can download or view the documentation at the Jakarta log4j web site at http://jakarta.apache.org/log4j.

15.4.1 Integrating log4j with Struts

To ensure that the log4j libraries are available to your Struts applications, you should place the log4j JAR file in the WEB-INF/lib directory for each web application that you deploy. Resist the temptation to put it inside the container-wide lib directory, even if you're deploying multiple web applications with log4j. If you do attempt to install it at the container level, you probably will encounter one or more ClassNotFoundException problems.

Based on the requirements set forth in the 2.3 Servlet specification, the web container should automatically load all JAR files in the WEB-INF/lib directory, including the log4j library. After this initial step is complete, you are free to use log4j as the logging implementation for the Commons Logging package.

Keep in mind that the configuration of log4j is totally independent of the configuration of the logging implementation for the Commons Logging package. You still need to understand how to configure log4j (if that's the implementation you choose) and perform the necessary steps required by the log4j package.

15.4.2 What Are Loggers?

The org.apache.log4j.Logger is the central class in the log4j toolkit. Other than configuration, most of the functionality is performed through this class. In earlier versions of the log4j project, the org.apache.log4j.Category class implemented this functionality. To promote backward compatibility, the Logger class extends the Category class. Although the methods in the Category class have not yet been deprecated, you should always go through the Logger class itself. Eventually, the Category class will be removed from the library.

When used with Struts, most of the log4j classes and interfaces (other than the configuration aspects of log4j) are encapsulated within the Commons Logging API.

15.4.3 Configuring log4j Appenders

With log4j, you can send log messages to multiple destinations. log4j refers to a message destination as an appender. The log4j framework provides the following appenders for you to use out of the box:

·         Console Appender

·         File Appender

·         Socket Appender

·         Java Message Service (JMS) Appender

·         NT Event Logger Appender

·         Unix Syslog Appender

·         Null Appender

·         SMTP Appender

·         Telnet Appender

·         Asynchronous Appender

The log4j framework allows one or more appenders to be established for a logging environment. You can even send log messages to particular appenders, based on various conditions. The other nice feature of the appender architecture is that if none of these default appenders meets your application's requirements, you can create your own by extending the org.apache.log4j.AppenderSkeleton class.

15.4.4 Understanding the log4j Log Levels

A log message in log4j can be assigned one of five different levels or priorities. The levels allow you to set a threshold for a particular logger and filter out any log messages that don't reach the threshold for which the Logger is configured. The five logging levels are:

·         DEBUG

·         INFO

·         WARN

·         ERROR

·         FATAL

An earlier version of log4j also defined the levels OFF and ALL; however, these seem to have been deprecated and probably should be avoided. If you set the threshold to DEBUG, you'll get the same results as using ALL, and OFF isn't really necessary because you can simply choose not to configure an appender for the environment, which will stop all logging from occurring.

There is a cascading effect that causes only levels equal to the threshold and higher to be logged. For example, if a threshold of WARN is configured, only messages with a level of WARN, ERROR, or FATAL will make it to an output destination.

15.4.5 Initializing log4j

There are many properties that can be configured for the log4j toolkit. In fact, log4j is so flexible that all the configuration options can't be covered here. The best source of information is the log4j manual itself. You can find the manual online at http://jakarta.apache.org/log4j/docs/documentation.html, and it's also available locally when you download log4j.

Because log4j doesn't make any assumptions about the environment in which it is running, you need to configure the environment for your particular needs. In other words, no default appenders are configured out of the box.

There are various ways in which you can initialize the configuration properties for the log4j environment. We will focus on two related, but distinct, approaches here.

15.4.5.1 Initializing using the log4j.properties file

The first approach is to create a file called log4j.properties that contains the necessary configuration elements for your logging needs. This file must follow the guidelines of the java.util.Properties format. One of these guidelines is that each property is in the format key=value.

Example 15-5 illustrates a very simple log4j configuration file that logs messages with a logging threshold of INFO to the console using an org.apache.log4j.ConsoleAppender.

Example 15-5. An example log4j.properties file
# A basic log4j configuration file that creates a single console appender
 
# Create a single console appender that logs INFO and higher
log4j.rootLogger=INFO, stdout
 
# Configure the stdout appender to go to the console
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
 
# Configure the stdout appender to use the PatternLayout
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
 
# Pattern to output the caller's filename and line number.
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n

The configuration file shown in Example 15-5 is a very simple example of setting up a single appender—in this case, the ConsoleAppender, which directs log messages to System.out.

This log4j.properties file must be installed in the WEB-INF/classes directory so that the log4j environment will be able to locate it and use it to configure the logging environment for the web application. If you have multiple web applications, you can have a separate log4j.properties file for each.

The log4j configuration file shown in Example 15-5 sends log messages to only a single destination, the console. However, you can configure the log messages to go to multiple locations and also have certain messages go to certain locations based on the level of the message and other parameters. Example 15-6 shows a simple example using two appenders.

Example 15-6. A log4j configuration file using two appenders
# A sample log4j configuration file
 
# Create two appenders, one called stdout and the other called rolling
log4j.rootLogger=DEBUG, stdout, rolling
 
# Configure the stdout appender to go to the console
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
 
# Configure the stdout appender to use the PatternLayout
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
 
# Pattern to output the caller's filename and line number
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n
 
# Configure the rolling appender to be a RollingFileAppender
log4j.appender.rolling=org.apache.log4j.RollingFileAppender
 
# Configure the name of the logout for the rolling appender
log4j.appender.rolling.File=output.log
 
# Set up the maximum size of the rolling log file
log4j.appender.rolling.MaxFileSize=100KB
 
# Keep one backup file of the rolling appender
log4j.appender.rolling.MaxBackupIndex=1
 
# Configure the layout pattern and conversion pattern for the rolling appender
log4j.appender.rolling.layout=org.apache.log4j.PatternLayout
log4j.appender.rolling.layout.ConversionPattern=%d{ABSOLUTE} - %p %c - %m%n

The log4j configuration file in Example 15-6 creates one appender that logs messages to the console, just as in Example 15-5, and another appender that logs messages to a log file called output.log. Again, we won't try to cover all of the configuration settings for log4j; you can learn more from the log4j web site.

15.4.5.2 Initializing using an XML file

The second approach to initializing the configuration properties for the log4j environment is to use an XML file. Example 15-7 illustrates an XML file that configures the same information as in Example 15-5.

Example 15-7. A log4j configuration file using an XML format
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
 
<log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'>
 
  <appender name="stdout" class="org.apache.log4j.ConsoleAppender">
    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern" value="%5p [%t] (%F:%L) - %m%n"/>
    </layout>     
  </appender>
       
  <root>
    <priority value ="INFO" />
       <appender-ref ref="stdout" />
  </root>     
</log4j:configuration>

You normally place this XML file in the WEB-INF/classes directory, just as with the log4j.properties file. However, you must set the log4j.configuration system property equal to the name of the file, so that the log4j environment knows which file to load. There's no default filename when using the XML format. (We didn't have to do this with the properties file because the name log4j.properties is part of the log4j default initialization. If it locates this file anywhere in the classpath, it will use it to initialize the log4j environment.)

There are various ways to set the log4j.configuration property, and the various containers may provide alternative methods. In Tomcat Version 4.0, for example, you can set a variable called CATALINA_OPTS in the catalina.bat file to provide this information to the logging environment. For example:

set CATALINA_OPTS=-Dlog4j.configuration=log4j.xml

When you start up Tomcat, the log4j environment will then be able to locate the XML configuration file. Other containers may provide alternate methods for setting the value, but you can always set the value on the Java command line as a system property. You will probably need to modify the container's startup script using this approach, however:

java -Dlog4j.configuration=log4j.xml

If the log4j environment is unable to find a valid configuration file, either properties-based or XML-based, you will see something similar to the following message when you first attempt to initialize the logging environment:

log4j:WARN No appenders could be found for logger XXX.
log4j:WARN Please initialize the log4j system properly.

The XXX in the first message will actually show the name of the logger for which no appenders were configured.

15.4.5.3 Specifying a relative versus an absolute path

When you use a system property to configure the log4j configuration file within a web application, the file is relative to the web application by default. The following example tells log4j to search for a file called log4j.xml in the WEB-INF/classes directory for the web application:

java -Dlog4j.configuration=log4j.xml

Most containers use a separate class loader for each web application, and some containers may not allow the web applications to know about classes or JARs loaded by the container itself. However, if you need to use an absolute path, you can specify one like this:

java -Dlog4j.configuration=file:/c:/dev/env/log4j.xml

Be careful when using an absolute path—because the configuration file is not relative to a web application, all web applications will share the same one.

Generally speaking, specifying a relative path is much more flexible than using an absolute path because you can't always guarantee the directory structure of all your target environments.

15.4.5.4 Synchronization issues

There's one more issue that you should be aware of when logging to resources such as filesystems. Even though log4j is able to handle multiple client threads using the same appender (because all threads are synchronized), if you have multiple appenders writing to the same resource or file, you will have unpredictable results. In other words, there is no synchronization between appenders, even within the same JVM.

This really has nothing to do with a deficiency in the log4j design; it's just a case of not being able to easily synchronize multiple writers to a resource. The easiest way to solve this problem is to ensure that if you have multiple appenders or web applications logging to the filesystem, you don't allow them to log to the same file. If you do, you will probably experience synchronization-related issues.

15.4.6 Log File Rollover

In a normal production environment, log files can grow quite large if not managed properly. If the logging threshold is set to DEBUG or INFO or if the files are not purged from time to time, the files can grow without bounds.

It's a good idea to periodically back up the log files and start again with an empty log file. For some production environments, this "rollover" period may be nightly; others may only need to perform this routine weekly. Unless you can shut down the application while you back up the log files, it's very cumbersome to back them up manually.

Fortunately, log4j provides a type of appender that automatically swaps the log file out with a new one while at the same time maintaining a backup of the old log file. The org.apache.log4j.DailyRollingFileAppender class provides the ability to log to a file and roll over or back up the log file while the application is still running. You can also specify the rollover frequency and the date pattern that will be used for the backup names. Having this functionality available out of the box makes log4j invaluable to any application that needs to roll over log files at user-defined intervals.