Slf4j MDC实现原理分析

作者: holly_wang_王小飞 | 来源:发表于2016-12-08 15:08 被阅读2503次

    MDC ( Mapped Diagnostic Contexts ) 有了日志之后,我们就可以追踪各种线上问题。但是,在分布式系统中,各种无关日志穿行其中,导致我们可能无法直接定位整个操作流程。因此,我们可能需要对一个用户的操作流程进行归类标记,比如使用线程+时间戳,或者用户身份标识等;如此,我们可以从大量日志信息中grep出某个用户的操作流程,或者某个时间的流转记录。其目的是为了便于我们诊断线上问题而出现的方法工具类。虽然,Slf4j 是用来适配其他的日志具体实现包的,但是针对 MDC功能,目前只有logback 以及 log4j 支持。
    MDC

    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");
                    }
                }
            }
        }
    

    相关文章

      网友评论

        本文标题:Slf4j MDC实现原理分析

        本文链接:https://www.haomeiwen.com/subject/hnlsmttx.html