Java中使用的日誌的實現框架有不少種,經常使用的log4j和logback以及java.util.logging,而log4j是apache實現的一個開源日誌組件(Wrapped implementations),logback是slf4j的原生實現(Native implementations)。須要說明的slf4j是Java簡單日誌的門面(The Simple Logging Facade for Java),若是使用slf4j日誌門面,必需要用到slf4j-api,而logback是直接實現的,因此不須要其餘額外的轉換以及轉換帶來的消耗,而slf4j要調用log4j的實現,就須要一個適配層,將log4j的實現適配到slf4j-api可調用的模式。html
說完基本的日誌框架的區別以後,咱們再看看NDC和MDC。java
不論是log4j仍是logback,打印的日誌要能體現出問題的所在,可以快速的定位到問題的癥結,就必須攜帶上下文信息(context information),那麼其存儲該信息的兩個重要的類就是NDC(Nested Diagnostic Context)和MDC(Mapped Diagnositc Context)。web
NDC採用棧的機制存儲上下文,線程獨立的,子線程會從父線程拷貝上下文。其調用方法以下:spring
1.開始調用 NDC.push(message);apache
2.刪除棧頂消息 NDC.pop();api
3.清除所有的消息,必須在線程退出前顯示的調用,不然會致使內存溢出。 NDC.remove();微信
4.輸出模板,注意是小寫的[%x] log4j.appender.stdout.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ssS}] [%x] : %m%nsession
MDC採用Map的方式存儲上下文,線程獨立的,子線程會從父線程拷貝上下文。其調用方法以下:mvc
1.保存信息到上下文 MDC.put(key, value);app
2.從上下文獲取設置的信息 MDC.get(key);
3.清楚上下文中指定的key的信息 MDC.remove(key);
4.清除全部 clear()
5.輸出模板,注意是大寫[%X{key}] log4j.appender.consoleAppender.layout.ConversionPattern = %-4r [%t] %5p %c %x - %m - %X{key}%n
最後須要注意的是:
//MdcUtils.java // import ...MdcConstants // 這個就是定義一個常量的類,定義了SERVER、SESSION_ID等 import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; public class MdcUtils { private final static Logger logger = LoggerFactory.getLogger(MdcUtils.class); private static void put(String key, Object value) { if (value != null) { String val = value.toString(); if (StringUtils.isNoneBlank(key, val)) { MDC.put(key, val); } } } public static String getServer() { return MDC.get(MdcConstants.SERVER); } public static void putServer(String server) { put(MdcConstants.SERVER, server); } public static String getSessionId() { return MDC.get(MdcConstants.SESSION_ID); } public static void putSessionId(String sId) { put(MdcConstants.SESSION_ID, sId); } public static void clear() { MDC.clear(); logger.debug("mdc clear done."); } }
上述工具類中MdcConstants是定義一個常量的類,定義了SERVER、SESSION_ID等,put方法就是調用了slf4j的MDC的put方法。其餘方法類比。
看看使用該工具類的具體方式:
// MdcClearInterceptor.java import ...MdcUtils; // 導入上面的工具類 import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class MdcClearInterceptor extends HandlerInterceptorAdapter { @Override public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { MdcUtils.clear(); } }
在該攔截器中,重寫了afterConcurrentHandlingStarted方法,該方法執行了工具類的clear方法,也就是經過調用slf4j的clear方法清除了本次會話上下文的日誌信息。爲何要放在afterConcurrentHandlingStarted方法中呢?這恐怕得從springmvc的攔截器的實現提及。
springmvc的攔截HandlerInterceptor接口定義了三個方法(代碼以下),具體說明在方法註釋上:
public interface HandlerInterceptor { //在控制器方法調用前執行 //返回值爲是否中斷,true,表示繼續執行(下一個攔截器或處理器) //false則會中斷後續的全部操做,因此咱們須要使用response來響應請求 boolean preHandle( HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; //在控制器方法調用後,解析視圖前調用,咱們能夠對視圖和模型作進一步渲染或修改 void postHandle( HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception; //整個請求完成,即視圖渲染結束後調用,這個時候能夠作些資源清理工做,或日誌記錄等 void afterCompletion( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception; }
不少時候,咱們只須要上面這3個方法就夠了,由於咱們只須要繼承HandlerInterceptorAdapter
就能夠了,HandlerInterceptorAdapter間接實現了HandlerInterceptor接口,併爲HandlerInterceptor的三個方法作了空實現,於是更方便咱們定製化本身的實現。
相對於HandlerInterceptor,HandlerInterceptorAdapter多了一個實現方法afterConcurrentHandlingStarted()
,它來自HandlerInterceptorAdapter的直接實現類AsyncHandlerInterceptor
,AsyncHandlerInterceptor接口直接繼承了HandlerInterceptor,並新添了afterConcurrentHandlingStarted()方法用於處理異步請求,當Controller中有異步請求方法的時候會觸發該方法時,異步請求先支持preHandle、而後執行afterConcurrentHandlingStarted。異步線程完成以後執行preHandle、postHandle、afterCompletion。
那至於這些可能用到的日誌字段從什麼地方賦值呢,也就是什麼地方調用MDCUtils.put()方法呢?通常咱們都會實現一個RequestHandlerInterceptor,在preHandler方法中處理日誌字段便可。以下:
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (DispatcherType.ASYNC.equals(request.getDispatcherType())) { return true; } // 開始保存信息到日誌上下文 MdcUtils.putServer(request.getServerName()); String sId = request.getHeader(HeaderConstants.SESSION_ID); MdcUtils.putSessionId(sId); if (sessionWhiteList.contains(request.getPathInfo())) { return true; } // TODO 處理其餘業務 }
還沒完,就目前看,咱們已經有兩個自定義的攔截器實現了。怎麼使用,才能將日誌根據咱們的意願正確的打印呢?必然,攔截器是有順序的,若是配置了多個攔截器,會造成一條攔截器鏈,執行順序相似於AOP,前置攔截先定義的先執行,後置攔截和完結攔截(afterCompletion)後註冊的後執行。
Soga,咱們須要清除上次請求的一些無用的信息,再次將咱們的信息寫入到MDC中(攔截器的配置在DispatcherServlet中),因爲afterConcurrentHandlingStarted()方法須要異步請求觸發,所以咱們須要在web.xml的DispatchServlet配置增長<async-supported>true</async-supported>
配置。
<mvc:interceptors> <bean class="com.xxx.handler.MdcClearInterceptor"/> <bean class="com.xxx.handler.RequestContextInterceptor"/> </mvc:interceptors>
或者這樣:
<mvc:interceptors> <!-- 前置攔截器 --> <mvc:interceptor> <!-- 這裏面還以增長一些攔截條件--> <!--<mvc:exclude-mapping path="/user/logout"/>--> <!-- 用戶退出登陸請求 --> <!-- <mvc:exclude-mapping path="/home/"/> --> <!--在home中定義了無須登陸的方法請求,直接過濾攔截--> <!-- <mvc:mapping path="/**"/>--> <bean class="com.xxx.handler.MdcClearInterceptor"/> </mvc:interceptor> <!-- 後置攔截器 --> <mvc:interceptor> <bean class="com.xxx.handler.RequestContextInterceptor"/> </mvc:interceptor> </mvc:interceptors>
該文首發《虛懷若谷》我的博客,轉載前請務必署名,轉載請標明出處。
古之善爲道者,微妙玄通,深不可識。夫惟不可識,故強爲之容: 豫兮若冬涉川,猶兮若畏四鄰,儼兮其若客,渙兮若冰之釋,敦兮其若樸,曠兮其若谷,混兮其若濁。 孰能濁以靜之徐清?孰能安以動之徐生? 保此道不欲盈。夫惟不盈,故能敝而新成。
請關注個人微信公衆號:下雨就像彈鋼琴,Thanks♪(・ω・)ノ
原文出處:https://www.cnblogs.com/joyven/p/11776524.html