魔改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"}

源码

是来源于 PatternLayout,用于格式化日志输出。因此,我们分析这个类即可

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

已上传Github
GitHub - KouShenhai/KCloud-Platform-IoT: KCloud-Platform-IoT(阻塞式)(老寇IoT云平台)是一个企业级微服务架构的IoT云平台。采用DDD(领域驱动设计)思想,基于Spring Boot 3.4.0、Spring Cloud 2024.0.0、Spring Cloud Alibaba 2023.0.1.3 最新版本开发的云服务多租户IoT平台,家人们,点个star!拜托啦~

我是老寇,我们下次再见

posted @ 2024-12-13 20:56  k↑  阅读(351)  评论(0)    收藏  举报