一、MDC介紹
MDC(Mapped Diagnostic Context,映射調試上下文)是 log4j 和 logback 提供的一種方便在多線程條件下記錄日誌的功能。某些應用程序採用多線程的方式來處理多個用戶的請求。在一個用戶的使用過程中,可能有多個不同的線程來進行處理。典型的例子是 Web 應用服務器。當用戶訪問某個頁面時,應用服務器可能會創建一個新的線程來處理該請求,也可能從線程池中複用已有的線程。在一個用戶的會話存續期間,可能有多個線程處理過該用戶的請求。這使得比較難以區分不同用戶所對應的日誌。當需要追蹤某個用戶在系統中的相關日誌記錄時,就會變得很麻煩。
一種解決的辦法是採用自定義的日誌格式,把用戶的信息採用某種方式編碼在日誌記錄中。這種方式的問題在於要求在每個使用日誌記錄器的類中,都可以訪問到用戶相關的信息。這樣纔可能在記錄日誌時使用。這樣的條件通常是比較難以滿足的。MDC 的作用是解決這個問題。
MDC 可以看成是一個與當前線程綁定的哈希表,可以往其中添加鍵值對。MDC 中包含的內容可以被同一線程中執行的代碼所訪問。當前線程的子線程會繼承其父線程中的 MDC 的內容。當需要記錄日誌時,只需要從 MDC 中獲取所需的信息即可。MDC 的內容則由程序在適當的時候保存進去。對於一個 Web 應用來說,通常是在請求被處理的最開始保存這些數據。
二、MDC使用案例
相對比較大的項目來說,一般會有多個開發人員,如果每個開發人員憑自己的理解打印日誌,那麼當用戶反饋問題時,很難通過日誌去快速的定位到出錯原因,也會消耗更多的時間。所以針對這種問題,一般會定義好整個項目的日誌格式,如果是需要追蹤的日誌,開發人員調用統一的打印方法,在日誌配置文件裏面定義好相應的字段,通過MDC功能就能很好的解決問題。
比如我們可以事先把用戶的sessionId,登錄用戶的用戶名,訪問的城市id,當前訪問商戶id等信息定義成字段,線程開始時把值放入MDC裏面,後續在其他地方就能直接使用,無需再去設置了。
使用MDC來記錄日誌,一來可以規範多開發下日誌格式的一致性,二來可以爲後續使用ELK對日誌進行分析。
所需依賴
- <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裏面進行設置,我們使用%X{userName}來定義此處會打印MDC裏面key爲userName的value,如果所定義的字段在MDC不存在對應的key,那麼將不會打印,會留一個佔位符。
- <?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>
日誌打印類
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- public class TraceLogger {
- //此處的"tranceLog"爲log4j中定義的對應的 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);
- }
- }
最後寫個日誌打印測試一下效果
- @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打印三");
- }
執行完後我們可以在定義的日誌輸出路徑下看到以下輸出
- [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打印三