JMX兑现Log4J级别的运行时动态更改

JMX实现Log4J级别的运行时动态更改

    首先来介绍一下MBean暴露的接口,主要是根据filter得到logger,设置logger的对象,动态的得到当前log4j的配置等,这个比较简单。

import org.apache.log4j.Level;

public interface LoggingConfig {

	/**
	 * 
	 * @param filter returns only loggers, which contain the filter string
	 * @return all available loggers
	 */
	public String[] getLoggers(String filter);
	
	/**
	 * assigns the {@link Level#INFO} to the given class
	 * @param target the FQCN of the class
	 */
	public void assignInfoLevel(String target);

	/**
	 * assigns the {@link Level#WARN} to the given class
	 * @param target the FQCN of the class
	 */
	public void assignWarnLevel(String target);

	/**
	 * assigns the {@link Level#ERROR} to the given class
	 * @param target the FQCN of the class
	 */
	public void assignErrorLevel(String target);

	/**
	 * assigns the {@link Level#DEBUG} to the given class
	 * @param target the FQCN of the class
	 */
	public void assignDebug(String target);
	
	/**
	 * assigns the {@link Level#FATAL} to the given class
	 * @param target the FQCN of the class
	 */
	public void assignFatalLevel(String target);
	
	/**
	 * assigns the {@link Level#TRACE} to the given class
	 * @param target the FQCN of the class
	 */
	public void assignTraceLevel(String target);
	
	/**
	 * deactivates the logging of the given class
	 * @param target the FQCN of the class
	 */
	public void deactivateLogging(String target);
	
	/**
	 * reloads the log4j configuration from the <code>log4j.properties</code> file in the classpath 
	 */
	public void resetConfiguration();

	/**
	 * 
	 * @return the log4j configuration from the <code>log4j.properties</code> file in the classpath 
	 */
	public String printLog4jConfig();


    下面的是它的实现类,实现了NotificationPublisherAware接口,在运行的时候会注入一个NotificationPublisher对象,实现Notification的发送。

import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.management.Notification;

import org.apache.log4j.Level;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import org.apache.log4j.config.PropertyPrinter;
import org.apache.log4j.spi.LoggerRepository;
import org.springframework.jmx.export.notification.NotificationPublisher;
import org.springframework.jmx.export.notification.NotificationPublisherAware;

public class LoggingConfigImpl implements LoggingConfig, NotificationPublisherAware {
	
	
	private Map<NotificationType, Long> notificationTypeMap = new HashMap<NotificationType, Long>();
	private NotificationPublisher publisher;

	public void setNotificationPublisher(NotificationPublisher notificationPublisher) {
        this.publisher = notificationPublisher;
    }
	
	public String[] getLoggers(String filter) {
		LoggerRepository r = LogManager.getLoggerRepository();

		Enumeration<Logger> enumList = r.getCurrentLoggers();

		Logger logger = null;
		List<String> resultList = new ArrayList<String>();
		while (enumList.hasMoreElements()) {
			logger = (Logger) enumList.nextElement();
			if (filter == null
					|| (filter != null && logger.getName().contains(filter))) {
				resultList.add(logger.getName());
			}
		}

		return (String[]) resultList.toArray(new String[] {});
	}

	public void assignInfoLevel(String target) {
		assignLogLevel(target, Level.INFO);
	}

	public void assignWarnLevel(String target) {
		assignLogLevel(target, Level.WARN);
	}

	public void assignErrorLevel(String target) {
		assignLogLevel(target, Level.ERROR);
	}

	public void assignDebug(String target) {
		assignLogLevel(target, Level.DEBUG);
	}

	public void assignFatalLevel(String target) {
		assignLogLevel(target, Level.FATAL);
	}

	public void deactivateLogging(String target) {
		assignLogLevel(target, Level.OFF);
	}

	public void assignTraceLevel(String target) {
		assignLogLevel(target, Level.TRACE);
	}

	private void assignLogLevel(String target, Level level) {
		String message = level.toString() + " for '" + target + "'";
		Logger existingLogger = LogManager.exists(target);
		if(existingLogger != null) {
			Level currentLevel = existingLogger.getLevel();
			if(currentLevel == null) {
				message = "initial to " + message;
			} else {
				message = "from " + currentLevel.toString() + " to " + message;
			}
		}
			 
		LogManager.getLogger(target).setLevel(level);
		sendNotification(NotificationType.CHANGE_LOG_LEVEL, message);
	}

	private synchronized void sendNotification(NotificationType notificationType, String message) {
		Long counter = 0L;
		if(!notificationTypeMap.containsKey(notificationType))
			notificationTypeMap.put(notificationType, counter);
		
		counter = notificationTypeMap.get(notificationType);
		notificationTypeMap.put(notificationType, Long.valueOf(counter + 1));
		
		Notification notification = new Notification(notificationType.toString(), this, counter);
		notification.setUserData(message);
		publisher.sendNotification(notification);
	}

