Monday, January 26, 2009

Log4j

Recently I started a new project at work. It is a server-side program suite for managing a large number of devices out in the world, with a database back-end and all sorts of goodies. I decided that it would be a good idea to use a standard logging package, instead of my standard method.

Not that my standard method is bad, it just isn't very flexible. There is a program-wide DEBUG flag set at startup which controls all debugging output of any sort, from any source. Since I'd be working with a much larger system than I'm used to, I thought it'd be nice to use a package with a few more features than 'debugging output is on' and 'debugging output is off.'

So Log4j seemed like the best choice. It's open-source (from the Apache foundation) and provides all of the neat little features that might be useful:

  1. It can selectively turn on/off logging depending on the source of the call.
  2. It can direct logging output to any of a number of different destinations.
  3. It has importance levels, so you can filter out the debugging messages from stable code but continue to see the errors/warnings from the same object.

Sounds pretty good, huh? It certainly did to me. But if that's the case, why am I writing this, you might ask? If you're a loyal reader, you may have noticed that I don't have many positive posts...

Log4j has virtually no documentation. Well... no, let me revise that: Log4j has virtually no free documentation.

There's a short introduction describing what it can do, how fast it is, ... BUT it doesn't tell you how to do it.

There's a FAQ which tells you what it can do, how fast it is, ... but again, not how to do it. It just gives you more details on the same subjects as the introduction.

There's even a Wiki! But no help there, it's aimed at developers of Log4j, not developers of programs using Log4j.

So where can I learn how to use this feature-rich, fast logging platform for Java? Well, you can buy a $20 PDF book that describes everything!

But surely there's information available online, through Google or something, right? No. Everybody just seems to know how to use it, but nobody is saying how they know. Did everybody read the source code? I don't know... I certainly don't want to learn how to use a library by reading its source, that's what documentation is for!

Oh, there are some examples on the Log4j page, and a few more spread around the web, but guess what? They're the same examples! Nobody thought that it would be a good idea to, perhaps, come up with their own to describe the other features that aren't documented on the home page. They just copied the same examples and assumed they explained everything.

So here are a few little pieces to help the lost developer trying to configure Log4j that doesn't want to buy the book or read through the source:

Other useful things you can do are use a rolling file appender (class is 'org.apache.log4j.RollingFileAppender'), which is given a maximum file size. It will output all messages to that file until it hits the max size, and then rename it and create a new one to write to. Here are the properties you should know about with it:

  • log4j.appender.RFA.File=filename.log
  • log4j.appender.RFA.MaxFileSize=1M
  • log4j.appender.RFA.MaxBackupIndex=3

This will append to filename.log until it reaches 1 megabyte, then it will rename it to filename.log.1 (renaming filename.log.1 to filename.log.2 and so on as necessary), keeping files up to filename.log.3, but no more.

You can specify multiple appenders for the root logger like this:

 log4j.rootCategory=ALL, Console, RFA

One more useful trick is to send logger output to specific places. You can do that like this:

 log4j.logger.loggerName=ALL, A2
 log4j.additivity.loggerName=false

That will cause all logger output from that specific logger to go to A2, but not to the root logger's appender.

And finally, to get Log4j working, you need to add a little bit of code to the initialization of your program. I'd suggest doing this in a static block in your main class. You just need to call

 PropertyConfigurator.configureAndWatch(
     "log4j.properties", 5000)

That will cause the properties file that you just wrote using all of the tips I gave to be loaded at startup, and it will be checked for updates every 5 seconds. If it changes, the new settings will go into effect. That allows you to selectively enable logging as you notice unexpected behavior, but not be overwhelmed by log messages all the time.

So what's my conclusion? I know, I jumped from complaining about Log4j to showing how it works and what you can do with it. I actually kind of like the system. It is convenient to work with (except when you need to configure something), it monitors the configuration file so I can enable logging after the program starts, and I can supress messages for parts of the program I've already finished debugging.

So I'd say that you should give it a try, and hopefully someone will help you out when you can't find the right option for your current situation. And if nothing else works, read the source. It's painful, but that's how I figured out the additivity options.