關注作者公眾號【互聯網專欄】獲取本項目源碼
本項目源碼已在多個項目中實踐
接着上一篇文章,項目中使用了線程池,那么子線程中日志就會丟失traceId,下面講解如何實現子線程中的traceId日志跟蹤。
解決思路
子線程在打印日志的過程中traceId將丟失,解決方式為重寫線程池,將主線程的traceId繼續傳遞到子線程中。當然,對於直接new創建線程的情況不考略【實際應用中應該避免這種用法】。
繼承ThreadPoolExecutor,重寫執行任務的方法
public final class OverrideThreadPoolExecutor extends ThreadPoolExecutor {
@Override
public void execute(Runnable task) {
super.execute(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
}
@Override
public <T> Future<T> submit(Runnable task, T result) {
return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()), result);
}
@Override
public <T> Future<T> submit(Callable<T> task) {
return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
}
@Override
public Future<?> submit(Runnable task) {
return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
}
}
封裝ThreadMdcUtil工具類
以封裝Callable為例:
- 判斷當前線程對應MDC的Map是否存在,如果存在則設置子線程的ContextMap為當前線程的;
- 如果不存在,則重新生成traceId;
- 執行run方法
public final class ThreadMdcUtil {
public static void setTraceIdIfAbsent() {
if (MDC.get(TraceConstant.MDC_TRACE) == null || MDC.get(TraceConstant.MDC_TRACE).length() == 0) {
String tid = UUID.randomUUID().toString().replace("-", "");
MDC.put(TraceConstant.MDC_TRACE, tid);
}
}
public static <T> Callable<T> wrap(final Callable<T> callable, final Map<String, String> context) {
return () -> {
if (context == null) {
MDC.clear();
} else {
MDC.setContextMap(context);
}
setTraceIdIfAbsent();
try {
return callable.call();
} finally {
MDC.clear();
}
};
}
}
測試子線程中traceId的傳遞,本項目中ExecutorService已經重寫了線程池
@RestController
@RequestMapping("trace")
@Slf4j
@AllArgsConstructor
public class TestTraceController {
private final ExecutorService executorService;
@GetMapping("traceLog")
public String traceLog() {
log.info("---接口調用了---");
traceService();
asyncTrace();
return "success";
}
private void traceService(){
log.error("## 執行traceService方法");
}
private void asyncTrace(){
CompletableFuture.runAsync(()->{
log.info("執行線程池中的方法asyncTrace,未重寫了線程池");
}, executorService);
}
}