前言
查看服務日志時,當服務被調(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);
}
}