log4j2动态指定log输出路径superdiamond+log4j2+spring

使用log4j2.xml进行logger输出配置时,在property标签中可以定义一些变量来配置log文件的基础路径。但是工程环境是相当复杂的,如开发环境、测试环境、灰度环境、以及正式环境,由于种种原因每个环境下的log home文件地址都有差别,这就非常难为负责发包的同事了,如果工程少还好,但是对于分布式微服务等系统,每次修改配置文件打包简直就是噩梦了。
至此,有些童鞋就有疑问了,上述问题解决方案完全可以通过maven war plugin来解决啊,编译时动态指定package.environment来使用不同的log4j2.xml配置文件。是的,这种方案是可以解决不同环境不同配置文件的问题,但是环境是相当复杂的,比如我们线上服务器log文件要输出至共享存储挂载点,顾名思义多台服务器同一服务输出的日志文件在同一个存储区内。我们上面通过maven war plugin打包得到的log4j2.xml配置文件在部署至每台服务器时,其日志输出路径是一致的。这样多台服务器输出日志至同一文件是会有问题的,所以maven war plugin并不能解决我们的终极问题。

上面我们说过log4j2.xml是可以配置property标签的,如果这些变量的值本身就是动态的,不就能解决问题了吗。非常感谢log4j2.xml支持jvm、系统环境变量的使用。改造的基本思路如下图:

log4j2动态指定log输出路径superdiamond+log4j2+spring_第1张图片

superdiamond配置中心,负责配置每个服务的loghome路径配置,其中包括一部分动态参数区,比如:/data/{ip}/pear/v1/cache-server-default
服务自身需在spring容器加载完成后reload log4j2配置,这主要是因为superdiamond依赖于spring容器,spring placeholder 需要将diamond配置作为properties属性。
1. 实现ApplicationListener接口onApplicationEvent方法,并在spring容器加载时加载该bean;
2. 获取diamond中loghome的配置路径,并解析其中的{ip}参数,将最终生成的日志路径reallogpath设置进jvm变量中;
3. 重载log4j2.xml配置。

CustomerLog4j2Initializer 实现ApplicationListener接口代码如下:
@Override
public void onApplicationEvent(ApplicationEvent event) {
		LOGGER.info("CustomerLog4j2Initializer onApplicationEvent");
		// 容器启动完成之后load  
            if (event instanceof ContextRefreshedEvent) {  
            if (((ContextRefreshedEvent) event).getApplicationContext().getParent() == null) {
            	LOGGER.info("CustomerLog4j2Initializer , reloading log4j2");
            	
                try {
					reloadLog4j2();
				} catch (FileNotFoundException e) {
					e.printStackTrace();
				}
                
                LOGGER.info("CustomerLog4j2Initializer , reloaded log4j2 successful");
            }  
        }
}
使用diamond获取配置,并动态解析路径并存储jvm变量,完成log42配置重载代码如下:
private PropertiesConfiguration prop = PropertiesConfigurationFactoryBean.getPropertiesConfiguration();
	
	/**
	 * 重新加载log4j2配置文件
	 * @throws FileNotFoundException
	 * @return void
	 */
	private void reloadLog4j2() throws FileNotFoundException{
		LOGGER.info("reloadLog4j2 , load log4j2 properties");
		
		String realLogPath = "";	//最终日志存放的真实路径
		
		//获取diamond配置中心中配置的日志路径
		realLogPath = prop.getString("logpath",DEFAULT_LOGPATH);
		
		//解析日志存放路径中的变量,替换ip
		realLogPath = realLogPath.replace("{ip}", packageIp());
		
		//获取log4j配置文件路径,如无配置则默认加载classpath:log4j2.xml
		String location = prop.getString("log4j2Filepath",DEFAULT_LOG4J2_FILEPATH);
		
		LOGGER.info("reloadLog4j2 , location:"+location+",realLogPath:"+realLogPath);
		
		//将日志存放路径设置至jvm变量中,供log4j配置文件中加载
		System.setProperty("reallogpath", realLogPath);
		
		//重新加载log4j2配置文件
		String resolvedLocation = SystemPropertyUtils.resolvePlaceholders(location);
		if (resolvedLocation.toLowerCase().endsWith(XML_FILE_EXTENSION)) {
			LOGGER.info("reloadLog4j2 , LoggerContext.reconfigure--start");
			
		    File file = ResourceUtils.getFile(resolvedLocation);
			LoggerContext context =(LoggerContext)LogManager.getContext(false);  
			context.setConfigLocation(file.toURI());
			LOGGER.info("reloadLog4j2 , LoggerContext.setConfigLocation:"+file.toURI());
			//重新初始化Log4j2的配置上下文  
			context.reconfigure();  
			
			LOGGER.info("reloadLog4j2 , LoggerContext.reconfigure--finished");
		}
		else {
			//暂不支持非xml配置
		}
		
	}
log4j2.xml配置文件配置如下:
	
		
		${sys:reallogpath}
		
		${LOG_HOME}/static
		${LOG_HOME}/ERROR
		${LOG_HOME}/INFO
		${LOG_HOME}/monitoring
	
问题:
1. log4j2.xml默认在classpath下会默认加载配置,此时使用了${sys:reallogpath}变量会导致log4j2装载错误,出现异常信息。
解决方法:将log4j2.xml文件放至其它目录,如classpath:log/log4j2.xml,在重载log4j2配置时设置configLocation至此文件即可。
2. 此实例仅适用于log4j2,log4j装载过程有区别。
3. log4j2.xml配置文件中使用${sys:reallogpath}变量,也可以通过jvm启动参数来指定变量的值,此方式改造更简单,但缺点显而易见。
4. 如何实现diamond配置修改loghome配置后动态重载log4j2.
解决思路:实现diamond接口ConfigurationListener的configurationChanged方法,在configurationChanged方法中监听loghome配置项的修改,发生修改则重载log4j2。


你可能感兴趣的:(log4j2动态指定log输出路径superdiamond+log4j2+spring)