1. 程式人生 > >logback MDC機制

logback MDC機制

logback日誌與MDC機制

部落格分類:  JAVA  

    logback是個比較好用的java日誌輸出工具包,可配置型高,而且效能優秀。

 

一、Layout描述

1、%logger{length}、%c{length}、%lo{length}:在日誌事件的源點輸出logger的名稱,比如

    1)LoggerFactory.getLogger(TestMain.class),此時%logger的值為“com.xxx.TestMain”

    2)LoggerFactory.getLogger("FILE-LOGGER"),此時其值為“FILE-LOGGER”。

    其中{length}為可選項,length值為數字型別(>=0),在不丟失含義的情況下來限定logger名字的長度(縮短);在指定length情況下,將會直接返回“.”字元最右端的子字串。如下為示例:

Java程式碼   收藏程式碼
  1. 配置                      logger名                            結果  
  2. %logger         mainPackage.sub.sample.Bar  mainPackage.sub.sample.Bar  
  3. %logger{0}  mainPackage.sub.sample.Bar  Bar  
  4. %logger{5}  mainPackage.sub.sample.Bar  m.s.s.Bar  
  5. %logger{10} mainPackage.sub.sample.Bar  m.s.s.Bar  
  6. %logger{15} mainPackage.sub.sample.Bar  m.s.sample.Bar  
  7. %logger{16} mainPackage.sub.sample.Bar  m.sub.sample.Bar  
  8. %logger{26} mainPackage.sub.sample.Bar  mainPackage.sub.sample.Bar   

    由此可見,無論length如何設定,“Bar” 總會完整輸出;當length過小時,將會根據“.”分割且只輸出縮寫;根據length的情況,從最右端開始逐級補全。為了易讀,我們儘可能使用%logger輸出全名。

 

2、%C{length}、%class{length}:輸出發生日誌事件的呼叫類的全限定名。與%logger類似,{length}可選項來限定類名的長度,適度進行縮寫。

3、%d{pattern}、%date{pattern}、%d{pattern,timezone}、%date{pattern,timezone}:輸出日誌事件的時間;{pattern}為可選項,用於宣告時間的格式,比如%d{yyyy-MM-dd HH:mm:ss},pattern必須為“java.text.SimpleDateFormat”類可相容的格式。

4、%F、%file:輸出發生日誌請求的java原始檔名,產生檔名資訊不是特別的快,有一定的效能損耗,除非對執行速度不敏感否則應該避免使用此選項。(比如輸出:TestMain.java,預設異常棧中會輸出類名)

5、%caller{depth}、%caller{depthStart..depthEnd}:輸出產生日誌事件的呼叫者位置資訊,{depth}為可選項;位置資訊依賴於JVM實現,不過通常會包含呼叫方法的全限定名、檔名和行號。

假如:TestMain.java中main()-->test1()-->test2(),在test2方法中觸發日誌事件,假如%caller{3}將會輸出:

Java程式碼   收藏程式碼
  1. Caller+0    at com.test.demo.TestMain2.test2(TestMain2.java:26)  
  2. Caller+1    at com.test.demo.TestMain2.test1(TestMain2.java:18)  
  3. Caller+2    at com.test.demo.TestMain2.main(TestMain2.java:14)  

    這個配置項,對我們排查問題非常有用。不過在exception時,異常棧中已經包含了全部的追蹤棧。

 

6、%L、%line:輸出發生日誌請求的原始檔行號,產生行號資訊不是非常的快速,有一定的效能損耗,除非對執行速度不敏感否則應該避免使用此選項。(預設異常棧中會輸出行號)

7、%m、%msg、%message:在日誌中輸出應用提供的message。

    比如:LOGGER.error("message",exception),輸出“message”和exception棧。

8、%M、%method:輸出發出日誌記錄請求的方法名稱,產生方法名不是特別快速。

9、%n:輸出一個行分隔符,即換行符。(取決於執行平臺,可能是“\n”,"\r\n")

10、%p、%le、%level:輸出日誌事件的level。

11、%t、%thread:輸出產生日誌事件的執行緒名稱。

12、%ex{depth}、%exception{depth}:輸出日誌事件相關的異常棧,預設會輸出異常的全跟蹤棧。(%m會包含此部分)

13、%nopex:輸出日誌資料,但是忽略exception。

 

二、pom.xml

Java程式碼   收藏程式碼
  1. <dependency>  
  2.       <groupId>ch.qos.logback</groupId>  
  3.       <artifactId>logback-classic</artifactId>  
  4.       <version>1.1.8</version>  
  5. </dependency>  
  6. <dependency>  
  7.       <groupId>org.slf4j</groupId>  
  8.       <artifactId>slf4j-api</artifactId>  
  9.       <version>1.7.22</version>  
  10. </dependency>  

  

三、logback.xml樣例(放置在classpath下即可)

