天天看點

使用MDC為Logback slf4 日志記錄線程ID,區分每次執行的會話日志

版權聲明:本文為部落客原創文章,未經部落客允許不得轉載。 https://blog.csdn.net/catoop/article/details/71713232

我們先回想這樣一個場景:

在Java開發中,因為業務需求肯定會輸出很多日志。在出現問題時,根據輸出的日志分析問題。

對于一個通路量很大的網站來說,日志的輸出速度是很快的,同樣的代碼方法被同時調用是很正常的。

那麼現在問題來了,我們如何從日志中來區分每一個會話的日志呢?就是我們發現了一個異常,如何知道在這個異常之前對應的一些列日志是什麼呢?

我們都知道,不管是普通背景代碼執行(定時任務這種)還是使用者的HTTP請求,系統都會為開啟每一個獨立的線程來執行。

意思就是:10個使用者瞬間同時通路了同一個請求,那麼Tomcat就會配置設定10個不同的線程來分别執行每個請求。

思路來了:我們隻需要在輸出日志的時候,将每個線程的ID同時輸出出來即可。這樣我們首先要保證每個線程的ID是唯一的。logback日志本身就支援輸出線程名稱,使用這個是不行的,因為現在都是線程池,同一個線程是會被不同時間的多次請求公用的。

sl4j 提供的一個工具類

MDC

,支援 logback和log4j,其作用就是可以讓你放入一些變量值到日志中并輸出。

下面看一下片段代碼:

線上程執行第一行代碼之前放入變量值

MDC.put("ThreadID", java.util.UUID.randomUUID().toString().replaceAll("-", "").toUpperCase());           

然後在 (以logback為例) 中使用通配符就可以輸出這個值,配置片段如下:

注意留意其中的

{ThreadID}

<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>
                %d|%thread|%X{ThreadID}|%-5level|%logger{65}|%msg%n
            </pattern>
        </encoder>           

現在又有一個問題,如何讓線程在執行第一行(相對)代碼的時候使用 MDC.put 加入變量值呢?

使用 Filter、MVC 攔截器(Interceptor)。

特别注意:在開始的地方加入ID,同樣需要線上程結束的時候删除該ID,因為線程線上程池裡,如果不在執行完業務代碼後删除ThreadID,那麼該線程下次被使用的時候ThreadID還是存在的,就達不到我們預期的效果了。

不多說了,下面給出執行個體代碼:

我沒有使用Filter,使用的是HandlerInterceptor,道理一樣的。因為我嫌Filter 攔截範圍太大,什麼都攔截還要自己判斷處理(這個根據自己實際需求選擇吧)。

/**
 * 日志攔截器
 */
public class LogInterceptor implements HandlerInterceptor {

    /**
     * 會話ID
     */
    private final static String SESSION_TOKEN_KEY = "sessionTokenId";

    private static final transient Logger logger = LoggerFactory.getLogger(LogInterceptor.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 放SessionId
        String token = java.util.UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();
        MDC. put(SESSION_TOKEN_KEY, token);

        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
            throws Exception {
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        // 其他邏輯代碼

        // 最後執行MDC删除
        MDC. remove(SESSION_TOKEN_KEY);
    }
}           

logback.xml 配置檔案中的部分代碼,僅供參考。

<appender name="front"
        class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${logger.basedir}/front.log</file>
        <rollingPolicy
            class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${logger.basedir}/front.%d{yyyy-MM-dd}.%i.log
            </fileNamePattern>
            <maxFileSize>100MB</maxFileSize>
        </rollingPolicy>

        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>
                %d|%thread|%X{sessionTokenId}|%-5level|%logger{65}|%msg%n
            </pattern>
        </encoder>

        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>DENY</onMatch>
            <onMismatch>ACCEPT</onMismatch>
        </filter>
    </appender>           

logback 還内置了過濾器,比如 MDCInsertingServletFilter,說了這麼多都不如直接看官網資料,官網位址:

https://logback.qos.ch/manual/mdc.html#autoMDC