Log4j 之 在运行时重新加载日志记录级别

过多的日志记录是导致应用程序性能下降的常见原因。这是确保Java EE应用程序实现中正确记录的最佳实践之一。但是,请注意在生产环境中启用的日志记录级别。过多的日志记录将触发服务器上的高IO,并增加CPU使用率。对于使用较旧硬件的较旧环境或处理大量并发卷的环境而言,这尤其可能成为问题。

一种平衡的方法是实现“可重载日志记录级别”功能,以打开/关闭额外的日志记录 
在日常生产支持中需要时。

让我们看看如何使用Java 7提供的WatchService完成此操作。

Log4j重新加载

步骤1)实现一个WatchService,它将监听给定log4j文件中的更改

下面的给定代码初始化了一个线程,该线程连续监视给定log4j文件[StandardWatchEventKinds.ENTRY_MODIFY]中的修改。观察到文件更改后,将立即调用configurationChanged()方法,并使用DOMConfigurator.configure()方法再次重新加载log4j配置。

Log4jChangeWatcherService.java

package com.how2codex.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)添加Log4jConfigurator,这是您的应用程序与log4j一起使用的接口

此类是一个实用程序类,它将log4j初始化代码和重载策略与应用程序代码分开。

Log4jConfigurator.java

package com.how2codex.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)测试log4j重新加载事件

为了测试代码,我记录了两个两个语句:一个调试级别和一个信息级别。两个语句在循环2秒后都被记录。我将在一些日志语句之后更改日志记录级别,并且应该重新加载log4j。

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.how2codex.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.how2codex.demo.Log4jConfigReloadExample  - A info message !!
[main] INFO  com.how2codex.demo.Log4jConfigReloadExample  - A info message !!
[main] INFO  com.how2codex.demo.Log4jConfigReloadExample  - A info message !!
[main] INFO  com.how2codex.demo.Log4jConfigReloadExample  - A info message !!
[main] INFO  com.how2codex.demo.Log4jConfigReloadExample  - A info message !!
Log4j configuration file changed。 Reloading logging levels !!
[main] DEBUG com.how2codex.demo.Log4jConfigReloadExample  - A debug message !!
[main] INFO  com.how2codex.demo.Log4jConfigReloadExample  - A info message !!
[main] DEBUG com.how2codex.demo.Log4jConfigReloadExample  - A debug message !!
[main] INFO  com.how2codex.demo.Log4jConfigReloadExample  - A info message !!

 

源代码下载

saigon has written 1445 articles

Leave a Reply