Logback中除MDC外添加自定义日志字段的替代方案咨询
实现方案:在Logback中添加独立的code日志字段(无需MDC)
针对你的需求,结合Java 15、Spring Boot 3和Logback,推荐通过自定义日志对象+Logback转换器+结构化日志编码器的方式实现,完全满足你每个日志条目携带独立code、不拼接进message的要求。
步骤1:定义自定义日志对象LogObject
先实现你调用示例中的LogObject,确保它包含code和message的getter方法,方便后续提取:
public class LogObject { private final String code; private final String message; public LogObject(String code, String message) { this.code = code; this.message = message; } public String getCode() { return code; } public String getMessage() { return message; } }
步骤2:自定义Logback转换器提取code
写一个Logback转换器,从日志事件的参数中提取LogObject的code值:
import ch.qos.logback.classic.pattern.ClassicConverter; import ch.qos.logback.classic.spi.ILoggingEvent; public class LogCodeConverter extends ClassicConverter { @Override public String convert(ILoggingEvent event) { // 检查日志参数是否包含LogObject if (event.getArgumentArray() != null && event.getArgumentArray().length > 0) { Object arg = event.getArgumentArray()[0]; if (arg instanceof LogObject) { return ((LogObject) arg).getCode(); } } // 无code时返回空字符串或默认值 return ""; } }
步骤3:自定义消息格式化器(提取message)
默认情况下,Logback会打印LogObject的toString,所以需要自定义格式化器,将LogObject的message作为日志的message输出:
import ch.qos.logback.classic.spi.ILoggingEvent; import net.logstash.logback.formatter.MessageFormatter; public class LogObjectMessageFormatter implements MessageFormatter { @Override public String format(ILoggingEvent event) { if (event.getArgumentArray() != null && event.getArgumentArray().length > 0) { Object arg = event.getArgumentArray()[0]; if (arg instanceof LogObject) { return ((LogObject) arg).getMessage(); } } // 非LogObject时使用原消息 return event.getMessage(); } }
步骤4:配置Logback(logback-spring.xml)
使用Spring Boot常用的LogstashEncoder生成结构化JSON日志,注册自定义转换器和格式化器,将code作为独立字段输出:
<configuration> <!-- 控制台输出Appender --> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="net.logstash.logback.encoder.LogstashEncoder"> <!-- 添加自定义code字段,使用转换器提取的值 --> <customFields>{"code": "%logCode"}</customFields> <providers> <!-- 输出@timestamp字段 --> <timestamp/> <!-- 输出@level字段 --> <level/> <!-- 使用自定义格式化器输出message --> <message> <messageFormatter class="com.yourpackage.LogObjectMessageFormatter"/> </message> <!-- 可添加其他需要的字段,比如线程名、类名等 --> <!-- <threadName/> --> <!-- <loggerName/> --> </providers> </encoder> <!-- 注册自定义转换器,指定conversionWord为logCode --> <conversionRule conversionWord="logCode" converterClass="com.yourpackage.LogCodeConverter"/> </appender> <!-- 根日志级别配置 --> <root level="info"> <appender-ref ref="CONSOLE"/> </root> </configuration>
验证效果
当你调用logger.info(new LogObject("APP.010", "Transaction Saved"));时,日志输出会符合你的预期:
{ "@timestamp": "2024-05-20T12:34:56.789+08:00", "@level": "INFO", "message": "Transaction Saved", "code": "APP.010" }
为什么不用MDC?
MDC是线程级的上下文存储,适合同一线程下所有日志共享同一个值的场景(比如请求ID)。而你的需求是每个日志条目携带独立的code,使用自定义日志对象+转换器的方式更直接,不会出现线程污染或值覆盖的问题。
内容的提问来源于stack exchange,提问作者dpcllalala
相关产品推荐
相关产品推荐

