logback MDC機制
logback日誌與MDC機制
部落格分類: JAVAlogback是個比較好用的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程式碼- 配置 logger名 結果
- %logger mainPackage.sub.sample.Bar mainPackage.sub.sample.Bar
- %logger{0} mainPackage.sub.sample.Bar Bar
- %logger{5} mainPackage.sub.sample.Bar m.s.s.Bar
- %logger{10} mainPackage.sub.sample.Bar m.s.s.Bar
- %logger{15} mainPackage.sub.sample.Bar m.s.sample.Bar
- %logger{16} mainPackage.sub.sample.Bar m.sub.sample.Bar
- %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程式碼- Caller+0 at com.test.demo.TestMain2.test2(TestMain2.java:26)
- Caller+1 at com.test.demo.TestMain2.test1(TestMain2.java:18)
- 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程式碼- <dependency>
- <groupId>ch.qos.logback</groupId>
- <artifactId>logback-classic</artifactId>
- <version>1.1.8</version>
- </dependency>
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-api</artifactId>
- <version>1.7.22</version>
- </dependency>
三、logback.xml樣例(放置在classpath下即可)
Java程式碼- <?xml version="1.0" encoding="UTF-8"?>
- <configuration>
- <property name="LOG_HOME" value="logs"/>
- <property name="encoding" value="UTF-8"/>
- <appender name="DEFAULT" class="ch.qos.logback.core.rolling.RollingFileAppender">
- <file>${LOG_HOME}/test.log</file>
- <Append>true</Append>
- <prudent>false</prudent>
- <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
- <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{50} %line - %m%n</pattern>
- </encoder>
- <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
- <!--歸檔日誌檔名-->
- <FileNamePattern>${LOG_HOME}/test.log.%d{yyyy-MM-dd}</FileNamePattern>
- <maxHistory>15</maxHistory> <!-- 最多儲存15天曆史檔案 -->
- </rollingPolicy>
- </appender>
- <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
- <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
- <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{50} %line - %m%n</pattern>
- </encoder>
- </appender>
- <logger name="com.test.demo" level="DEBUG">
- <appender-ref ref="DEFAULT"/>
- </logger>
- <!-- 日誌輸出級別 -->
- <root level="DEBUG">
- <appender-ref ref="DEFAULT"/>
- <appender-ref ref="STDOUT"/>
- </root>
- </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程式碼- 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程式碼- import org.slf4j.MDC;
- import javax.servlet.*;
- import javax.servlet.http.Cookie;
- import javax.servlet.http.HttpServletRequest;
- import java.io.IOException;
- import java.net.InetAddress;
- import java.net.NetworkInterface;
- import java.util.Enumeration;
- /**
- * Created by liuguanqing on 16/12/28.
- * 在logback日誌輸出中增加MDC引數選項
- * 注意,此Filter儘可能的放在其他Filter之前
- *
- * 預設情況下,將會把“requestId”、“requestSeq”、“localIp”、“timestamp”、“uri”新增到MDC上下文中。
- * 1)其中requestId,requestSeq為呼叫鏈跟蹤使用,開發者不需要手動修改他們。
- * 2)localIp為當前web容器的宿主機器的本地IP,為內網IP。
- * 3)timestamp為請求開始被servlet處理的時間戳,設計上為被此Filter執行的開始時間,可以使用此值來判斷內部程式執行的效率。
- * 4)uri為當前request的uri引數值。
- *
- * 我們可以在logback.xml檔案的layout部分,通過%X{key}的方式使用MDC中的變數
- */
- public class HttpRequestMDCFilter implements Filter {
- /**
- * 是否開啟cookies對映,如果開啟,那麼將可以在logback中使用
- * %X{_C_:<name>}來列印此cookie,比如:%X{_C_:user};
- * 如果開啟此選項,還可以使用如下格式列印所有cookies列表:
- * 格式為:key:value,key:value
- * %X{requestCookies}
- */
- private boolean mappedCookies;
- /**
- * 是否開啟headers對映,如果開啟,將可以在logback中使用
- * %X{_H_:<header>}來列印此header,比如:%X{_H_:X-Forwarded-For}
- * 如果開啟此引數,還可以使用如下格式列印所有的headers列表:
- * 格式為:key:value,key:value
- * %X{requestHeaders}
- */
- private boolean mappedHeaders;
- /**
- * 是否開啟parameters對映,此parameters可以為Get的查詢字串,可以為post的Form Entries
- * %X{_P_:<parameter>}來答應此引數值,比如:%X{_P_:page}
- * 如果開啟此引數,還可以使用如下格式列印所有的headers列表:
- * 格式為:key:value,key:value
- * %X{requestParameters}
- */
- private boolean mappedParameters;
- private String localIp;//本機IP
- //all headers,content as key:value,key:value
- private static final String HEADERS_CONTENT = "requestHeaders";
- //all cookies
- private static final String COOKIES_CONTENT = "requestCookies";
- //all parameters
- private static final String PARAMETERS_CONTENT = "requestParameters";
- @Override
- public void init(FilterConfig filterConfig) throws ServletException {
- mappedCookies = Boolean.valueOf(filterConfig.getInitParameter("mappedCookies"));
- mappedHeaders = Boolean.valueOf(filterConfig.getInitParameter("mappedHeaders"));
- mappedParameters = Boolean.valueOf(filterConfig.getInitParameter("mappedParameters"));
- //getLocalIp
- localIp = getLocalIp();
- }
- private String getLocalIp() {
- try {
- //一個主機有多個網路介面
- Enumeration<NetworkInterface> netInterfaces = NetworkInterface.getNetworkInterfaces();
- while (netInterfaces.hasMoreElements()) {
- NetworkInterface netInterface = netInterfaces.nextElement();
- //每個網路介面,都會有多個"網路地址",比如一定會有loopback地址,會有siteLocal地址等.以及IPV4或者IPV6 .
- Enumeration<InetAddress> addresses = netInterface.getInetAddresses();
- while (addresses.hasMoreElements()) {
- InetAddress address = addresses.nextElement();
- //get only :172.*,192.*,10.*
- if (address.isSiteLocalAddress() && !address.isLoopbackAddress()) {
- return address.getHostAddress();
- }
- }
- }
- }catch (Exception e) {
- //
- }
- return null;
- }
- @Override
- public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
- HttpServletRequest hsr = (HttpServletRequest)request;
- try {
- mdc(hsr);
- } catch (Exception e) {
- //
- }
- try {
- chain.doFilter(request,response);
- } finally {
- MDC.clear();//must be,threadLocal
- }
- }
- private void mdc(HttpServletRequest hsr) {
- MDC.put(MDCConstants.LOCAL_IP_MDC_KEY,localIp);
- MDC.put(MDCConstants.REQUEST_ID_MDC_KEY,hsr.getHeader(MDCConstants.REQUEST_ID_HEADER));
- String requestSeq = hsr.getHeader(MDCConstants.REQUEST_SEQ_HEADER);
- if(requestSeq != null) {
- String nextSeq = requestSeq + "0";//seq will be like:000,real seq is the number of "0"
- MDC.put(MDCConstants.NEXT_REQUEST_SEQ_MDC_KEY,nextSeq);
- }else {
- MDC.put(MDCConstants.NEXT_REQUEST_SEQ_MDC_KEY,"0");
- }
- MDC.put(MDCConstants.REQUEST_SEQ_MDC_KEY,requestSeq);
- MDC.put(MDCConstants.TIMESTAMP,"" + System.currentTimeMillis());
- MDC.put(MDCConstants.URI_MDC_KEY,hsr.getRequestURI());
- if(mappedHeaders) {
- Enumeration<String> e = hsr.getHeaderNames();
- if(e != null) {
- //
- while (e.hasMoreElements()) {
- String header = e.nextElement();
- String value = hsr.getHeader(header);
- MDC.put(MDCConstants.HEADER_KEY_PREFIX + header, value);
- &nb