Java日誌Log4j或者Logback的NDC和MDC功能

NDC和MDC的區別

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

最後須要注意的是:

  • Use %X Map中所有數據
  • Use %X{key} 指定輸出Map中的key的值
  • Use %x 輸出Stack中的所有內容

MDC的使用例子

//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

相關文章
相關標籤/搜索