在 java 日志记录中,mdc(mapped diagnostic context)通过 threadlocal 为每个线程维护一个键值对上下文,实现日志中的动态字段注入。具体做法是在业务逻辑入口处调用 mdc.put() 设置如 traceid 等标识,在出口处调用 mdc.clear() 清理数据,并在日志配置文件中通过 %x{key} 模式引用这些值。

如何在线程池中正确传递 MDC 上下文?
默认情况下,MDC 数据仅存在于当前线程的 ThreadLocal 中,直接提交任务到线程池会导致子线程无法获取父线程的 MDC 数据。解决方法是使用 MDC.getCopyOfContextMap() 捕获上下文,并在子线程执行前后分别调用 MDC.setContextMap() 和 MDC.clear() 进行传递和还原。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MdcThreadPoolDemo {
private static final Logger logger = LoggerFactory.getLogger(MdcThreadPoolDemo.class);
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(1);
// 主线程设置 MDC
MDC.put("userId", "user-123");
logger.info("Main thread starts");
try {
// 捕获当前线程的 MDC 上下文
final Map<string string> contextMap = MDC.getCopyOfContextMap();
executor.submit(() -> {
// 子线程恢复 MDC 上下文
if (contextMap != null) {
MDC.setContextMap(contextMap);
}
try {
logger.info("Sub thread executes, userId: {}", MDC.get("userId"));
} finally {
// 清理子线程的 MDC,防止复用污染
MDC.clear();
}
});
} finally {
// 主线程清理
MDC.clear();
executor.shutdown();
}
}
}
</string>
运行上述代码,控制台输出将显示子线程日志中也包含了 userId=user-123。若不使用 getCopyOfContextMap 和 setContextMap,子线程日志中的 %X{userId} 将为空。必须确保在子线程结束前调用 MDC.clear(),否则在线程池复用线程时,旧的 MDC 数据会残留并影响后续任务。
如何处理 MDC 上下文嵌套或覆盖的情况?
当多个组件或过滤器在同一线程中操作 MDC 时,后设置的值会覆盖先前的值。例如,AOP 切面设置了 requestId,而 Service 层又设置了 operationId,最终日志中只会保留最后设置的那个值,除非同时 put 了不同的 key。若需要保留多层级信息,应使用不同的 key 名称,如 parentRequestId 和 childRequestId,或者将复杂对象序列化为 JSON 字符串存入单个 key 中。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
public class MdcNestedDemo {
private static final Logger logger = LoggerFactory.getLogger(MdcNestedDemo.class);
public static void main(String[] args) {
// 模拟外层上下文
MDC.put("outerKey", "outerValue");
logger.info("Outer layer");
try {
// 模拟内层上下文,添加新 key
MDC.put("innerKey", "innerValue");
logger.info("Inner layer");
// 模拟内层结束,移除内层 key
MDC.remove("innerKey");
logger.info("Back to outer layer");
} finally {
// 清理所有 MDC
MDC.clear();
}
}
}
该程序演示了通过 MDC.remove() 精确清理特定键值对的能力。输出日志中,第一行只有 outerKey,第二行同时包含 outerKey 和 innerKey,第三行再次只包含 outerKey。这种机制允许在局部作用域内临时扩展上下文,而不影响全局或其他并行任务。避免使用相同的 key 存储不同层级的数据,除非明确意图是覆盖。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











