Monday, February 15, 2010

Automatically reload log4j configuration in tomcat

I was looking for a way to have Log4j reload its configuration file automatically when it changes, in Tomcat. Log4j can do this with the configureAndWatch method, but the default initialization procedure (simply putting a log4j.properties file in the classpath) doesn't use configureAndWatch. You have to write at least a little bit of code to get Log4j to do this. I found the easiest solution for integration with Tomcat to be to implement a Tomcat LifecycleListener.

import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleListener;
import org.apache.log4j.LogManager;
import org.apache.log4j.PropertyConfigurator;

public class Log4JInitializer implements LifecycleListener
{
    private String propertiesFile;

    public String getPropertiesFile()
    {
        return this.propertiesFile;
    }

    public void setPropertiesFile(String propertiesFile)
    {
        this.propertiesFile = propertiesFile;
    }

    @Override
    public void lifecycleEvent(LifecycleEvent event)
    {
        if (Lifecycle.BEFORE_START_EVENT.equals(event.getType()))
            initializeLog4j();
    }

    private void initializeLog4j()
    {
        // configure from file, and let log4j monitor the file for changes
        PropertyConfigurator.configureAndWatch(propertiesFile);

        // shutdown log4j (and its monitor thread) on shutdown
        Runtime.getRuntime().addShutdownHook(new Thread()
        {
            @Override
            public void run()
            {
                LogManager.shutdown();
            }
        });
    }
}

I simply listen for the "BEFORE_START_EVENT", and if that happens (which is once per Tomcat startup) I initialize Log4j using the configureAndWatch method. I also don't forget to install a shutdown hook to cleanup the thread Log4j creates to poll the configuration file for changes (I could also have chosen to listen to the "AFTER_STOP_EVENT" from Tomcat in stead).

Package this in a jar, put it on the Tomcat classpath, and now you can configure it in your Tomcat serverl.xml.

<Server>
  ...
  <Listener className="Log4JInitializer" propertiesFile="/path/to/log4j.properties"/>
</Server>


Can't be much easier, and it does what it has to do.

15 comments:

Rafał Rusin said...

This is great. What is the license of this code? I'd like to use it.

Jan Van Besien said...

@Rafal
The license is "do whatever you want to do with it" ;-)

Rafał Rusin said...

Great! Thanks, I created repo and uploaded artifact. It's on Apache License now (so - do whatever you want :-) ).

http://github.com/rafalrusin/log4j-initializer

We could add configurable time inverval.

Lucas Theisen said...

I agree, this is a great approach. I already have a LifecycleListener to unload my DataSouce connection pool. Do you know if a log4j.properties dropped in the classpath will override this? I know tomcat will initialize log4j based upon the presence of a properties file in the classpath, but dont know if that will override if already configured?

Anonymous said...

Does the LogManager.shutdown() really kill the thread that configureAndWatch spawns (Daemon thread) ?

Any tips/tricks on how you would achieve startup/shutdown of log file configuration per webapp ?

cheers
Magnus

Abhi said...

Thanks for the idea. I added the lifecycle in Tomcat, I can see that Log4j file is read, however the components still complain that Log4j is not configured correctly. Is there a classloading issue here? Has anyone else faced this issue?

Abhi said...

Thanks for the idea. I added the lifecycle in Tomcat, I can see that Log4j file is read, however the components still complain that Log4j is not configured correctly. Is there a classloading issue here? Has anyone else faced this issue?

Abhi said...

Regarding my previous comment, there was a error in my code. My log4j file is an XML so I should have been using DomConfigurator instead of PropertyConfigurator. Once I changed that the example worked like a charm. Thanks again.

P.S. Its strange though that no exception was thrown by log4j when I tried PropertyConfigurator with an xml file instead of properties file.

Hajo said...

So... for XM users, change the next lines:

import org.apache.log4j.PropertyConfigurator;

becomes :

import org.apache.log4j.xml.DOMConfigurator;

and

PropertyConfigurator.configureAndWatch(propertiesFile);

becomes:

DOMConfigurator.configureAndWatch(propertiesFile);

Hajo said...

So... for log4j.xml users: change the next lines:

import org.apache.log4j.PropertyConfigurator;

becomes:

import org.apache.log4j.xml.DOMConfigurator;

and

PropertyConfigurator.configureAndWatch(propertiesFile);

becomes:

DOMConfigurator.configureAndWatch(propertiesFile);

Khaled Ayoubi said...

Great, I was able to do the same using ServletContextListener and keep the it per app instead..

@Override
public void contextInitialized(ServletContextEvent event) {
String path = event.getServletContext().getRealPath(propertiesFile);
PropertyConfigurator.configureAndWatch(path);
}

@Override
public void contextDestroyed(ServletContextEvent event) {
LogManager.shutdown();
}

propertiesFile could be context attribute

dan latham said...

This seems like a good approach, but it doesn't work for me yet. When I have the log4j.properties in the classes folder, my loggers echo:
13/03/23 05:44:02 INFO com.myco.mypackage.myclass: *log message*

but when I load it via this method, my loggers echo:
13/03/23 05:44:02 INFO myclass: *log message*

The full path is missing. Also, if I change the log level to something differen, for examplet:
log4j.category.com.myco.mypackage.myclass=debug

it doesn't affect the log level that log4j outputs. Any suggestions?

dan latham said...

The problem I was seeing was caused by a log4j jar in my web app's lib folder. I removed it (leaving the log4j jar in tomcat /lib) and it works.

Anonymous said...

Like Jan already said that he could have used AFTER_STOP_EVENT instead of the shutdownhook. I did that, as I was missing a lot of shutdown messages (guess the shutdownhook kicks in a little bit early).

Anonymous said...

We used it with tomcat 7, but it doesn't seem to be working with tomcat 8.5 anymore.