How to Reload Log4j2 Configuration

Important Update

With Log4j2, Log4j can automatically reload its configuration upon modification. It will do so without losing log events while reconfiguration is taking place. (Link)

Excessive logging is a common cause of performance degradation of applications. It is one of the best practices to ensure proper logging within your Java EE application implementation.

However, be careful with the logging level that you enable in your production environment. Excessive logging will trigger high IO on your server and increase CPU utilization. This can be especially problematic for older environments using older hardware or environments dealing with heavy concurrent volumes.

The given example uses Java WatchService class to monitor the log4j configuration file changes and reload the logging configuration when such a change happens.

1. Implementing WatchService

Below the given code initializes a thread that continuously watches for modifications in the given log4j file using StandardWatchEventKinds.ENTRY_MODIFY.

As soon as a file change is observed, configurationChanged() method is invoked and DOMConfigurator.configure() method is used to reload the log4j configuration again.

import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
 
import org.apache.log4j.xml.DOMConfigurator;
 
public class Log4jChangeWatcherService implements Runnable
{
    private String configFileName = null;
    private String fullFilePath = null;
 
    public Log4jChangeWatcherService(final String filePath) {
        this.fullFilePath = filePath;
    }
     
    //This method will be called each time the log4j configuration is changed
    public void configurationChanged(final String file)
    {
        System.out.println("Log4j configuration file changed. Reloading logging levels !!");
        DOMConfigurator.configure(file);
    }
 
    public void run() {
        try {
            register(this.fullFilePath);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
    private void register(final String file) throws IOException {
        final int lastIndex = file.lastIndexOf("/");
        String dirPath = file.substring(0, lastIndex + 1);
        String fileName = file.substring(lastIndex + 1, file.length());
        this.configFileName = fileName;
 
        configurationChanged(file);
        startWatcher(dirPath, fileName);
    }
 
    private void startWatcher(String dirPath, String file) throws IOException {
        final WatchService watchService = FileSystems.getDefault().newWatchService();
         
        //Define the file and type of events which the watch service should handle
        Path path = Paths.get(dirPath);
        path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
 
        Runtime.getRuntime().addShutdownHook(new Thread() {
            public void run() {
                try {
                    watchService.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
 
        WatchKey key = null;
        while (true) {
            try {
                key = watchService.take();
                for (WatchEvent< ?> event : key.pollEvents()) {
                    if (event.context().toString().equals(configFileName)) {
                         
                        //From here the configuration change callback is triggered
                        configurationChanged(dirPath + file);
                    }
                }
                boolean reset = key.reset();
                if (!reset) {
                    System.out.println("Could not reset the watch key.");
                    break;
                }
            } catch (Exception e) {
                System.out.println("InterruptedException: " + e.getMessage());
            }
        }
    }
}

2. Demo

We are creating a new class Log4jConfigurator that separates the log4j initialization code and reloading strategy from the application code.

package com.howtodoinjava.demo;
 
public class Log4jConfigurator 
{
    //This ensures singleton instance of configurator
    private final static Log4jConfigurator INSTANCE = new Log4jConfigurator();
 
    public static Log4jConfigurator getInstance()
    {
        return INSTANCE;
    }
 
    //This method will start the watcher service of log4j.xml file and also configure the loggers
    public void initilize(final String file) {
        try
        {
            //Create the watch service thread and start it.
            //I will suggest to use some logic here which will check if this thread is still alive;
            //If thread is killed then restart the thread
            Log4jChangeWatcherService listner = new Log4jChangeWatcherService(file);
             
            //Start the thread
            new Thread(listner).start();
        } 
        catch (Exception e) 
        {
            e.printStackTrace();
        }
    }
}

To test the code, I am logging two statements: one at DEBUG level and one at INFO level. Both statements are logged after 2 seconds in a loop. I will change the logging level after a few log statements.

Our expectation is that the log4j configuration should be reloaded in runtime.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
 
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
 
  <appender name="console" class="org.apache.log4j.ConsoleAppender"> 
    <param name="Target" value="System.out"/> 
    <layout class="org.apache.log4j.PatternLayout"> 
      <param name="ConversionPattern" value="[%t] %-5p %c %x - %m%n"/> 
    </layout> 
  </appender> 
 
  <root> 
    <priority value ="info" />  
    <appender-ref ref="console" /> 
  </root>
   
</log4j:configuration>

Lets test.

import org.apache.log4j.Logger;
 
public class Log4jConfigReloadExample 
{
    private static final String LOG_FILE_PATH = "C:/Lokesh/Setup/workspace/Log4jReloadExample/log4j-config.xml";
     
    public static void main(String[] args) throws InterruptedException 
    {
        //Configure logger service
        Log4jConfigurator.getInstance().initilize(LOG_FILE_PATH);
         
        //Get logger instance
        Logger LOGGER = Logger.getLogger(Log4jConfigReloadExample.class);
         
        //Print the log messages and wait for log4j changes
        while(true)
        {
            //Debug level log message
            LOGGER.debug("A debug message !!");
            //Info level log message
            LOGGER.info("A info message !!");
             
            //Wait between log messages
            Thread.sleep(2000);
        }
    }
}

The program output.

[main] INFO  com.howtodoinjava.demo.Log4jConfigReloadExample  - A info message !!
[main] INFO  com.howtodoinjava.demo.Log4jConfigReloadExample  - A info message !!
[main] INFO  com.howtodoinjava.demo.Log4jConfigReloadExample  - A info message !!
[main] INFO  com.howtodoinjava.demo.Log4jConfigReloadExample  - A info message !!
[main] INFO  com.howtodoinjava.demo.Log4jConfigReloadExample  - A info message !!
Log4j configuration file changed. Reloading logging levels !!
[main] DEBUG com.howtodoinjava.demo.Log4jConfigReloadExample  - A debug message !!
[main] INFO  com.howtodoinjava.demo.Log4jConfigReloadExample  - A info message !!
[main] DEBUG com.howtodoinjava.demo.Log4jConfigReloadExample  - A debug message !!
[main] INFO  com.howtodoinjava.demo.Log4jConfigReloadExample  - A info message !!

Happy Learning !!

Comments

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.

Our Blogs

REST API Tutorial

Dark Mode

Dark Mode