ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Build Flexible Logs With log4j
Pages: 1, 2, 3

Add Those Enhancements

JDBCAppender

We will start with logging to a place other than the screen or the console. Since database logging is quite popular, let us look at how we can modify our simple example to log to a database. The example application was tested on a MySQL server, but it will work with any database, provided you have the right JDBC drivers. A note of warning, though, about using the JDBCAppender in the current version of log4j: the documentation says that it is going to be completely replaced in the future with a revamped version. Although the inner working of this appender might change, the way one goes about using it is unlikely to change.



Let us start by opening the config-JDBC.properties file from our example application. The first statement should not come as a surprise. As with the simple example, we have set the root logger and its level to DEBUG, and attached an appender, R, to it. The next few lines define R to be of the type JDBCAppender and declare its properties.

log4j.appender.R=org.apache.log4j.jdbc.JDBCAppender
log4j.appender.R.URL=jdbc:mysql://localhost/LOG4JDemo
log4j.appender.R.user=default
log4j.appender.R.password=default
log4j.appender.R.sql=INSERT INTO JDBCTEST (Message) VALUES ('%d - %c - %p - %m')

The first line tells log4j that R is of the type JDBCAppender. The second line declares the URL to the database to which we want to log. The third and the fourth lines are the user name and password to use to log to this database. The last line is the SQL query that will be executed. We will talk more about this soon.

Developers who have used JDBC will instantly recognize the similarity between declaring these values and using JDBC in general. These are the values that you need to specifiy to any JDBC-compliant application. You will, of course, need to change these values to reflect your own. For example, if you are logging to Oracle, the URL in the above case will become jdbc:oracle:thin:@yourhostSID. Another thing to note: these drivers should be available to your application, which in turn means that log4j gets access to them.

Let us now talk more about that last statement. The SQL that you use to log into the database can be varied and changed according to your requirements. The statement that I have used logs the following to a table called JDBCTEST: the date(%d), the category/logger(%c), the priority(%p); the actual message(%m) is logged to a column called Message. log4j picks up this statement and passes it through a formatter, which applies the layout specified for this appender, which in turn replaces the pattern matches with the actual values. log4j, then, simply executes this formatted SQL.

You can, if required, log different things in different columns. For example, another valid statement could be:

INSERT INTO JDBCTEST (Date, Logger, Priority, Message) VALUES ('%d', '%c', '%p', '%m')

This means that in our database, we have defined our JDBCTEST with corresponding columns of Date, Logger, Priority, and Message. You can test the application with JDBC by specifying the path to the config-JDBC.properties as the parameter in the StartupServlet.

So what code changes are needed to start logging to the database now? None! That is where the enormous power of log4j really manifests itself. To start logging to the database, we don't need to make any code changes. We just start using a different configuration file with the right parameters, and the log statements that were coming on the console will now be directed to the JDBCTEST table in your database.

NDC/MDC

Nested Diagnostic Context (NDC) and Mapped Diagnostic Context (MDC) help in situations where one application is involved in simultaneously serving multiple clients, and you are interested in separating or distingushing log content from each of these multiple clients. A Web application is a really good example of such a situation.

So how do we separate or distinguish content from multiple clients? By marking each log statement with client-specific information. This, in a typical Web application, could include, among other things, the client's IP address, which is readily available to the servlet container. So, in NDC, you push this information on a stack when entering a context, and pop it when leaving that context. log4j uses this context-specific information when writing to the appender, if in your associated layout, you use the %x conversion pattern. To see how this is done, lets look at one configuration file (config-NDC.properties) and one source file (GetCommentsServlet.java).

There is a marked difference in the config-NDC file from the previous configuration files that we have seen. For the first time, we are looking at multiple loggers. The first few lines are similar to the config-simple file, and that is exactly what we want to do. For all of the loggers in our application, except the logger associated with our GetCommentsServlet, we want the output to come on console in a simple layout. For the demo.log4j.servlet.GetCommentsServlet logger, we have a separate appender (R1), which also logs to the screen using a console appender, but its conversion pattern includes the %x symbol.

    log4j.logger.demo.log4j.servlet.GetCommentsServlet=debug, R1