Java程式碼   收藏程式碼
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <configuration>  
  3.     <property name="LOG_HOME" value="logs"/>  
  4.     <property name="encoding" value="UTF-8"/>  
  5.     <appender name="DEFAULT" class="ch.qos.logback.core.rolling.RollingFileAppender">  
  6.         <file>${LOG_HOME}/test.log</file>  
  7.         <Append>true</Append>  
  8.         <prudent>false</prudent>  
  9.         <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">  
  10.             <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{50} %line - %m%n</pattern>  
  11.         </encoder>  
  12.         <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">  
  13.             <!--歸檔日誌檔名-->  
  14.             <FileNamePattern>${LOG_HOME}/test.log.%d{yyyy-MM-dd}</FileNamePattern>  
  15.             <maxHistory>15</maxHistory> <!-- 最多儲存15天曆史檔案 -->  
  16.         </rollingPolicy>  
  17.     </appender>  
  18.   
  19.     <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">  
  20.         <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">  
  21.             <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{50} %line - %m%n</pattern>  
  22.         </encoder>  
  23.     </appender>  
  24.   
  25.     <logger name="com.test.demo" level="DEBUG">  
  26.         <appender-ref ref="DEFAULT"/>  
  27.     </logger>  
  28.     <!-- 日誌輸出級別 -->  
  29.     <root level="DEBUG">  
  30.         <appender-ref ref="DEFAULT"/>  
  31.         <appender-ref ref="STDOUT"/>  
  32.     </root>  
  33. </configuration>  

 

四、MDC

    logback內建的日誌欄位還是比較少,如果我們需要列印有關業務的更多的內容,包括自定義的一些資料,需要藉助logback MDC機制,MDC為“Mapped Diagnostic Context”(對映診斷上下文),即將一些執行時的上下文資料通過logback打印出來;此時我們需要藉助org.sl4j.MDC類。

    MDC類基本原理其實非常簡單,其內部持有一個InheritableThreadLocal例項,用於儲存context資料,MDC提供了put/get/clear等幾個核心介面,用於操作ThreadLocal中的資料;ThreadLocal中的K-V,可以在logback.xml中宣告,最終將會列印在日誌中。

Java程式碼   收藏程式碼
  1. MDC.put("userId",1000);  

    那麼在logback.xml中,即可在layout中通過宣告“%X{userId}”來列印此資訊。

    在使用MDC時需要注意一些問題,這些問題通常也是ThreadLocal引起的,比如我們需要線上程退出之前清除(clear)MDC中的資料;線上程池中使用MDC時,那麼需要在子執行緒退出之前清除資料;可以呼叫MDC.clear()方法。

 

    在JAVA WEB專案中,為了更好的跟蹤請求,我們可能希望在日誌中列印比如HTTP header資訊、執行時的一些token、code等,那麼我們藉助MDC即可非常便捷的實現。我們開發一個Filter,此Filter用於解析Http請求中的一些引數,並將這些引數新增到MDC中,並在logback.xml中宣告我們關注的欄位。

 

    HttpRequestMDCFilter.java

