使用 SLF4J MDC 给日志添加上下文信息
SLF4J MDC(Mapped Diagnostic Context)可以帮助在日志中添加上下文信息,从而更好地跟踪和调试应用程序。MDC 允许你将特定于线程的键值对存储在日志上下文中,便于在日志中输出相关信息。
使用步骤
-
添加依赖:确保你的项目中已经包含了 SLF4J 和相关的日志实现(如 Logback 或 Log4j)。
-
设置 MDC:在代码中使用
MDC.put(key, value)
设置上下文信息。 -
记录日志:在日志消息中使用
%X
输出 MDC 中的内容。 -
清理 MDC:在处理完成后,使用
MDC.clear()
清理上下文,以避免内存泄漏。
示例代码
以下是一个简单的示例,展示如何使用 SLF4J MDC:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; public class MdcExample { private static final Logger logger = LoggerFactory.getLogger(MdcExample.class); public static void main(String[] args) { // 设置 MDC 值 MDC.put("userId", "12345"); MDC.put("transactionId", "abc-xyz"); logger.info("This is a log message with MDC context"); // 清理 MDC MDC.clear(); } }
日志输出
假设你使用 Logback,日志配置文件中可以定义输出格式如下:
<configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg %X%n</pattern> </encoder> </appender> <root level="info"> <appender-ref ref="STDOUT"/> </root> </configuration>
输出结果
运行上述代码后,日志输出可能类似于:
2024-10-27 02:57:00 [main] INFO MdcExample - This is a log message with MDC context {userId=12345, transactionId=abc-xyz}
总结
通过使用 SLF4J MDC,你可以在日志中轻松地添加上下文信息,有助于在多线程环境中追踪问题。记得在使用完 MDC 后清理它,以保持良好的内存管理。
另,MDC 一般配合 AOP / Filter / Interceptor 使用:
@Around(value = "execution(* com.xx.xx.facade.impl.*.*(..))", argNames="pjp") public Object validator(ProceedingJoinPoint pjp) throws Throwable { try { MDC.put("userId", "12345"); MDC.put("transactionId", "abc-xyz"); return pjp.proceed(args); } catch(Throwable e) { // 处理错误 } finally { MDC.clear(); } }
源码
此处以 Logback 中的实现为例,只分析 MDC 的 put() 方法:
public class MDC { public static void put(String key, String val) throws IllegalArgumentException { if (key == null) { throw new IllegalArgumentException("key parameter cannot be null"); } if (mdcAdapter == null) { throw new IllegalStateException("MDCAdapter cannot be null. See also " + NULL_MDCA_URL); } mdcAdapter.put(key, val); } }
MDC 的 put() 方法利用 MDCAdapter 实现。
下面看一下 Logback 中 MDCAdapter 的实现 LogbackMDCAdapter:
public final class LogbackMDCAdapter implements MDCAdapter { final InheritableThreadLocal<Map<String, String>> copyOnInheritThreadLocal = new InheritableThreadLocal(); public void put(String key, String val) throws IllegalArgumentException { if (key == null) { throw new IllegalArgumentException("key cannot be null"); } else { Map<String, String> oldMap = (Map)this.copyOnInheritThreadLocal.get(); Integer lastOp = this.getAndSetLastOperation(1); if (!this.wasLastOpReadOrNull(lastOp) && oldMap != null) { oldMap.put(key, val); } else { Map<String, String> newMap = this.duplicateAndInsertNewMap(oldMap); newMap.put(key, val); } } } }
- 如上,LogbackMDCAdapter 有泛型为
Map<String, String>
的 InheritableThreadLocal,Map<String, String>
被用来存储和当前线程相关的上下文信息,MDC 的 put() 方法会将值放在和当前线程关联的 Map 中。 - MDC 内的键值对要能在调用链路中都能打印,那么 Map 肯定是存储在 ThreadLocal 中传递,从代码可以看到为 InheritableThreadLocal。
Map<String, String>
存储在 InheritableThreadLocal 中,AOP 内真正的业务方法内部若进行了子线程的创建,MDC 内的键值对也能正常打印到日志中。但内部若是使用线程池的方式执行细分业务,则线程池任务内打印的日志则不会有此内容(线程池的 ThreadLocal 传递可以用阿里的 TransmittableThreadLocal)。
参考:ChatGPT、slf4j MDC 是个好东西
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
2022-10-27 SQL 分号
2022-10-27 MySQL连接(登录)
2022-10-27 无法识别的USB设备,跟这台计算机连接的前一个USB设备工作不正常,Windows无法识别它