Log4j – Reload Logging Levels on Runtime

Excessive logging is a common cause of performance degrade of application. It is one of 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 especially be a problem for older environments using older hardware or environments dealing with very heavy concurrent volumes.

A balanced approach is to implement a "reloadable logging level" facility to turn extra logging ON / OFF 
when required in your day to day production support.

Lets see how this can be done using WatchService provided by Java 7.

log4j-logo-9173732

Step 1) Implements a WatchService which will listen for changes in given log4j file

Below given code initializes a thread which continuously watch for modifications in given log4j file [StandardWatchEventKinds.ENTRY_MODIFY]. As soon as a file change is observed, configurationChanged() method is invoked and DOMConfigurator.configure() method is used to again reload the log4j configuration.

Log4jChangeWatcherService.java

package com.howtodoinjava.demo;

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) Add Log4jConfigurator, an interface for your application to work with log4j

This class is a utility class which separates the log4j initialization code and reloading strategy from application code.

Log4jConfigurator.java

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();
		}
	}
}

3) Test the log4j reload events

To test the code, I am logging two two statements: one debug level and one info level. Both statements are logged after 2 seconds in a loop. I will change the logging level after few log statements and the log4j should be reloaded.

log4j-config.xml

<?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>

Log4jConfigReloadExample.java

package com.howtodoinjava.demo;

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);
		}
	}
}

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 !!

 

Sourcecode Download

Happy Learning !!

Was this post helpful?

Join 7000+ Fellow Programmers

Subscribe to get new post notifications, industry updates, best practices, and much more. Directly into your inbox, for free.

14 thoughts on “Log4j – Reload Logging Levels on Runtime”

  1. Hi Lokesh,
    Excellent article. It was really helpful. Won’t there be any performance impact because of one thread (Log4jChangeWatcherService in this case) running continuously ??

  2. Suil…you are correct..I had tested with

    PropertyConfigurator.configureAndWatch and is working lie a gem…:)…Thank you Lokesh and Sunil…

Comments are closed.

HowToDoInJava

A blog about Java and its related technologies, the best practices, algorithms, interview questions, scripting languages, and Python.