slf4j中的MDC工具類使用

????MDC是SLF4J中的一個(gè)類,通過MDC我們可以很方便的實(shí)現(xiàn)同一個(gè)線程內(nèi)(包括父線程和子線程之間)的日志的追蹤。對(duì)于Web項(xiàng)目來講,在MDC的幫助下,很方便的幫我們找出一次請(qǐng)求中所有的日志。下面我們先來看一下一個(gè)簡單的demo。


mdc-demo

????1、首先我們需要一個(gè)Java的Maven項(xiàng)目這里我們?nèi)∶麨閙dc-demo。
????2、引入我們需要的依賴包slf4j和logback

    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.7.21</version>
    </dependency>

    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.2.3</version>
    </dependency>

????3、新建logback.xml文件,配置日志打印格式,如果想打印MDC中的內(nèi)容,需要在appender的pattern中配置。

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd hh:mm:ss} [%thread] [traceId = %X{traceId}] [%logger{32}] - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="info">
        <appender-ref ref="console"/>
    </root>
</configuration>

補(bǔ)充以下,為什么配置文件要取名為logback.xml,因?yàn)閘ogback在讀取配置文件時(shí)會(huì)默認(rèn)讀取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代碼就可以了,運(yùn)行查看效果就可以了。

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

輸入結(jié)果如下,成功的打印出我們的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中的自己命名的變量名對(duì)應(yīng)起來就可以了。
具體mdc-demo我放在了github上,地址:


MDC工作原理

????看了demo之后,我們?cè)賮硪黄鹂匆幌?,MDC是如何把traceId放入每行日志中的,這里以logback為例來講解。
????1、先來看一下MDC類自身的結(jié)構(gòu),MDC類中提供了get,put,remove,clear四個(gè)常用的增刪改查接口,getCopyOfContextMap方法獲取整個(gè)線程上下文中存入的Map值,返回的是一個(gè)新的Map??梢苑乐乖碝ap被改動(dòng)后,影響已獲取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<String, String> getCopyOfContextMap()

    public static void setContextMap(Map<String, String> contextMap)

????再來看一下MDC類的初始化,MDC類中持有一個(gè)MDCAdapter接口類,在初始化時(shí)靜態(tài)代碼塊中,會(huì)調(diào)用bwCompatibleMDCAdapterFromBinder方法,在該方法中再調(diào)用StaticMDCBinder的getMDCA()方法,獲取MDCAdapter的一個(gè)實(shí)現(xiàn)。

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類的繼承關(guān)系

image.png

????從上圖中可以看出來,MDC類中持有一個(gè)MDCAdapter的接口,MDCAdapter接口有三個(gè)實(shí)現(xiàn)類,分別是BasicMDCAdapter,LogbackMDCAdapter,NOPMDCAdapter這三個(gè)類。其中BasicMDCAdapter,NOPMDCAdapter類是SLF4J框架中實(shí)現(xiàn)的。LogbackMDCAdapter是logback框架實(shí)現(xiàn)的。
????下面是MDC初始化時(shí)的一段代碼,靜態(tài)代碼塊中,調(diào)用了bwCompatibleGetMDCAdapterFromBinder()這個(gè)方法來初始化mdcAdapter接口,
再看bwCompatibleGetMDCAdapterFromBinder()方法的實(shí)現(xiàn),調(diào)用了StaticMDCBinder類的getMDCA()方法。StaticMDCBinder類的實(shí)現(xiàn)很簡單,getMDCA()的方法,直接返回了一個(gè)LogbackMDCAdapter的實(shí)例。

    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如何注入到打印的日志中

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容