Slf4j的MDC初嘗試


為什么會用到MDC?

本人使用Java兩年時間,鑒於經驗有限,在開發java后端代碼過程中,為了定位問題,希望同一個線程的requestId可以從web層的日志一直輸出到dao層,這樣使用Linux命令 grep 的時候,可以把同一個線程的相關日志都檢索出來,一開始我是這樣實現的:

 在每次請求的時候,獲取到請求的sessionId或者在web層生成一個sessionId,並將該sessionId透傳到service層,dao層等,然后在每次log中將該log輸出到日志中。

  

這個方案是完全可以實現上述功能的,但是代碼侵入型強且代碼冗余。為了實現從web端到底層的所有log輸出同一個線程的sessionId,需要透傳該id且顯示打印到日志中。

於是我在思考,肯定是有更合適的方式解決此類問題,因此找到了MDC這個東東。

什么是MDC?

MDC(Mapped Diagnostic Context,映射調試上下文)是 log4j 和 logback 提供的一種方便在多線程條件下記錄日志的功能。具體介紹參考 鏈接

自己的理解,MDC相當於一個全局的哈希表,配合AOP/Filter/Interceptor這類工具,在每個請求到來時,將對應的sessionId put到MDC中,同時在log輸出中增加 %X{對應的key},會自動將每個線程相關的日志增加上sessionId這個字段,很方便。

關於MDC的底層實現原理,可參考這篇博客

使用Demo

下面以Interceptor為例,看下MDC的使用。

具體使用環境:
spring boot工程

構造一個攔截器

package xxx;

import java.util.UUID;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.MDC;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

/***
 * 日志攔截器的Demo
 * 
 * @author xxx
 * @since 2018/12/06
 */
public class LogInterceptor extends HandlerInterceptorAdapter {
    private final static String REQUEST_ID = "REQUEST_ID";

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        // 刪除requestId
        MDC.remove(REQUEST_ID);
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        String requestId = UUID.randomUUID().toString().replace("-", "");
// 在攔截器中將對應的requestId放到MDC中 MDC.put(REQUEST_ID, requestId);
return true; } }

 

添加攔截器 

package xxx;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

/**
 * 注冊攔截器Demo
 * 
 * @author xxx
 * @since 2018/12/06
 */
@Configuration
@ComponentScan(basePackageClasses={WebMvcConfigDemo.class})
public class WebMvcConfigDemo extends WebMvcConfigurerAdapter {

    /** 把相關的攔截器注入為Bean */
    @Bean
    public HandlerInterceptor logInterceptor() {
        return new LogInterceptor();
    }

    /** 添加攔截器 */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(logInterceptor()).addPathPatterns("/**");
        super.addInterceptors(registry);
    }

}

 

日志配置

<property name="CONSOLE_LOG_PATTERN" value="%red(%date{yyyy-MM-dd HH:mm:ss.SSS}) %X{REQUEST_ID}  %green([%16.16thread]) %highlight(%-5level) %boldGreen(%-40.40logger{39}) - %msg%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"></property>

  

測試demo

web層代碼

@GetMapping(value = "/getById")
    public Result getById(@RequestParam(name = "id") Long id) {
        logger.info("==========test log requestId in controller==============");
        return dataplusAuthorityTenantService.testMDCInService(id);
    }

 

service層代碼

@Override
    public Result testMDCInService(Long id) {
        logger.info("==========test log requestId in service==============");
        return super.get(id);
    }

 

測試輸出

2018-12-06 19:49:45.298 ed4b3d86377140af8f8f3f138dfc0f78  [-nio-7001-exec-1] INFO  c.a.DataplusAuthorityTenantApiController - ==========test log requestId in controller==============
2018-12-06 19:49:45.316 ed4b3d86377140af8f8f3f138dfc0f78  [32m[-nio-7001-exec-1] INFO  s.d.i.DataplusAuthorityTenantServiceImpl - ==========test log requestId in service==============

  

MDC帶來的好處

1 應急

如果你的系統已經上線,突然日志中要增加一些額外信息,如果直接改代碼,那你的代碼都需要打補丁;如果直接扔在MDC中,直接配置在log中即可。

2 代碼規范

在多線程環境中(現在幾乎沒有單線程),可直接通過攔截器/過濾器/AOP+log配置方式直接輸出每個線程唯一的sessionId,不需要侵入到每行代碼;

3 日志鏈路追蹤

同2,尤其是喜歡在日志文件中使用grep命令的童鞋,一鍵grep;

參考文章


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM