????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)系

????從上圖中可以看出來,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如何注入到打印的日志中