	public void resetConfiguration() {
		
		
		ClassLoader cl = getClass().getClassLoader();
		LogManager.resetConfiguration();
		URL log4jprops = cl.getResource("log4j.properties");
		if (log4jprops != null) {
			PropertyConfigurator.configure(log4jprops);
		}
		sendNotification(NotificationType.RESET_CONFIGURATION , "used file: " + log4jprops.getFile());
	}

	public String printLog4jConfig() {
		StringWriter sw = new StringWriter();
		PrintWriter pw = new PrintWriter(sw);
		PropertyPrinter pp = new PropertyPrinter(pw);
		pp.print(pw);
		// System.out.println(sw.toString());
		return sw.toString();
	}


}


下面定义的是Notification的类型

public enum NotificationType {
     CHANGE_LOG_LEVEL, RESET_CONFIGURATION
}


其实最主要的就是下面的配置文件,借助sping的JMX Support,就可以避免了MBean的注册等一些细节。

<!-- Service to set the log level of a class. -->
    <bean id="loggingMBean" class="de.stefanheintz.log.jmxservice.LoggingConfigImpl"/>
	<bean id="exporterLogConfig" class="org.springframework.jmx.export.MBeanExporter">
		<property name="beans">
			<map>
				<entry key="de.stefanheintz.log:jmxservice=loggingConfiguration" value-ref="loggingMBean" />
			</map>
		</property>
		<property name="assembler">
	      <bean class="org.springframework.jmx.export.assembler.InterfaceBasedMBeanInfoAssembler">
	        <property name="managedInterfaces">
	          <value>de.stefanheintz.log.jmxservice.LoggingConfig</value>
	        </property>
	      </bean>
	    </property>
	</bean>



    首先我们先介绍一下运行该程序的系统属性的设置:

-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=8004
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false

-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=8004
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=true
-Dcom.sun.management.jmxremote.access.file=D:/temp/jmxremote.access
-Dcom.sun.management.jmxremote.password.file=D:/temp/jmxremote.password


下面我们介绍一下测试,我们构造一个客户端程序,然后使用jconsole调用刚才我们暴露的接口来更改log级别,然后客户端程序的log信息会有相应的调整,然后再jconsole里面可以看到客户端发出的Notification。

public class LetsGo {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		Test test = new TestImpl();
                
	}

}

public interface Test {

	void logAllLogLevels();
}


import org.apache.log4j.Logger;

public class TestImpl implements Test {

	private static final Logger logger = Logger.getLogger(TestImpl.class);
	
	public void logAllLogLevels() {
		while (true) {
			try {
				Thread.currentThread().sleep(500L);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			logger.trace("This is the TRACE logging");
			logger.debug("This is the DEBUG logging");
			logger.warn("This is the WARN logging");
			logger.info("This is the INFO logging");
			logger.error("This is the ERROR logging");		
			logger.fatal("This is the FATAL logging");
		}
	}

}


用jconsole连接后,可以找到我们的mbean,然后我们调用mbean暴露的函数,可以看到客户端的日志信息会实时的变化。


JMX兑现Log4J级别的运行时动态更改
JMX兑现Log4J级别的运行时动态更改


1 楼 asialee 2010-03-27  
大家没有遇到要动态更改级别的问题吗?
2 楼 calorie520 2010-03-27  
曾开发过这样的系统,通过日志控制台,下发日志策略到日志系统中,这些日志策略有:日志记本地,日记写远程文件,日志写数据库,同时可控制只记录某个时间段的日志。这样的项目如果控制台和日志系统都是Java开发的话,很适合使用JMX来控制,当然通过其他协议下发策略也是可以的,当初我们项目就是通过UDP承载syslog协议来做的
3 楼 sw1982 2010-03-27  
确实遇到过。。一直没认真研究。
4 楼 nickycheng 2010-03-28  
对不起,问下。 log4j在web容器下不是可以自己监控配置文件的更改并做对应的修改吗?
5 楼 spyker 2010-03-28  
calorie520 写道
曾开发过这样的系统,通过日志控制台,下发日志策略到日志系统中,这些日志策略有:日志记本地,日记写远程文件,日志写数据库,同时可控制只记录某个时间段的日志。这样的项目如果控制台和日志系统都是Java开发的话,很适合使用JMX来控制,当然通过其他协议下发策略也是可以的,当初我们项目就是通过UDP承载syslog协议来做的

去年 自己弄了一个用jmx控制日志  流量监测 系统启动和关闭的东西

日志采用的是sl4j
spring2.0
6 楼 spyker 2010-03-28  
logger的filter可以根据包来进行设置
还可以根据logger的内容进行 过滤 
这些是log的设置

jmx控制log可以通过spring很简单的完成
7 楼 spyker 2010-03-28  
log对外暴露jmx服务 如果我没有记错的话 应该只是一个设置的问题
你仔细看一看log4j的文档
它的配置文件支持jmx的