slf4j中的MDC工具类使用

    MDC是SLF4J中的一个类,通过MDC我们可以很方便的实现同一个线程内(包括父线程和子线程之间)的日志的追踪。对于Web项目来讲,在MDC的帮助下,很方便的帮我们找出一次请求中所有的日志。下面我们先来看一下一个简单的demo。


mdc-demo

    1、首先我们需要一个Java的Maven项目这里我们取名为mdc-demo。
    2、引入我们需要的依赖包slf4j和logback

    
      org.slf4j
      slf4j-api
      1.7.21
    

    
      ch.qos.logback
      logback-classic
      1.2.3
    

    3、新建logback.xml文件,配置日志打印格式,如果想打印MDC中的内容,需要在appender的pattern中配置。



    
        
            %d{yyyy-MM-dd hh:mm:ss} [%thread] [traceId = %X{traceId}] [%logger{32}] - %msg%n
        
    

    
        
    

补充以下,为什么配置文件要取名为logback.xml,因为logback在读取配置文件时会默认读取resources目录下的logback命名的xml文件。源码如下,在ContextInitializer的类中。

    final public static String GROOVY_AUTOCONFIG_FILE = "logback.groovy";
    final public static String AUTOCONFIG_FILE = "logback.xml";
    final public static String TEST_AUTOCONFIG_FILE = "logback-test.xml";
    final public static String CONFIG_FILE_PROPERTY = "logback.configurationFile";

    public URL findURLOfDefaultConfigurationFile(boolean updateStatus) {
        ClassLoader myClassLoader = Loader.getClassLoaderOfObject(this);
        URL url = findConfigFileURLFromSystemProperties(myClassLoader, updateStatus);
        if (url != null) {
            return url;
        }

        url = getResource(TEST_AUTOCONFIG_FILE, myClassLoader, updateStatus);
        if (url != null) {
            return url;
        }

        url = getResource(GROOVY_AUTOCONFIG_FILE, myClassLoader, updateStatus);
        if (url != null) {
            return url;
        }

        return getResource(AUTOCONFIG_FILE, myClassLoader, updateStatus);
    }

    4、直接写Java代码就可以了,运行查看效果就可以了。

public class MdcTest {

    private static Logger logger = LoggerFactory.getLogger(MdcTest.class);

    public static void main(String[] args) {
        MDC.put("traceId", UUID.randomUUID().toString());
        logger.info("MdcTest");
        MDC.clear();
        logger.info("clear test");
        MDC.remove("traceId");
    }
}

输入结果如下,成功的打印出我们的traceId了。

2019-03-24 04:21:23 [main] [traceId = b48f3382-e71a-4fd1-a411-0e20661b2ccf] [com.tom.mdc.MdcTest] - MdcTest
2019-03-24 04:21:23 [main] [traceId = ] [com.tom.mdc.MdcTest] - clear test

这里我把MDC.put(key,value)中的key命名为了traceId,只要跟logback.xml中的自己命名的变量名对应起来就可以了。
具体mdc-demo我放在了github上,地址:


MDC工作原理

    看了demo之后,我们再来一起看一下,MDC是如何把traceId放入每行日志中的,这里以logback为例来讲解。
    1、先来看一下MDC类自身的结构,MDC类中提供了get,put,remove,clear四个常用的增删改查接口,getCopyOfContextMap方法获取整个线程上下文中存入的Map值,返回的是一个新的Map。可以防止源Map被改动后,影响已获取Map的正常使用。


    public static void put(String key, String val) throws IllegalArgumentException
    
    public static String get(String key) throws IllegalArgumentException
    
    public static void remove(String key) throws IllegalArgumentException

    public static void clear()

    public static Map getCopyOfContextMap()

    public static void setContextMap(Map contextMap)

    再来看一下MDC类的初始化,MDC类中持有一个MDCAdapter接口类,在初始化时静态代码块中,会调用bwCompatibleMDCAdapterFromBinder方法,在该方法中再调用StaticMDCBinder的getMDCA()方法,获取MDCAdapter的一个实现。

private static MDCAdapter bwCompatibleGetMDCAdapterFromBinder() throws NoClassDefFoundError {
        try {
            return StaticMDCBinder.getSingleton().getMDCA();
        } catch (NoSuchMethodError nsme) {
            // binding is probably a version of SLF4J older than 1.7.14
            return StaticMDCBinder.SINGLETON.getMDCA();
        }
    }

    static {
        try {
            mdcAdapter = bwCompatibleGetMDCAdapterFromBinder();
        } catch (NoClassDefFoundError ncde) {
            mdcAdapter = new NOPMDCAdapter();
            String msg = ncde.getMessage();
            if (msg != null && msg.contains("StaticMDCBinder")) {
                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);
        }
    }

     2、MDC类的继承关系

image.png

    从上图中可以看出来,MDC类中持有一个MDCAdapter的接口,MDCAdapter接口有三个实现类,分别是BasicMDCAdapter,LogbackMDCAdapter,NOPMDCAdapter这三个类。其中BasicMDCAdapter,NOPMDCAdapter类是SLF4J框架中实现的。LogbackMDCAdapter是logback框架实现的。
    下面是MDC初始化时的一段代码,静态代码块中,调用了bwCompatibleGetMDCAdapterFromBinder()这个方法来初始化mdcAdapter接口,
再看bwCompatibleGetMDCAdapterFromBinder()方法的实现,调用了StaticMDCBinder类的getMDCA()方法。StaticMDCBinder类的实现很简单,getMDCA()的方法,直接返回了一个LogbackMDCAdapter的实例。

    private static MDCAdapter bwCompatibleGetMDCAdapterFromBinder() throws NoClassDefFoundError {
        try {
            return StaticMDCBinder.getSingleton().getMDCA();
        } catch (NoSuchMethodError nsme) {
            // binding is probably a version of SLF4J older than 1.7.14
            return StaticMDCBinder.SINGLETON.getMDCA();
        }
    }

    static {
        try {
            mdcAdapter = bwCompatibleGetMDCAdapterFromBinder();
        } catch (NoClassDefFoundError ncde) {
            mdcAdapter = new NOPMDCAdapter();
            String msg = ncde.getMessage();
            if (msg != null && msg.contains("StaticMDCBinder")) {
                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 class StaticMDCBinder {

    /**
     * The unique instance of this class.
     */
    public static final StaticMDCBinder SINGLETON = new StaticMDCBinder();

    private StaticMDCBinder() {
    }

    /**
     * Currently this method always returns an instance of 
     * {@link StaticMDCBinder}.
     */
    public MDCAdapter getMDCA() {
        return new LogbackMDCAdapter();
    }

    public String getMDCAdapterClassStr() {
        return LogbackMDCAdapter.class.getName();
    }
}

    3、traceId如何注入到打印的日志中

你可能感兴趣的:(slf4j中的MDC工具类使用)