SpringBoot項目traceId生成/日志打印

參考文章 : W3C_0101博文鏈接

前言

查看服務日志時,當服務被調(diào)過于頻繁,日志刷新太快,會影響到聯(lián)調(diào)、測試、線上問題的排查效率,能不能為每一個請求的日志打一個唯一標識呢?后面使用該表示去匹配,直接檢索出該請求的日志?引入本文的正題,“traceId”。

image

MDC

MDC定義 Mapped Diagnostic Context,即:映射診斷環(huán)境。

MDC是 log4j 和 logback 提供的一種方便在多線程條件下記錄日志的功能。

MDC 可以看成是一個與當前線程綁定的哈希表,可以往其中添加鍵值對。

MDC的使用方法

? 向MDC設置值:MDC.put(key, value);

? 從MDC中取值:MDC.get(key);

? 將MDC中的內(nèi)容打印到日志中:%X{key};

初始化TraceId并向MDC設置值

這里主要是利用切面,方法執(zhí)行前設置MDC,方法執(zhí)行后擦除MDC。具體實現(xiàn)方式有很多,如過濾器、攔截器、AOP等等。個人比較推薦Filter實現(xiàn),因為Filter是請求最先碰到的,也是響應給前端前最后一個碰到的。

過濾器實現(xiàn)

@Slf4j
@WebFilter(filterName = "traceIdFilter", urlPatterns = "/*")
@Order(0)
@Component
public class TraceIdFilter implements Filter {
    /**
     * 日志跟蹤標識
     */
    public static final String TRACE_ID = "TRACE_ID";

    @Override
    public void init(FilterConfig filterConfig) {
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException {
        MDC.put(TRACE_ID, UUID.randomUUID().toString());
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {
        MDC.clear();
    }
}

攔截器實現(xiàn)

@Component
public class TraceIdInterceptor extends HandlerInterceptorAdapter {
    private static final String TRACE_ID = "TRACE_ID";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        MDC.put(TRACE_ID, UUID.randomUUID().toString());
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) {
        MDC.clear();
    }
}

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new TraceIdInterceptor());
    }
}

日志打印配置pattern中配置traceId

與之前的相比只是添加了[%X{TRACE_ID}], [%X{***}]是一個模板,中間屬性名是我們使用MDC put進去的。

%d{yyyy-MM-dd} [%thread] %-5level %logger{50} - [%X{TRACE_ID}] - %msg%n

異步方法的日志打印traceId

異步方法會開啟一個新線程,我們想要是異步方法和主線程共用同一個traceId,首先先新建一個任務適配器MdcTaskDecorator。

public class MdcTaskDecorator implements TaskDecorator {
    @Override
    public Runnable decorate(Runnable runnable) {
        Map<String, String> map = MDC.getCopyOfContextMap();
        return () -> {
            try {
                MDC.setContextMap(map);
                String traceId = MDC.get(TRACE_ID);
                if (StringUtils.isBlank(traceId)) {
                    traceId = UUID.randomUUID().toString();
                    MDC.put(TRACE_ID, traceId);
                }
                runnable.run();
            } finally {
                MDC.clear();
            }
        };
    }
}

在線程池配置中增加executor.setTaskDecorator(new MdcTaskDecorator())的設置

@EnableAsync
@Configuration
public class ThreadPoolConfig {
    private int corePoolSize = Runtime.getRuntime().availableProcessors() + 1;
    private int maxPoolSize = corePoolSize * 2;
    private static final int queueCapacity = 50;
    private static final int keepAliveSeconds = 30;

    @Bean(name = "threadPoolTaskExecutor")
    public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setMaxPoolSize(maxPoolSize);
        executor.setCorePoolSize(corePoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setKeepAliveSeconds(keepAliveSeconds);
        executor.setTaskDecorator(new MdcTaskDecorator());
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return executor;
    }
}

使用指定的線程池執(zhí)行業(yè)務代碼

@Async("threadPoolTaskExecutor")
public void testThreadPoolTaskExecutor(){
    log.info("Async 測試一下");
}

在響應DTO中返回traceId

@Data
@AllArgsConstructor
public class RetResult<T> {

    private Integer code;

    private String msg;

    private T data;

    private String traceId;

    public RetResult(Integer code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
        this.traceId = MDC.get(TRACE_ID);
    }

    public static <T> RetResult<T> success(T t) {
        return new RetResult<>(RetCode.SUCCESS.getCode(), "ok", t);
    }

    public static <T> RetResult<T> success() {
        return new RetResult<>(RetCode.SUCCESS.getCode(), "ok", null);
    }

    public static <T> RetResult<T> fail() {
        return new RetResult<>(RetCode.FAIL.getCode(), "fail", null);
    }

    public static <T> RetResult<T> fail(String msg) {
        return new RetResult<>(RetCode.FAIL.getCode(), msg, null);
    }

    public static <T> RetResult<T> fail(Integer code, String msg) {
        return new RetResult<>(code, msg, null);
    }

}
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容