使用log4j2 JsonLayout自定义JSON格式日志
需求
log4j2 在性能上提升了很多,可以减小日志输出对高并发程序的性能。 程序的日志需要输出为json格式,通过ELK 收集和存储日志,使用json格式便于Elasticsearch提取JSON字段信息,进行搜索或者通过Kinana展示
log4j2 JsonLayout使用
log4j2 输出json log 使用示例
maven pom 依赖:
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<properties>
<java.version>1.8</java.version>
<java.compile.version>1.8</java.compile.version>
<log4j-api.version>2.13.1</log4j-api.version>
<jackson.version>2.10.0</jackson.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4j-api.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>${log4j-api.version}</version>
</dependency>
<!-- json format -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
Java Code 示例:
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
28import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class LogJsonTest {
private static Logger log = LogManager.getLogger(LogJsonTest.class);
public static void main(String[] args) {
ThreadContext.put("hello","world");
for (int i = 0; i < 2; i++) {
log.info("current index:{},date:{}",i, LocalDateTime.now().toString());
}
try{
throw new RuntimeException("hello i am fool");
}catch (Exception e){ log.error("error cause:",e); }
// test json object demo
log.info("demo str:{}",new Demo());
}
static class Demo{
private List<String> arr = new ArrayList<>();
private String he="";
public String toString() {
return Arrays.toString(arr.toArray()) ;
}
}
}log4j
log4j2.xml
配置示例: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
<Configuration status="OFF">
<properties>
<property name="logPath">logs</property>
<property name="logLevel">info</property>
<property name ="project" >spring-cloud-app-demo</property>
</properties>
<Appenders>
<Console name="console" target="SYSTEM_OUT">
<!-- complete="true" Complete well-formed JSON -->
<JsonLayout complete="false" compact="true" eventEol="true" properties="true"
locationInfo="true" includeStacktrace="true"
stacktraceAsString="true"
objectMessageAsJsonObject="true">
<KeyValuePair key="project" value="${project}"/>
</JsonLayout>
</Console>
</Appenders>
<Loggers>
<Logger name="com.github.ordiy" level="debug" additivity="false">
<AppenderRef ref="console"/>
</Logger>
<Root level="debug">
<AppenderRef ref="console"/>
</Root>
</Loggers>
</Configuration>json 日志输出格式
1 | {"thread":"main","level":"ERROR","loggerName":"com.github.ordiy.LogJsonTest","message":"error cause:","thrown":{"commonElementCount":0,"localizedMessage":"hello i am fool","message":"hello i am fool","name":"java.lang.RuntimeException","extendedStackTrace":"java.lang.RuntimeException: hello i am fool\n\tat com.github.ordiy.LogJsonTest.main(LogJsonTest.java:24) [classes/:?]\n"},"endOfBatch":false,"loggerFqcn":"org.apache.logging.log4j.spi.AbstractLogger","instant":{"epochSecond":1594867434,"nanoOfSecond":597759000},"contextMap":{"hello":"world"},"threadId":1,"threadPriority":5,"source":{"class":"com.github.ordiy.LogJsonTest","method":"main","file":"LogJsonTest.java","line":26,"classLoaderName":"app"},"project":"spring-cloud-app-demo"} |
json layout 配置说明
JsonLayout可以使用compact
locationInfo
等配置输出格式和内容,配置项解说参照log4j2 json layout
常用的配置项目:
1 | <JsonLayout complete="false" compact="true" eventEol="true" properties="true" |
json 输出格式及备注:
使用log4j 输出自定义的日志格式
log4j2.xml 文件内容:
1 |
|
Java 代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16private static Logger log = LogManager.getLogger(LogJsonTest.class);
public static void main(String[] args) {
//log4j ctx
ThreadContext.put("X-B3-ParentSpanId","world");
ThreadContext.put("X-B3-SpanId","world-span");
ThreadContext.put("X-B3-uId","world-user");
ThreadContext.put("X-B3-uId","world-user");
ThreadContext.put("X-B3-TraceId","world-trace");
for (int i = 0; i < 100; i++) {
log.info("current index:{},date:{}",i, LocalDateTime.now().toString());
}
try{
throw new RuntimeException("hello i am fool");
}catch (Exception e){ log.error("error cause:",e); }
}Json日志输出文件示例:
1
{"thread":"main","level":"INFO","loggerName":"com.github.ordiy.LogJsonTest","message":"demo str:[]","endOfBatch":false,"loggerFqcn":"org.apache.logging.log4j.spi.AbstractLogger","instant":{"epochSecond":1594888243,"nanoOfSecond":231053000},"threadId":1,"threadPriority":5,"project":"spring-cloud-app-demo","timestamp":"2020-07-16T16:30:42.913+0800","parent":"world","span":"world-span","user":"world-user","trace":"world-trace","index":"spring-cloud-app-demo-2020-07-16","line_number":""}
进行pretty 优化展示后的格式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22{
"thread":"main",
"level":"INFO",
"loggerName":"com.github.ordiy.LogJsonTest",
"message":"demo str:[]",
"endOfBatch":false,
"loggerFqcn":"org.apache.logging.log4j.spi.AbstractLogger",
"instant":{
"epochSecond":1594888243,
"nanoOfSecond":231053000
},
"threadId":1,
"threadPriority":5,
"project":"spring-cloud-app-demo",
"timestamp":"2020-07-16T16:30:42.913+0800",
"parent":"world",
"span":"world-span",
"user":"world-user",
"trace":"world-trace",
"index":"spring-cloud-app-demo-2020-07-16",
"line_number":""
}
Thread Context
thread context
通过Thread Context Stack保存context信息。
1 | ThreadContext.put("id", UUID.randomUUID().toString()); // Add the fishtag; |
CloseableThreadContext
CloseableThreadContext 用于代码块内部ThreadContext。
1 | //在IO 操作使用ThreadContext标记特定的操作信息 |
跨Thread的ThreadContext
Thread之间切换会导致ThreadContext信息丢失。需要进行手动处理:
1 | public class StreamGobbler implements Runnable{ |
需要注意的问题
log4j2 jsonLayout
处理MDC
的兼容问题spring cloud sleuth
默认的 Trace日志默认使用的是slf4j
在MDC
中保存的traceID
等信息,log4j2 jsonLayout
处理MDC
,会存在兼容性问题。(貌似可以通过重新实现JsonLayout
解决该问题,可以参照log4j2-logstash-layout的实现)代码地址 https://github.com/ordiy/demo-project/tree/master/test-log4j2-json-layout