Build Flexible Logs With log4jby Vikram Goyal
log4j is the open source logging tool developed under the Jakarta Apache project. It is a set of APIs that allows developers to write log statements in their code and configure them externally, using properties files. This article explains the main concepts of this tool, followed by some advanced concepts using a Web-based example application.
A Note on the Accompanying Application
The application that accompanies this article is a Web-based application, which has been developed and tested using JDK1.3.1, Tomcat 4.0.1,
log4j 1.2.4 and Ant 1.4.1 on a Windows XP machine. You can download the log4jdemo.war application file that you can deploy in any servlet engine. You can also download the source code. This application has the following flows, which are found in most Web applications:
- Main Page --> Login --> Welcome Page --> Submit Comments.
- Main Page --> Register --> Login.
- Main Page --> Forgot Password.
Keep in mind that the application itself is trivial and is just a path to the
main goal in this article -- understanding
log4j. I have opted for a Web application to explain
log4j instead of a normal application because of the high number of Web applications using
log4j as the preferred logging mechanism.
log4j can be downloaded from the Apache Web site. The download is available in two formats, tar.gz and zip. Both downloads contain the source code for the
log4j API, documentation, examples, and distributable
log4j executables in the form of .jar files. Extract the files to a location of your choice. The source code is supplied, in case you want to do a build for yourself. The build can be done with the help of Ant and the
build.xml files that are provided; you can customize the build script if required. The build will produce the
log4j-1.2.4.jar file in the
dist/lib directory if built using the dist target. Note that for the build to be successful, you will need to have the Java Management Extensions (JMX) API in your classpath, as well.
To make the
log4j classes available to your application, you need to have the
log4j-1.2.4.jar file in the classpath of your application. This file, as described above, is in the
dist/lib folder. For our example Web application, this means that this .jar file should be in the
WEB-INF/lib directory of our application. This is all that is required to make sure that your application can access and use the
log4j API. The rest of the effort needs to go into writing a configuration file that tells
log4j what, where, and how to log, and into introducing actual log statements in your application.
What Is a Logger?
Before we start cutting some code, let's look at the basics of
log4j. There are three aspects of
log4j: logger, appender, and layout. A logger logs to an appender in a particular layout (style).
Think of a logger as the silent component within your application that will take your request for logging and log it. Each class in your application can have an individual logger or a common logger.
log4j provides a root logger that all of your loggers will inherit from. This also means that if you don't have an individual logger for each class, you can always use the root logger by calling
Logger.getRootLogger(), although this is not recommended.
To create a logger and use it for logging in your classes, you typically need to call a static method of the
Logger class that will retrieve a logger for you by name. If the particular logger has not already been created it will be created for you, and there will always be one instance of this logger in your JVM.
Loggers need to know where to send your requests for logging. This is where the appenders come into picture.
log4j supports writing to files (
FileAppender), to console (
ConsoleAppender), to databases (
JDBCAppender), to NT event logs (
NTEventLogAppender), to SMTP servers (
SMTPAppender), to remote servers (
SocketAppender), and others. An appender defines the properties of the logging target to
log4j. So, for example, if we attach a
JDBCAppender to one of our loggers, we are telling
log4j that logging requests from our logger should go to a certain database (using a certain username, password, connection string, etc.). All of these are properties of an appender (a
JDBCAppender in this case), which are used by
log4j to target our logging requests.
Loggers and appenders are concerned with originating and targeting our log requests. What about the format of the output? This is where a layout comes into picture. A layout defines the style and content of the output log. Some built-in layouts are provided with
log4j, and you can create your own layouts, if they are required. A layout will tell
log4j whether or not to include the date and time in the output, whether to include information about the logger, whether to include the line number from which the log originated, etc.
Loggers follow a parent-child relationship pattern. As mentioned earlier,
log4j provides a root logger by default. This implies that all of our loggers will ultimately inherit from this root logger. A parent-child relationship is followed by using a named pattern. To illustrate, let's say you have a class called
MyClass in a package called
com.foo.bar. In this class, let's instantiate a logger by the name of
com.foo.bar.MyClass. In this case,
com.foo.bar.MyClass logger will be a child of the logger
com.foo.bar -- if it exists. If it does not exist, the only ancestor of the
com.foo.bar.MyClass logger will be the root logger provided by
log4j (in case there is no logger by the name of
Each logger in
log4j is assigned a level. If you don't assign a level to a logger yourself,
log4j automatically assigns to your logger the level of the parent logger. Since there is a possibility that the user might not assign any level to the self-created logger(s), the root logger always has a default level assigned to it, which is
DEBUG. Thus, all loggers are guaranteed to have a level.
There are five levels of logging in
The signs indicate the ordering of the logging that
How does each logger having a level affect our logging efforts? Because a log request made from within our application using a particular logger will be sent to a corresponding appender only if the level of the log request is greater than or equal to the level of the logger itself. This means that all log requests from within our application can be of these five types only. This is a very important rule, and really is at the core of
log4j. Let's take our previous example of
com.foo.bar.MyClass logger to explain this further.
Suppose within your class (which is called
com.foo.bar.MyClass -- no surprise here; we named our logger based on the class where it is instantiated, a standard practice), you use the logger to log a message of type
WARN. Now let's say your logger
com.foo.bar.MyClass has the level set at
ERROR through your configuration. What does this mean? This means that the log request of type
WARN will not be sent to any appender.
log4j recognizes that the level of the log request is lower than the level of the logger, and thus does not log this request. Had the log request been of type
FATAL, it would have been logged. Similarly, had the level of the logger been set at level
WARN, the log request would have been logged. This leads to a very important concept about
log4j. You can externally change the level of each and every logger without having to change your source code, recompile, and deploy.
log4j is mostly configured using an external configuration file. The API provides for configuring the
log4j system through code as well. The next section will guide you through a simple example and show how the configuration can be done.