Saturday, July 7, 2007

Changing Log4j logging levels dynamically

Simple problem and may seem oh-not-so-cool. Make the log4j level dynamically configurable. You should be a able to change from DEBUG to INFO or any of the others. All this in a running application server.

First the simple, but not so elegant approach. Don't get me wrong (about the elegance statement) this approach works.

Log4j API

Often applications will have custom log4j properties files. Here we define the appenders and the layouts for the appenders. Somewhere in the java code we have to initialize log4j and point it to this properties file. We can use the following API call to configure and apply the dynamic update.
org.apache.log4j.PropertyConfigurator.configureAndWatch(logFilePath, logFileWatchDelay);
  • Pass it the path to the custom log4j.properties and a delay in milliseconds. Log4j will periodically check the file for changes (after passage of the configured delay time).

Spring Helpers

If you are using Spring then you are in luck. Spring provides ready-to-use classes to do this job. You can use the support class org.springframework.web.util.Log4jWebConfigurer. Provide it values for log4jConfigLocation, log4jRefreshInterval. For the path you can pass either one that is relative to your web application (this means you need to deploy in expanded WAR form) or provide an absolute path. I prefer the latter; that way I can keep my WAR file warred and not expanded.

There is also a web application listener class org.springframework.web.util.Log4jConfigListener that you can use in the web.xml file. The actual implementation of the Spring class Log4jWebConfigurer does the call to either:
org.apache.log4j.PropertyConfigurator.configureAndWatch 
//OR
org.apache.log4j.xml.DOMConfigurator.configureAndWatch

Log4j spawns a separate thread to watch the file. Make sure your application has a shutdown hook where you can org.apache.log4j.LogManager.shutdown() to shut down log4j cleanly. The thread unfortunately does not die if your application is undeployed. Thats the only downside of using Log4j configureAndWatch API. In most cases thats not a big deal so I think its fine.

JMX Approach

JMX according to me is the cleanest approach. Involves some leg work initially but is well worth it. This example here is run on JBoss 4.0.5. Lets look at a simple class that will actually change the log level.
package com.aver.logging;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

public class Log4jLevelChanger {
   public void setLogLevel(String loggerName, String level) {
      if ("debug".equalsIgnoreCase(level)) {
         Logger.getLogger(loggerName).setLevel(Level.DEBUG);
      } else if ("info".equalsIgnoreCase(level)) {
         Logger.getLogger(loggerName).setLevel(Level.INFO);
      } else if ("error".equalsIgnoreCase(level)) {
         Logger.getLogger(loggerName).setLevel(Level.ERROR);
      } else if ("fatal".equalsIgnoreCase(level)) {
         Logger.getLogger(loggerName).setLevel(Level.FATAL);
      } else if ("warn".equalsIgnoreCase(level)) {
         Logger.getLogger(loggerName).setLevel(Level.WARN);
      }
  }
}
  • Given a logger name and a level to change to this code will do just that. The code needs some error handling and can be cleaned up a little. But this works for what I am showing.
  • To change the log level we get the logger for the specified loggerName and change to the new level.
My application uses Spring so the rest of the configuration is Spring related. Now we need to register this bean as an MBean into the MBeanServer running inside JBoss. Here is the Spring configuration.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" 
  "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>

  <bean id="exporter"  
        class="org.springframework.jmx.export.MBeanExporter"
 lazy-init="false">
 <property name="beans">
    <map>
  <entry key="bean:name=Log4jLevelChanger"
         value-ref="com.aver.logging.Log4jLevelChanger" />
    </map>
 </property>
  </bean>

  <bean id="com.aver.logging.Log4jLevelChanger"
 class="com.aver.logging.Log4jLevelChanger">
  </bean>

</beans>
  • In Spring we use the MBeanExporter to register your MBeans with the containers running MBean Server. 
  • I provide MBeanExporter with references to beans that I want to expose via JMX.
  • Finally my management bean is Log4jLevelChanger is registered with Spring.
Thats it. With this configuration your bean will get registered into JBoss's MBean server. By default Spring will publish all public methods on the bean via JMX. If you need more control on what methods get published then refer to Spring documentation. I will probably cover that topic in a separate blog since I had to do all of that when i set up JMX for a project using Weblogic 8.1. With Weblogic 8.1 things are unfortunately not that straight forward as above. Thats for another day another blog.

One thing to note here is that the parameter names are p1 (for loggerName) and p2 for (level). This is because I have not provided any meta data about the parameters. When I do my blog on using JMX+Spring+CommonsAttributes under Weblogic 8.1, you will see how this can be resolved. BTW for jdk 1.4 based Spring projects you must use commons attributes tags provided by Spring to register and describe your beans as JMX beans. The initial minor learning curve will save you tons of time later.
By Mathew

1 comment:

Anonymous said...

Rather than code the call to org.apache.log4j.PropertyConfigurator.configureAndWatch, it would be nice if one could simply specify the refresh delay in log4j.properties.