...
log4j.appender.R1.layout.ConversionPattern=%p - [%x] - %m%n

Note how we reference the logger for GetCommentsServlet. All loggers (except the rootLogger, which is referenced by log4j.rootLogger) can be referenced by log4j.logger.<name of logger>. This is true for most elements of log4j.

The source for GetCommentsServlet points to the way we would use NDC by pushing and popping client specific information to the NDC stack.

Notice lines 40 and 57. On line 40, we push the client-specific information on the stack. Now all enabled log statements will contain this specific information replacing the %x conversion pattern. On line 57, we pop this information off of the stack. So the log statements will not contain this client-specific information.

Having covered the basics of NDC, lets discuss Mapped Diagnostic Context (MDC). MDC is similar to NDC, except that instead of pushing and popping information on a stack, the client-specific information is maintained in a map (in a java.util.Map). This implies that there must be a unique key associated with each piece of client information. If you look at line 43 in GetCommentsServlet.java, you'll see we do exactly that.

MDC.put("RemoteHost", remHost);

The MDC class provides static methods to manipulate client-specific information in the map. Thus, with each log statement sent to an appender, this information about the RemoteHost will replace the corresponding conversion character for MDC, which is %X{key}. What is this key? This is the key that we associate with our remHost value while putting it in the map, RemoteHost. If, for example, we wanted to put the RemoteAddress in our output, we would do the following:

In the code:

MDC.put("RemoteAddress", 
req.getRemoteAddr());

In the configuration file, add the following:

%X{RemoteAddress}

An example of this configuration is in the config-MDC.properties file. Although it is similar to the config-NDC.properties file, it differs in two important things. First, we use MDC instead of NDC, and second, the appender for our second logger is a RollingFileAppender instead of the ConsoleAppender.

Tips And Tricks

The first tip, if you are a planning to be a regular user of log4j, is to go to the very source of tips -- the log4j user mailing list. An archive of all messages is maintained at the Mail Archive, and you can subscribe to the list by sending an email to the list.

Here are some more concrete tips:

  • ConfigureAndWatch: You must have noticed that each time you make a change to the configuration file, you have to restart your application. This can be a little bit cumbersome. log4j provides a mechanism by which it can monitor changes that you make to the application. To do so, replace PropertyConfigurator.configure(props); with PropertyConfigurator.configureAndWatch(props);, which uses a default value of 60 seconds to watch for changes to the configuration file. You can override that value if desired.

  • Additivity: If you go back to the config-MDC and config-NDC files, you will notice this line in both of them:

    log4j.additivity.demo.log4j.servlet.GetCommentsServlet=false

    What is additivity and why do we need to set it to false? Loggers, by default, inherit all of the appenders of their parents. So our demo.log4j.servlet.GetCommentsServlet logger would do so, too, and inherit the appender R from our rootLogger. This in turn means that all log messages arising from our GetCommentsServlet logger will go to two different appenders. One to its own and one to the inherited one, as well. Thus, messages will get printed twice. This may or may not be the desired effect. If both appenders are pointing to the same appender, let's say Console, then messages coming out of the GetCommentsServlet logger will print on the screen twice. To stop this behavior, we tell log4j to not inherit appenders from the ancestors of the GetCommentsServletLogger by setting its value to false. To see if this is true, all you need to do is to set this value to true (or remove this line) and you will see two messages on the screen.

Resources

At this point in time, the best resource for learning log4j remain the log4j Web site, the source code that comes with it, and the short manual that accompanies it. The log4j mailing list is also good.

Conclusion

log4j is a popular logging tool developed and distributed under the Apache Project. There are other logging tools available, including the log API bundled in JDK 1.4. log4j is superior to all of these APIs, as it provides unprecedented control over all aspects of logging and is hierarchical in nature. It provides runtime control over logging without having to touch the source code.

I hope this article has given you some direction on how to get started with log4j and to use some of its advanced features; however, the best way to learn log4j is to experiment with it. Use the accompanying example application as a starting guide. Modify the configuration files, add, remove appenders, play with the layouts, etc. This will give you confidence in using log4j in your own application and give you an understanding of how it works. Good Luck !

Vikram Goyal is the author of Pro Java ME MMAPI.


Return to ONJava.com.