Log4j2 PatternLayout 源码分析

Log4j2 简单配置

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<Console name="STDOUT" target="SYSTEM_OUT">
<PatternLayout pattern="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="warn">
<AppenderRef ref="STDOUT"/>
</Root>
</Loggers>
</Configuration>

Log4j2 中类的结构和配置文件的结构是类似的, Appender 中设置 PatternLayout, PatternLayout 中 设置pattern, 其中 pattern 会被解析成不同的类来进行 message formate。

PatternLayout 代码解析

Log4j2 Version 2.11.1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
org.apache.logging.log4j.core.layout.PatternLayout


/**
* org.apache.logging.log4j.core.layout.AbstractLayout 类的接口实现
* Appender 调用此方法进行 日志 message 的格式化
*/
@Override
public void encode(final LogEvent event, final ByteBufferDestination destination) {
if (!(eventSerializer instanceof Serializer2)) {
super.encode(event, destination);
return;
}
// 格式化消息,加入pattern 中的内容,如线程ID
final StringBuilder text = toText((Serializer2) eventSerializer, event, getStringBuilder());
final Encoder<StringBuilder> encoder = getStringBuilderEncoder();
// 把Message 写入到 ByteBufferDestination 中, ByteBufferDestination 会输出到对象的日志输出流中,如控制台或者磁盘。
encoder.encode(text, destination);
trimToMaxSize(text);
}

/**
* 负责Message的格式化,调用的是Serializer2 接口
*/
private StringBuilder toText(final Serializer2 serializer, final LogEvent event,
final StringBuilder destination) {
return serializer.toSerializable(event, destination);
}

PatternLayout Serializer 分析

PatternLayout 中有两种Serializer 实现,PatternSerializer 和 PatternSelectorSerializer。

  • PatternSerializer 是常用的Message 格式化方式,就是静态的消息格式化,所有的Message 都使用一种 pattern ,上述 XML 配置就是使用的PatternSerializer。
  • PatternSelectorSerializer 是动态的消息格式化方式,不同种类的消息可以使用不同的pattern 。 参考《在log4j2中灵活切换输出日志的格式

以下分析为 PatternSerializer 。PatternSerializer 和 PatternSelectorSerializer 在格式化消息的方式上是一致的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 private static class PatternSerializer implements Serializer, Serializer2 {
/**
一个 PatternSerializer 中包含多个 PatternFormatter,PatternFormatter 就是负责解析 pattern 中的内容,
%d %-5p [%t] %C{2} (%F:%L) - %m%n , %d 会对应一个PatternFormatter,其他的也是类似,每一段对应一个 PatternFormatter。%d PatternFormatter 负责在Message 中加入时间。
*/
private final PatternFormatter[] formatters;



@Override
public StringBuilder toSerializable(final LogEvent event, final StringBuilder buffer) {
final int len = formatters.length;
for (int i = 0; i < len; i++) {
/**
遍历所有的PatternFormatter,格式化消息
*/
formatters[i].format(event, buffer);
}
if (replace != null) { // creates temporary objects
String str = buffer.toString();
str = replace.format(str);
buffer.setLength(0);
buffer.append(str);
}
return buffer;
}

PatternFormatter 分析

PatternFormatter 中的 LogEventPatternConverter 具体负责消息的格式化,LogEventPatternConverter 是一个抽象类,每一种配置对应自己的实现类。比如 %d 对应的就是 org.apache.logging.log4j.core.pattern.DatePatternConverter,负责加入时间信息。

1
2
3
4
5
6
7
8
9
10
public class PatternFormatter {
private final LogEventPatternConverter converter;
//进行格式化
public void format(final LogEvent event, final StringBuilder buf) {
if (skipFormattingInfo) {
converter.format(event, buf);
} else {
formatWithInfo(event, buf);
}
}

小结

Log4j2 API的设计更加的清晰,配置和使用的API进行了分离。配置使用Builder模式,使用起来也非常的便捷,但是Log4j2的配置API,基本上没有更新配置的API,只能使用Builder重新创建,比如想动态切换 Layout中的 pattern 就比较繁琐 参考《Log4j2 动态创建Logger和修改logger实现业务追踪》。

在开发 Apptalking agent 日志追溯功能的时候就需要动态的切换pattern,处理起来比较Logback和log4j上难度大一些,不知道Log4j2的API以后会不会有相应的调整。