Java程式碼   收藏程式碼
  1. import org.slf4j.MDC;  
  2.   
  3. import javax.servlet.*;  
  4. import javax.servlet.http.Cookie;  
  5. import javax.servlet.http.HttpServletRequest;  
  6. import java.io.IOException;  
  7. import java.net.InetAddress;  
  8. import java.net.NetworkInterface;  
  9. import java.util.Enumeration;  
  10.   
  11. /** 
  12.  * Created by liuguanqing on 16/12/28. 
  13.  * 在logback日誌輸出中增加MDC引數選項 
  14.  * 注意,此Filter儘可能的放在其他Filter之前 
  15.  * 
  16.  * 預設情況下,將會把“requestId”、“requestSeq”、“localIp”、“timestamp”、“uri”新增到MDC上下文中。 
  17.  * 1)其中requestId,requestSeq為呼叫鏈跟蹤使用,開發者不需要手動修改他們。 
  18.  * 2)localIp為當前web容器的宿主機器的本地IP,為內網IP。 
  19.  * 3)timestamp為請求開始被servlet處理的時間戳,設計上為被此Filter執行的開始時間,可以使用此值來判斷內部程式執行的效率。 
  20.  * 4)uri為當前request的uri引數值。 
  21.  * 
  22.  * 我們可以在logback.xml檔案的layout部分,通過%X{key}的方式使用MDC中的變數 
  23.  */  
  24. public class HttpRequestMDCFilter implements Filter {  
  25.   
  26.     /** 
  27.      *  是否開啟cookies對映,如果開啟,那麼將可以在logback中使用 
  28.      *  %X{_C_:<name>}來列印此cookie,比如:%X{_C_:user}; 
  29.      *  如果開啟此選項,還可以使用如下格式列印所有cookies列表: 
  30.      *  格式為:key:value,key:value 
  31.      *  %X{requestCookies} 
  32.      */  
  33.   
  34.     private boolean mappedCookies;  
  35.     /** 
  36.      * 是否開啟headers對映,如果開啟,將可以在logback中使用 
  37.      * %X{_H_:<header>}來列印此header,比如:%X{_H_:X-Forwarded-For} 
  38.      * 如果開啟此引數,還可以使用如下格式列印所有的headers列表: 
  39.      * 格式為:key:value,key:value 
  40.      * %X{requestHeaders} 
  41.      */  
  42.     private boolean mappedHeaders;  
  43.   
  44.     /** 
  45.      * 是否開啟parameters對映,此parameters可以為Get的查詢字串,可以為post的Form Entries 
  46.      * %X{_P_:<parameter>}來答應此引數值,比如:%X{_P_:page} 
  47.      * 如果開啟此引數,還可以使用如下格式列印所有的headers列表: 
  48.      * 格式為:key:value,key:value 
  49.      * %X{requestParameters} 
  50.      */  
  51.     private boolean mappedParameters;  
  52.   
  53.     private String localIp;//本機IP  
  54.   
  55.   
  56.     //all headers,content as key:value,key:value  
  57.     private static final String HEADERS_CONTENT = "requestHeaders";  
  58.   
  59.     //all cookies  
  60.     private static final String COOKIES_CONTENT = "requestCookies";  
  61.   
  62.     //all parameters  
  63.     private static final String PARAMETERS_CONTENT = "requestParameters";  
  64.   
  65.     @Override  
  66.     public void init(FilterConfig filterConfig) throws ServletException {  
  67.         mappedCookies = Boolean.valueOf(filterConfig.getInitParameter("mappedCookies"));  
  68.         mappedHeaders = Boolean.valueOf(filterConfig.getInitParameter("mappedHeaders"));  
  69.         mappedParameters = Boolean.valueOf(filterConfig.getInitParameter("mappedParameters"));  
  70.         //getLocalIp  
  71.         localIp = getLocalIp();  
  72.     }  
  73.   
  74.     private String getLocalIp() {  
  75.         try {  
  76.             //一個主機有多個網路介面  
  77.             Enumeration<NetworkInterface> netInterfaces = NetworkInterface.getNetworkInterfaces();  
  78.             while (netInterfaces.hasMoreElements()) {  
  79.                 NetworkInterface netInterface = netInterfaces.nextElement();  
  80.                 //每個網路介面,都會有多個"網路地址",比如一定會有loopback地址,會有siteLocal地址等.以及IPV4或者IPV6    .  
  81.                 Enumeration<InetAddress> addresses = netInterface.getInetAddresses();  
  82.                 while (addresses.hasMoreElements()) {  
  83.                     InetAddress address = addresses.nextElement();  
  84.                     //get only :172.*,192.*,10.*  
  85.                     if (address.isSiteLocalAddress() && !address.isLoopbackAddress()) {  
  86.                         return address.getHostAddress();  
  87.                     }  
  88.                 }  
  89.             }  
  90.         }catch (Exception e) {  
  91.             //  
  92.         }  
  93.         return null;  
  94.     }  
  95.   
  96.     @Override  
  97.     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {  
  98.         HttpServletRequest hsr = (HttpServletRequest)request;  
  99.         try {  
  100.             mdc(hsr);  
  101.         } catch (Exception e) {  
  102.             //  
  103.         }  
  104.   
  105.         try {  
  106.             chain.doFilter(request,response);  
  107.         } finally {  
  108.             MDC.clear();//must be,threadLocal  
  109.         }  
  110.   
  111.     }  
  112.   
  113.     private void mdc(HttpServletRequest hsr) {  
  114.         MDC.put(MDCConstants.LOCAL_IP_MDC_KEY,localIp);  
  115.         MDC.put(MDCConstants.REQUEST_ID_MDC_KEY,hsr.getHeader(MDCConstants.REQUEST_ID_HEADER));  
  116.         String requestSeq = hsr.getHeader(MDCConstants.REQUEST_SEQ_HEADER);  
  117.         if(requestSeq != null) {  
  118.             String nextSeq = requestSeq + "0";//seq will be like:000,real seq is the number of "0"  
  119.             MDC.put(MDCConstants.NEXT_REQUEST_SEQ_MDC_KEY,nextSeq);  
  120.         }else {  
  121.             MDC.put(MDCConstants.NEXT_REQUEST_SEQ_MDC_KEY,"0");  
  122.         }  
  123.         MDC.put(MDCConstants.REQUEST_SEQ_MDC_KEY,requestSeq);  
  124.         MDC.put(MDCConstants.TIMESTAMP,"" + System.currentTimeMillis());  
  125.         MDC.put(MDCConstants.URI_MDC_KEY,hsr.getRequestURI());  
  126.   
  127.         if(mappedHeaders) {  
  128.             Enumeration<String> e = hsr.getHeaderNames();  
  129.             if(e != null) {  
  130.                 //  
  131.                 while (e.hasMoreElements()) {  
  132.                     String header = e.nextElement();  
  133.                     String value = hsr.getHeader(header);  
  134.                     MDC.put(MDCConstants.HEADER_KEY_PREFIX + header, value);  
  135.                 &nb