Tuesday, January 26, 2010

Migrating Log4J properties to XML format

A few weeks on from my JBoss logging experience and I've worked out one thing so far. JBoss certainly seems to prefer the XML format over the old properties file format. So, I started to think about migrating the dozens of properties files we have to an XML format to reduce some of the pain.

I was not interested in doing that manually, and I was surprised to find that there was no tool to convert from the log4j.properties format to XML. At least, not one that I could Google.

So, in the spirit of open source, I bashed out a tool that does just that. Feed it a log4j.properties and it will spit out an equivalent XML file. It's a bit rough but seems to work for the cases I threw at it, so help yourself and let me know if it works for you. You need a JRE version 1.5 or better to run it, but I figure anyone using Log4J must surely have Java installed!

The project page, downloads and source are all over here: http://code.google.com/p/log4j-properties-converter/


Tuesday, January 12, 2010

Using your own Log4J with JBoss

If you use JBoss and Log4J, you've probably seen this error in JBoss' server.log file...

log4j:ERROR A "org.jboss.logging.appender.FileAppender" object is not assignable to a "org.apache.log4j.Appender" variable.
log4j:ERROR The class "org.apache.log4j.Appender" was loaded by 
log4j:ERROR [BaseClassLoader@73f05c08{vfszip:/home/foo/work/tools/other/JEE_Servers/jboss-5.1.0.GA/server/default/deploy/smallapp.war/}] whereas object of type 
log4j:ERROR "org.jboss.logging.appender.FileAppender" was loaded by [org.jboss.bootstrap.NoAnnotationURLClassLoader@1cb1c5fa].
log4j:ERROR Could not instantiate appender named "FILE".

The InterGoogleWeb suggests "remove log4j.jar from your war and all is sweet". That's true and it will fix the immediate problem. The webapp class loader loads org.apache.log4j.Appender from your application's copy of Log4J, and loads the custom JBoss appender org.jboss.logging.appender.FileAppender from a different classloader. In these multi-classloader scenario's it is appropriate to get a ClassCastException if two classes are in different loaders, even if they are normally assignable (this is enforcing Class Namespace Isolation). Removing your log4j.jar removes Log4J in the webapp class loader, leaving JBoss's class loader to happily load these by itself and the problem appears to go away.

But, since when is Log4J part of the JavaEE spec? Remove Log4J at your own peril, otherwise you'll one day deploy to Tomcat, or Resin, or any number of app servers and get ClassNotFoundException's or NoClassDefFoundError's because you did not package Log4J!

So what's happening here. Consider this fairly typical scenario.
  • Your application uses Log4J and packages it up in WEB-INF/lib. 
  • You have a log4j.properties file which is on the classpath, but not in your application's WEB-INF/classes (or don't have a log4j.properties at all). 
  • You let Log4J auto-discover this properties file, rather than hard-code it or set a system property. 
This means when Log4J goes and executes Thread.currentThread().getContextClassLoader().loadResource("log4j.properties"), it will load your configuration, right?


In JBoss 5.1.0 GA (and possibly other versions), what you get is the log4j.properties bundled in jboss/bin/run.jar. That JAR is higher in the class loading hierarchy than your copy of the properties file, so it will always be picked up first. And you got it, the FileAppender that JBoss's log4j.properties declares is the special "org.jboss.logging.appender.FileAppender" class, the same one mentioned in the error message.

So what to do?
  • You can try using the XML format for the log4J configuration, because Log4J looks for log4j.xml before it looks for log4j.properties. This appears to work OK.
  • You can rename the log4j.properties inside the "run.jar" to something else, and give JBoss a system property to use the changed configuration file. Not sure if this would have other effects though
  • You can package up log4j.properties in WEB-INF/classes and that seems to work. But then you can't change your logging config without re-deploying your app 
  • Use Logback and ditch Log4J. Logback provides Log4J adapters, and it works really well. Thats a higher impact change to your app though
  • You can probably ignore the error if you don't care about logging, it relates to your application, not the server.
If I find a solution I'll update this post, but if you have one feel free to share!