Log4j2 动态创建Logger和修改logger实现业务追踪

实现思路

Log4j2 Logger的配置的Api和使用的Api做了分离,使用的新的API可以很容易创建Logger,同样使用API可以修改Logger的Appender Pattern 。Log4j2 中Thread Context 和 Log4j 中的 NDC 类似,结合以上的技术可以实现。

示例代码

API创建Logger

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static Logger createLogger(String loggerName , String appenderName) {
final LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
final Configuration config = ctx.getConfiguration();
Layout layout = PatternLayout.newBuilder().withConfiguration(config).withPattern(" %d [%t] %p %c - %m%n").build();

FileAppender appender = FileAppender.newBuilder().withName(appenderName).withFileName("c://test.log").withLayout(layout).build();
appender.start();
config.addAppender(appender);
AppenderRef ref = AppenderRef.createAppenderRef("File", null, null);
AppenderRef[] refs = new AppenderRef[] {ref};
LoggerConfig loggerConfig = LoggerConfig.createLogger(false, Level.ALL, loggerName, "true", refs, (Property[])null, config, (Filter)null);
loggerConfig.addAppender(appender, null, null);
config.addLogger(loggerName, loggerConfig);
ctx.updateLoggers();
return LogManager.getLogger(loggerName);
}

API修改Logger的Appender

修改Pattern 添加获取 ThreadContext的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void changePattern(String appenderName , String pattern){
final LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
final Configuration config = ctx.getConfiguration();
FileAppender appender = config.getAppender(appenderName);
PatternLayout layout = (PatternLayout)appender.getLayout();
PatternLayout nlayout = PatternLayout.newBuilder().withConfiguration(layout.getConfiguration()).withPattern(pattern).build();
try {
Field field = AbstractAppender.class.getDeclaredField("layout");
field.setAccessible(true);
field.set(appender, nlayout);
} catch (Exception e) {
e.printStackTrace();
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) {
ThreadContext.put("requestId", "ID-1");
Logger logger = createLogger("com.test" , "MyAppender");
for(int i = 0 ; i < 100 ; i++ ){
logger.info("test"+i);
}
/**
* %X{requestId} 从 ThreadContext 中获取ID
*/
changePattern("MyAppender" , "%X{requestId} %d [%t] %p %c - %m%n");
for(int i = 0 ; i < 100 ; i++ ){
logger.info("test"+i);
}
}

日志输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 2018-10-10 15:26:55,069 [main] INFO com.test - test92
2018-10-10 15:26:55,069 [main] INFO com.test - test93
2018-10-10 15:26:55,069 [main] INFO com.test - test94
2018-10-10 15:26:55,069 [main] INFO com.test - test95
2018-10-10 15:26:55,069 [main] INFO com.test - test96
2018-10-10 15:26:55,069 [main] INFO com.test - test97
2018-10-10 15:26:55,069 [main] INFO com.test - test98
2018-10-10 15:26:55,069 [main] INFO com.test - test99
ID-1 2018-10-10 15:26:55,070 [main] INFO com.test - test0
ID-1 2018-10-10 15:26:55,071 [main] INFO com.test - test1
ID-1 2018-10-10 15:26:55,071 [main] INFO com.test - test2
ID-1 2018-10-10 15:26:55,071 [main] INFO com.test - test3
ID-1 2018-10-10 15:26:55,071 [main] INFO com.test - test4
ID-1 2018-10-10 15:26:55,071 [main] INFO com.test - test5
ID-1 2018-10-10 15:26:55,071 [main] INFO com.test - test6
ID-1 2018-10-10 15:26:55,071 [main] INFO com.test - test7
ID-1 2018-10-10 15:26:55,071 [main] INFO com.test - test8
ID-1 2018-10-10 15:26:55,071 [main] INFO com.test - test9

待改进之处

以上的方法是新创建了一个PatternLayout,如果可以修改原有的PatternLayout而不创建新的PatternLayout方案会更好一些。

1
2
3
4
5
6
7
8
public void setConversionPattern(final String conversionPattern) {
final String pattern = OptionConverter.convertSpecialChars(conversionPattern);
if (pattern == null) {
return;
}
final PatternParser parser = createPatternParser(this.config);
formatters = parser.parse(pattern, this.handleExceptions);
}

参考:
http://logging.apache.org/log4j/log4j-2.0-beta7/log4j-core/apidocs/src-html/org/apache/logging/log4j/core/layout/PatternLayout.html#line.125

参考:
https://logging.apache.org/log4j/2.x/manual/thread-context.html