MDC ( Mapped Diagnostic Contexts ) 有了日志之后,我们就可以追踪各种线上问题。但是,在分布式系统中,各种无关日志穿行其中,导致我们可能无法直接定位整个操作流程。因此,我们可能需要对一个用户的操作流程进行归类标记,比如使用线程+时间戳,或者用户身份标识等;如此,我们可以从大量日志信息中grep出某个用户的操作流程,或者某个时间的流转记录。其目的是为了便于我们诊断线上问题而出现的方法工具类。虽然,Slf4j 是用来适配其他的日志具体实现包的,但是针对 MDC功能,目前只有logback 以及 log4j 支持。
package org.slf4j; import java.io.Closeable; import java.util.Map; import org.slf4j.helpers.NOPMDCAdapter; import org.slf4j.helpers.BasicMDCAdapter; import org.slf4j.helpers.Util; import org.slf4j.impl.StaticMDCBinder; import org.slf4j.spi.MDCAdapter; public class MDC { static final String NULL_MDCA_URL = "http://www.slf4j.org/codes.html#null_MDCA"; static final String NO_STATIC_MDC_BINDER_URL = "http://www.slf4j.org/codes.html#no_static_mdc_binder"; static MDCAdapter mdcAdapter; public static class MDCCloseable implements Closeable { private final String key; private MDCCloseable(String key) { this.key = key; } public void close() { MDC.remove(this.key); } } private MDC() { } static { try { mdcAdapter = StaticMDCBinder.SINGLETON.getMDCA(); } catch (NoClassDefFoundError ncde) { mdcAdapter = new NOPMDCAdapter(); String msg = ncde.getMessage(); if (msg != null && msg.indexOf("StaticMDCBinder") != -1) { Util.report("Failed to load class \"org.slf4j.impl.StaticMDCBinder\"."); Util.report("Defaulting to no-operation MDCAdapter implementation."); Util.report("See " + NO_STATIC_MDC_BINDER_URL + " for further details."); } else { throw ncde; } } catch (Exception e) { // we should never get here Util.report("MDC binding unsuccessful.", e); } } 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); } public static MDCCloseable putCloseable(String key, String val) throws IllegalArgumentException { put(key, val); return new MDCCloseable(key); } public static String get(String key) 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); } return mdcAdapter.get(key); } public static void remove(String key) 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.remove(key); } public static void clear() { if (mdcAdapter == null) { throw new IllegalStateException("MDCAdapter cannot be null. See also " + NULL_MDCA_URL); } mdcAdapter.clear(); } public static Map<String, String> getCopyOfContextMap() { if (mdcAdapter == null) { throw new IllegalStateException("MDCAdapter cannot be null. See also " + NULL_MDCA_URL); } return mdcAdapter.getCopyOfContextMap(); } public static void setContextMap(Map<String, String> contextMap) { if (mdcAdapter == null) { throw new IllegalStateException("MDCAdapter cannot be null. See also " + NULL_MDCA_URL); } mdcAdapter.setContextMap(contextMap); } public static MDCAdapter getMDCAdapter() { return mdcAdapter; } }
简单的demo
package com.alibaba.otter.canal.common; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; public class LogTest { private static final Logger logger = LoggerFactory.getLogger(LogTest.class); public static void main(String[] args) { MDC.put("THREAD_ID", String.valueOf(Thread.currentThread().getId())); logger.info("纯字符串信息的info级别日志"); } }
logback.xml 配置
<configuration scan="true" scanPeriod=" 5 seconds"> <jmxConfigurator /> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} %X{THREAD_ID} - %msg%n </pattern> </encoder> </appender> <root level="INFO"> <appender-ref ref="STDOUT"/> </root> </configuration>
对应的输出日志 可以看到输出了THREAD_ID
2016-12-08 14:59:32.855 [main] INFO com.alibaba.otter.canal.common.LogTest THREAD_ID 1 - 纯字符串信息的info级别日志
slf4j只是起到适配的作用 故查看实现类LogbackMDCAdapter属性
final InheritableThreadLocal<Map<String, String>> copyOnInheritThreadLocal = new InheritableThreadLocal<Map<String, String>>();
InheritableThreadLocal 该类扩展了 ThreadLocal,为子线程提供从父线程那里继承的值:在创建子线程时,子线程会接收所有可继承的线程局部变量的初始值,以获得父线程所具有的值。通常,子线程的值与父线程的值是一致的;但是,通过重写这个类中的 childValue 方法,子线程的值可以作为父线程值的一个任意函数。
当必须将变量(如用户 ID 和 事务 ID)中维护的每线程属性(per-thread-attribute)自动传送给创建的所有子线程时,应尽可能地采用可继承的线程局部变量,而不是采用普通的线程局部变量
验证一下
package com.alibaba.otter.canal.parse.driver.mysql; import org.junit.Test; public class TestInheritableThreadLocal { @Test public void testThreadLocal() { final ThreadLocal<String> local = new ThreadLocal<String>(); work(local); } @Test public void testInheritableThreadLocal() { final ThreadLocal<String> local = new InheritableThreadLocal<String>(); work(local); } private void work(final ThreadLocal<String> local) { local.set("a"); System.out.println(Thread.currentThread() + "," + local.get()); Thread t = new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread() + "," + local.get()); local.set("b"); System.out.println(Thread.currentThread() + "," + local.get()); } }); t.start(); try { t.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread() + "," + local.get()); } }
分别运行得到的输出结果
ThreadLocal 存贮输出结果 Thread[main,5,main],a Thread[Thread-0,5,main],null Thread[Thread-0,5,main],b Thread[main,5,main],a InheritableThreadLocal存贮输出结果 Thread[main,5,main],a Thread[Thread-0,5,main],a Thread[Thread-0,5,main],b Thread[main,5,main],a
输出结果说明一切 对于参数传递十分有用 我知道 canal的源码中用到了MDC
在 CanalServerWithEmbedded 中的 start 和stop等方法中都有用到
public void start(final String destination) { final CanalInstance canalInstance = canalInstances.get(destination); if (!canalInstance.isStart()) { try { MDC.put("destination", destination); canalInstance.start(); logger.info("start CanalInstances[{}] successfully", destination); } finally { MDC.remove("destination"); } } } public void stop(String destination) { CanalInstance canalInstance = canalInstances.remove(destination); if (canalInstance != null) { if (canalInstance.isStart()) { try { MDC.put("destination", destination); canalInstance.stop(); logger.info("stop CanalInstances[{}] successfully", destination); } finally { MDC.remove("destination"); } } } }
作者:holly_wang_王小飞
链接:https://www.jianshu.com/p/06b1d35526c2
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
转载请注明:学时网 » Slf4j MDC实现原理分析