SLF4J日志框架提供了一个MDC(Mapped Diagnostic Contexts)工具类,谷歌翻译为映射的诊断上下文 ,入口调用MDC.put()
方法传入请求ID,在出口调用MDC.remove()
方法移除请求ID.
MDC在异步线程中不生效,原因是底层采用ThreadLocal 作为数据结构,我们调用MDC.put()
方法传入的请求ID只在当前线程有效.
解决方案如下:
可以使用装饰器模式 ,新写一个MDCRunnable类
对Runnable接口
进行一层装饰。在创建MDCRunnable类
时保存当前线程的MDC值,在执行run()
方法时再将保存的MDC值拷贝到异步线程中去。
public class MDCRunnable implements Runnable { private final Runnable runnable; private final Map<String, String> map; public MDCRunnable(Runnable runnable) { this.runnable = runnable; // 保存当前线程的MDC值 this.map = MDC.getCopyOfContextMap(); } @Override public void run() { // 传入已保存的MDC值 for (Map.Entry<String, String> entry : map.entrySet()) { MDC.put(entry.getKey(), entry.getValue()); } // 装饰器模式,执行run方法 runnable.run(); // 移除已保存的MDC值 for (Map.Entry<String, String> entry : map.entrySet()) { MDC.remove(entry.getKey()); } } }
public class Main { private static final String KEY = "requestId"; private static final Logger logger = LoggerFactory.getLogger(Main.class); private static final ExecutorService EXECUTOR = Executors.newSingleThreadExecutor(); public static void main(String[] args) { // 入口传入请求ID MDC.put(KEY, UUID.randomUUID().toString()); // 主线程打印<font style="color: #1e6bb8;word-wrap: break-word;font-weight: bold;border-bottom: 1px solid">日志</font> logger.debug("log in main thread"); // 异步线程打印<font style="color: #1e6bb8;word-wrap: break-word;font-weight: bold;border-bottom: 1px solid">日志</font>,用MDCRunnable装饰Runnable new Thread(new MDCRunnable(new Runnable() { @Override public void run() { logger.debug("log in other thread"); } })).start(); // 异步线程池打印日志,用MDCRunnable装饰Runnable EXECUTOR.execute(new MDCRunnable(new Runnable() { @Override public void run() { logger.debug("log in other thread pool"); } })); EXECUTOR.shutdown(); // 出口移除请求ID MDC.remove(KEY); } }