taming webapp logging with log4j

Logging is an aspect of programming that should follow some simple rules. If I should describe logging for a dictionary it would be something like: “Providing essential information with object introspection at a decent level of output that enables you to look into a running application or providing runtime information for debugging.”

But even the simplest thing can be complicated if you use Java…

There are different logging frameworks like log4j, commons logging or logback even though Java includes it’s own java.util.logging API. To make it even worse, the Java community came up with a simple logging facade so one can use the logging framework of choice but implement against the facade - WTF?!

a standard that sucks

I would guess that 99% of Java code is written against log4j. Nobody really want’s to switch a logging framework during implementation and most developers I know see log4j as a technical standard.

I won’t recommend using log4j to anyone. The log4j documentation is crap! There is no usefull literature either (I never thought that one would have to buy a book for logging…). The framework misses essential things like pattern matching filters (you will have to use log4j companions) and it is difficult to configure, especially on a tomcat.

Log4j is a bitch when it comes to deployment. One has to be careful where to put the log4j jar(s) and xml or plain text configuration files. If you do it wrong you end up with broken logging behavior; webapps log to the wrong log file, spit out stuff to the system out or stop logging at all.

this is how we do it

Our Java infrastructure is service oriented. We have a bunch of webservices hosted on serveral distributed tomcats and all wars depend on log4j for logging. We want to reload the logging configuration files at runtime (why is jboss able to do it and tomcat is not?!) and manipulate it via JMX.

additional log4j configuration

To achieve this, we have to put some additional configuration into every webapp. Log4j comes with a watchdog that looks for changes in the configuration files. The functionality can be enabled by using the configureAndWatch() method. There is a precanned Spring solution for this, but it has configuration overhead and environmental restrictions. So the best place to implement it ourselves is in a custom context listener:


   [...]
	
	<listener>
		<listener-class>de.nofail.TomcatLoggingListener</listener-class>
	</listener>
   [...]
   [...]
	public void contextInitialized(ServletContextEvent sce) {
		ServletContext servletContext = sce.getServletContext();
		try {
			String log4jFile = String.format("%s-log4j.xml", servletContext.getContextPath());
			String configFilename = new File(getClass().getResource(log4jFile).toURI()).getAbsolutePath();
			DOMConfigurator.configureAndWatch(configFilename, 1000);
		} catch (URISyntaxException e) {
			throw new IllegalStateException("Error resolving log4j configuration file for context=" + servletContext, e);
		}
	}
   [...]

This listener will use a config file named WEBAPP-log4j.xml lying in the root directory. The goal is, that every webapp has it’s own logging context. Every war should include a log4j.jar so that logging won’t be affected by other apps, the classloader hierarchie will ensure this. The different xml configuration files can than be placed in a shared tomcat directory for easy access of an operations team (which is probably yourself).

Using different configuration files has the advantage that you don’t have to mess around with appenders based on packages. That’s how one can configure different log levels for the same packages in each application, very usefull if you release a new application!

Java Management Extension

There are different ways to integrate JMX functionality into a webapp. The simplest approach is to use another servlet listener for propagating an mbean:


   [...]
	
	<listener>
		<listener-class>de.nofail.JMXListener</listener-class>
	</listener>
   [...]
   [...]
	public void contextInitialized(final ServletContextEvent sce) {
		try {
			TomcatLogging mbean = new TomcatLogging();
			ObjectName clutter = new ObjectName("de.nofail:type=TomcatLogging");
			ManagementFactory.getPlatformMBeanServer().registerMBean(mbean, clutter);
		} catch (Exception e) {
			throw new IllegalStateException("Could not create JMX context: " + e.getMessage(), e);
		}
	}
   [...]

If you are using Spring it’s easy to add JMX support via annotations.

Now you can access your running webapp via jconsole:

more information?

You can have a look at a working example based on Maven on github.

Drink a cup of Java™©®, but don’t forget the sugar!