一、MDC介紹
MDC(Mapped Diagnostic Context,映射調(diào)試上下文)是 log4j 和 logback 提供的一種方便在多線程條件下記錄日志的功能。某些應(yīng)用程序采用多線程的方式來處理多個(gè)用戶的請求。在一個(gè)用戶的使用過程中,可能有多個(gè)不同的線程來進(jìn)行處理。典型的例子是 Web 應(yīng)用服務(wù)器。當(dāng)用戶訪問某個(gè)頁面時(shí),應(yīng)用服務(wù)器可能會創(chuàng)建一個(gè)新的線程來處理該請求,也可能從線程池中復(fù)用已有的線程。在一個(gè)用戶的會話存續(xù)期間,可能有多個(gè)線程處理過該用戶的請求。這使得比較難以區(qū)分不同用戶所對應(yīng)的日志。當(dāng)需要追蹤某個(gè)用戶在系統(tǒng)中的相關(guān)日志記錄時(shí),就會變得很麻煩。
一種解決的辦法是采用自定義的日志格式,把用戶的信息采用某種方式編碼在日志記錄中。這種方式的問題在于要求在每個(gè)使用日志記錄器的類中,都可以訪問到用戶相關(guān)的信息。這樣才可能在記錄日志時(shí)使用。這樣的條件通常是比較難以滿足的。MDC 的作用是解決這個(gè)問題。
MDC 可以看成是一個(gè)與當(dāng)前線程綁定的哈希表,可以往其中添加鍵值對。MDC 中包含的內(nèi)容可以被同一線程中執(zhí)行的代碼所訪問。當(dāng)前線程的子線程會繼承其父線程中的 MDC 的內(nèi)容。當(dāng)需要記錄日志時(shí),只需要從 MDC 中獲取所需的信息即可。MDC 的內(nèi)容則由程序在適當(dāng)?shù)臅r(shí)候保存進(jìn)去。對于一個(gè) Web 應(yīng)用來說,通常是在請求被處理的最開始保存這些數(shù)據(jù)。
二、MDC使用案例
相對比較大的項(xiàng)目來說,一般會有多個(gè)開發(fā)人員,如果每個(gè)開發(fā)人員憑自己的理解打印日志,那么當(dāng)用戶反饋問題時(shí),很難通過日志去快速的定位到出錯(cuò)原因,也會消耗更多的時(shí)間。所以針對這種問題,一般會定義好整個(gè)項(xiàng)目的日志格式,如果是需要追蹤的日志,開發(fā)人員調(diào)用統(tǒng)一的打印方法,在日志配置文件里面定義好相應(yīng)的字段,通過MDC功能就能很好的解決問題。
比如我們可以事先把用戶的sessionId,登錄用戶的用戶名,訪問的城市id,當(dāng)前訪問商戶id等信息定義成字段,線程開始時(shí)把值放入MDC里面,后續(xù)在其他地方就能直接使用,無需再去設(shè)置了。
使用MDC來記錄日志,一來可以規(guī)范多開發(fā)下日志格式的一致性,二來可以為后續(xù)使用ELK對日志進(jìn)行分析。
所需依賴
[plain]
view plain copy<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>
log4j.xml配置樣例,追蹤日志自定義格式主要在name="trance"的layout里面進(jìn)行設(shè)置,我們使用%X{userName}來定義此處會打印MDC里面key為userName的value,如果所定義的字段在MDC不存在對應(yīng)的key,那么將不會打印,會留一個(gè)占位符。
[html]
view plain copy<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration>
<appender name="console" class="org.apache.log4j.ConsoleAppender">
<param name="target" value="System.out" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-d{yyyy-MM-dd HH:mm:ss.SSS} %-6p%c:%L %x - %m%n" />
</layout>
</appender>
<appender name="error" class="org.apache.log4j.DailyRollingFileAppender">
<param name="File" value="D://logs//error.log" />
<param name="DatePattern" value="'.'yyyy-MM-dd" />
<param name="threshold" value="error"/>
<param name="append" value="true"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] %-6p%c:%L - %m%n" />
</layout>
</appender>
<appender name="logic" class="org.apache.log4j.DailyRollingFileAppender">
<param name="File" value="D://logs//logic.log" />
<param name="DatePattern" value="'.'yyyy-MM-dd" />
<param name="threshold" value="info"/>
<param name="append" value="true"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] %-6p%c:%L - %m%n" />
</layout>
</appender>
<appender name="trace" class="org.apache.log4j.DailyRollingFileAppender">
<param name="File" value="D://logs//trace.log" />
<param name="DatePattern" value="'.'yyyy-MM-dd" />
<param name="threshold" value="info"/>
<param name="append" value="true"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] - %X{mchId} - %X{mchName} - %X{siteName} - %X{sessionId} - %X{cityId} - %X{userName} - %X{mobile} - %m%n" />
</layout>
</appender>
<logger name="traceLog" additivity="false">
<level value="info" />
<appender-ref ref="trace" />
</logger>
<root>
<level value="info" />
<appender-ref ref="console"/>
<appender-ref ref="logic" />
<appender-ref ref="error" />
</root>
</log4j:configuration>
日志打印類
[java]
view plain copyimport org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TraceLogger {
//此處的"tranceLog"為log4j中定義的對應(yīng)的 logger的name
private static final Logger TRACE_LOGGER = LoggerFactory.getLogger("traceLog");
private TraceLogger() {
}
public static void info(String message){
TRACE_LOGGER.info(message);
}
public static void info(String format,Object... arguments){
TRACE_LOGGER.info(format, arguments);
}
}
最后寫個(gè)日志打印測試一下效果
[java]
view plain copy@Test
public void Test(){
MDC.clear();
MDC.put("sessionId" , "f9e287fad9e84cff8b2c2f2ed92adbe6");
MDC.put("cityId" , 1);
MDC.put("siteName" , "北京");
MDC.put("userName" , "userwyh");
TraceLogger. info("測試MDC打印一");
MDC.put("mobile" , "110");
TraceLogger. info("測試MDC打印二");
MDC.put("mchId" , 12);
MDC.put("mchName", "商戶名稱");
TraceLogger. info("測試MDC打印三");
}
執(zhí)行完后我們可以在定義的日志輸出路徑下看到以下輸出
[java]
view plain copy[2016-10-19 19:20:26.564] - - - 北京 - f9e287fad9e84cff8b2c2f2ed92adbe6 - 1 - userwyh - - 測試MDC打印一
[2016-10-19 19:20:26.565] - - - 北京 - f9e287fad9e84cff8b2c2f2ed92adbe6 - 1 - userwyh - 110 - 測試MDC打印二
[2016-10-19 19:20:26.565] - 12 - 商戶名稱 - 北京 - f9e287fad9e84cff8b2c2f2ed92adbe6 - 1 - userwyh - 110 - 測試MDC打印三