让log4j支持占位符
目标:让log4j.xml配置文件中允许使用占位符(${key}).使用场景:
在运行期决定一些动态的配置内容.
比如在我们项目中,希望一台物理机同一个应用跑多个实例.
因为多进程操作同一份log文件存在并发问题(打印,DailyRolling等),所以我希望配置如下:${loggingRoot}/${instance}/project.log
在运行脚本中,通过加入-Dinstance=instance1参数,来动态指定实例名.让同一份应用在不同的运行实例下,日志打印到不同的路径
Log4j分析:
我以为,Log4j天生就支持占位符的.请见:org.apache.log4j.helpers.OptionConverter.substVars(String val, Properties props)就有对占位符的处理.
org.apache.log4j.PropertyConfigurator (log4j.properties文件解析).默认就支持对占位符的处理.
org.apache.log4j.xml.DOMConfigurator挺怪异的.明明也有对占位符的处理.但是我们就是无法对其属性props进行赋值.
(当然,有可能是我误解了其props的用法--还没有完整读过他的源码)
处理方案:
继承org.apache.log4j.xml.DOMConfigurator,实现自己的DOMConfigurator.
public
class
PlaceHolderDOMConfigurator
extends
org.apache.log4j.xml.DOMConfigurator {
private Properties props;
public PlaceHolderDOMConfigurator(Properties props){
this .props = props;
}
public static void configure(String filename, Properties props) {
new PlaceHolderDOMConfigurator(props).doConfigure(filename, LogManager.getLoggerRepository());
}
//主要是覆写这个方案.传入properties对象
protected String subst(String value) {
try {
return OptionConverter.substVars(value, props);
} catch (IllegalArgumentException e) {
LogLog.warn( " Could not perform variable substitution. " , e);
return value;
}
}
}
private Properties props;
public PlaceHolderDOMConfigurator(Properties props){
this .props = props;
}
public static void configure(String filename, Properties props) {
new PlaceHolderDOMConfigurator(props).doConfigure(filename, LogManager.getLoggerRepository());
}
//主要是覆写这个方案.传入properties对象
protected String subst(String value) {
try {
return OptionConverter.substVars(value, props);
} catch (IllegalArgumentException e) {
LogLog.warn( " Could not perform variable substitution. " , e);
return value;
}
}
}
测试代码:
log4j.xml片段:
<
appender name
=
"
PROJECT
"
class
=
"
org.apache.log4j.DailyRollingFileAppender
"
>
< param name = " file " value = " ${loggingRoot}/${instance}/project.log " />
< param name = " append " value = " false " />
< param name = " encoding " value = " GB2312 " />
< param name = " threshold " value = " info " />
< layout class = " org.apache.log4j.PatternLayout " >
< param name = " ConversionPattern " value = " %d [%X{requestURIWithQueryString}] %-5p %c{2} - %m%n " />
</ layout >
</ appender >
Run.java:
< param name = " file " value = " ${loggingRoot}/${instance}/project.log " />
< param name = " append " value = " false " />
< param name = " encoding " value = " GB2312 " />
< param name = " threshold " value = " info " />
< layout class = " org.apache.log4j.PatternLayout " >
< param name = " ConversionPattern " value = " %d [%X{requestURIWithQueryString}] %-5p %c{2} - %m%n " />
</ layout >
</ appender >
public
static
void
main(String[] args) {
Properties props = new Properties();
props.setProperty( " loggingRoot " , " d:/tmp " );
props.setProperty( " instance " , " instance1 " );
PlaceHolderDOMConfigurator.configure(LOG4J_PATH, props);
Logger rootLogger = LogManager.getRootLogger();
FileAppender fileAppender = (FileAppender) rootLogger.getAppender( " PROJECT " );
System.out.println(fileAppender.getFile());
}
Properties props = new Properties();
props.setProperty( " loggingRoot " , " d:/tmp " );
props.setProperty( " instance " , " instance1 " );
PlaceHolderDOMConfigurator.configure(LOG4J_PATH, props);
Logger rootLogger = LogManager.getRootLogger();
FileAppender fileAppender = (FileAppender) rootLogger.getAppender( " PROJECT " );
System.out.println(fileAppender.getFile());
}
输出结果:
d:/tmp/instance1/project.log
当然,你也可以通过在启动参数中加 -DloggingRoot=xxxx -Dinstance=yyyy动态指定内容.
特别说明:
本文:log4j版本为1.2.14
log4j 1.2.15测试不通过,原因见: https://issues.apache.org/bugzilla/show_bug.cgi?id=43325