使用 SLF4J MDC 给日志添加上下文信息

SLF4J MDC(Mapped Diagnostic Context)可以帮助在日志中添加上下文信息,从而更好地跟踪和调试应用程序。MDC 允许你将特定于线程的键值对存储在日志上下文中,便于在日志中输出相关信息。

使用步骤

  1. 添加依赖:确保你的项目中已经包含了 SLF4J 和相关的日志实现(如 Logback 或 Log4j)。

  2. 设置 MDC:在代码中使用MDC.put(key, value)设置上下文信息。

  3. 记录日志:在日志消息中使用%X输出 MDC 中的内容。

  4. 清理 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 是个好东西

posted @   Higurashi-kagome  阅读(165)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
历史上的今天:
2022-10-27 SQL 分号
2022-10-27 MySQL连接(登录)
2022-10-27 无法识别的USB设备,跟这台计算机连接的前一个USB设备工作不正常,Windows无法识别它
点击右上角即可分享
微信分享提示