魔改log4j2的JsonLayout,支持自定义json格式日志
小伙伴们,你们好,我是老寇,我又回来辣,1个多月不见甚是想念啊!!!跟我一起魔改源码吧
1.自定义json格式【PatternLayout】
大部分教程都是这个,因此,我就简单给个配置,没啥好说,然后,我们一起看看源码吧
配置
<PatternLayout>
<!--
注意:真实的生产环境,日志打印的内容是五花八门,日志内容会出现一些莫名其他的特殊符号,导致json无法反序列化
因此,可以利用Pattern Layout 提供的标签enc,enc支持4种转义,HTML/XML/JSON/CRLF,默认进行HTML转义
目前,只对JSON处理,即%enc{%m}{JSON} => {"msg":"%enc{%m}{JSON}"}
-->
<pattern>
{"serviceId":"${SERVICE_ID}", "profile":"test", "dateTime":"%d{yyyy-MM-dd HH:mm:ss.SSS}", "traceId":"%X{traceId}", "spanId":"%X{spanId}", "address":"${sys:ip}:${sys:server.port}]", "level":"%-5level", "thread":"%thread", "logger":"%logger", "msg":"%enc{%m}{JSON}"}%n
</pattern>
</PatternLayout>
{"serviceId":"laokou-sample-websocket","profile":"test","address":"127.0.0.1:9032","dateTime":"2024-11-17 06:37:56.501","packageName":"com.ulisesbocchio.jasyptspringboot.caching.RefreshScopeRefreshedEventListener","threadName":"main","level":"INFO","message":"Refreshing cached encryptable property sources on ServletWebServerInitializedEvent"}
源码
2.自定义json格式【JsonLayout】
这个需要魔改JsonLayout源码,因为JsonLayout无法继承,我只能全路径覆盖源码,我们修改一下源码吧
注解【 @Plugin 】
@Plugin 是 Log4j 提供的一个注解,用于注册和配置自定义的插件。Log4j 插件可以是各种类型,例如 Appender(输出目的地)、Layout(格式化日志的方式)等
| 参数名称 | 参数描述 | 参数值【举例】 |
| name | 插件名称 | JsonLayout |
| category | 插件类别,有以下几种常见插件类型,例如:Core => 核心插件Layout => 日志布局Node.CATEGORY => Log4j内部变量,通常用于解析配置 | Node.CATEGORY |
| elementType | 定义插件的 XML 配置中对应的元素类型 | elementType = Layout.ELEMENT_TYPE【表示布局插件】 |
| printObject | 是否将日志事件以格式化输出 | true |
魔改【全路径覆盖】
@Plugin(name = "JsonLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true)
public final class JsonLayout extends AbstractJacksonLayout {
// 省略大部分代码
// ...
@Override
public void toSerializable(final LogEvent event, final Writer writer) throws IOException {
if (complete && eventCount > 0) {
writer.append(", ");
}
// 判断是否定义<KeyValuePair>
if (additionalFields.length > 0) {
objectWriter.writeValue(writer, getFieldsMap(event));
writer.write(eol);
if (includeNullDelimiter) {
writer.write('\0');
}
markEvent();
}
else {
super.toSerializable(event, writer);
}
}
// @formatter:off
private Map<String, String> getFieldsMap(LogEvent event) {
LogEvent evt = convertMutableToLog4jEvent(event);
Map<String, String> additionalFieldsMap = resolveAdditionalFields(evt);
additionalFieldsMap.putAll(evt.getContextData().toMap());
additionalFieldsMap.putAll(Map.of(
"address", System.getProperty("address", ""),
"dateTime", FORMATTER.format(getLocalDateTimeOfTimestamp(evt.getTimeMillis())),
"level", evt.getLevel().name(),
"threadName", evt.getThreadName(),
"packageName", evt.getLoggerName(),
"message", evt.getMessage().getFormattedMessage(),
"stacktrace", getStackTraceAsString(evt.getThrown()))
);
return additionalFieldsMap;
}
// @formatter:on
private String getStackTraceAsString(Throwable throwable) {
if (ObjectUtils.isEmpty(throwable)) {
return "";
}
StringWriter stringWriter = new StringWriter();
try (PrintWriter printWriter = new PrintWriter(stringWriter)) {
throwable.printStackTrace(printWriter);
}
return stringWriter.toString();
}
}
配置
<JsonLayout complete="false" compact="true" eventEol="true" properties="true" locationInfo="true" includeStacktrace="true" stacktraceAsString="false" objectMessageAsJsonObject="false">
<KeyValuePair key="serviceId" value="${SERVICE_ID}"/>
<KeyValuePair key="profile" value="${PROFILE}" />
</JsonLayout>
{"serviceId":"laokou-sample-websocket","profile":"test","threadName":"main","level":"INFO","message":"Skipping PropertySource servletConfigInitParams [class org.springframework.core.env.PropertySource$StubPropertySource","address":"198.18.0.1:9032","dateTime":"2024-11-17 09:37:28.183","packageName":"com.ulisesbocchio.jasyptspringboot.EncryptablePropertySourceConverter"}
标签【JsonLayout 】
| 参数名称 | 参数描述 | 默认值 |
| complete | 是否输出完整的 JSON 对象,true作为单独的json文件,false逐行处理 | false |
| compact | 是否紧凑输出json,true单行输出,节省空间,false多行输出方便阅读和调试 | true |
| eventEol | 如果为 true,每条日志记录后添加换行符,方便逐条处理(推荐开启)。 |
false |
| properties | 如果为 true,输出上下文的键值对(MDC 和 NDC 数据) |
false |
| includeStacktrace | 如果为 false,不包含堆栈跟踪信息(适用于异常日志) |
true |
| locationInfo | 如果为 true,输出日志发生的位置(类名、方法名、行号等信息),可能会影响性能 |
false |
| charset | 设置输出文件的字符编码 | UTF-8 |
| objectMessageAsJsonObject | 如果为 true,对象消息将直接作为 JSON 对象输出,而不是作为字符串 |
false |
| stacktraceAsString | 是否将堆栈跟踪信息作为字符串处理 | false |
我是老寇,我们下次再见

浙公网安备 33010602011771号