项目背景:项目中用到了异步操作,导致一些操作无法直接通过Tomcat自带的线程号去追踪整个链路。实现跨线程的traceID打印可以解决这个问题。其实这玩意就相当于每个请求的requestId
定义拦截器,设置、清除traceID
@Slf4j
@Component
public class LogInterceptor implements HandlerInterceptor {
public LogInterceptor() {
log.info("LogInterceptor is loaded");
}
public String TRACE_ID = "traceId";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//如果有上层调用就用上层的ID
String traceId = request.getHeader(TRACE_ID);
if (traceId == null) {
traceId = UUID.fastUUID().toString();//TraceIdUtil.getTraceId()
}
MDC.put(TRACE_ID, traceId);
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.remove(TRACE_ID);
}
}
拦截器设置
@Slf4j
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
log.info("addInterceptors");
// 添加拦截器,并指定拦截路径
registry.addInterceptor(new LogInterceptor()).addPathPatterns("/**");
}
}
对原有的线程池配置进行修改,加上自定义的线程池装饰器。这么做是为了解决异步任务时,子线程的traceId 为空的问题??
/**
* 线程池
*/
@Configuration
@EnableAsync
public class SyncConfiguration {
public SyncConfiguration() {
System.out.println("线程池is loading");
}
@Bean(name = "scorePoolTaskExecutor")
public ThreadPoolTaskExecutor getScorePoolTaskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
//核心线程数
taskExecutor.setCorePoolSize(1);
//线程池维护线程的最大数量,只有在缓冲队列满了之后才会申请超过核心线程数的线程
taskExecutor.setMaxPoolSize(1);
//缓存队列
taskExecutor.setQueueCapacity(1);
//允许的空闲时间,当超过了核心线程数之外的线程在空闲时间到达之后会被销毁
taskExecutor.setKeepAliveSeconds(1);
//异步方法内部线程名称
taskExecutor.setThreadNamePrefix("Async-Service-");
taskExecutor.setTaskDecorator(new ContextCopyingDecorator());//配置装饰器
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
taskExecutor.initialize();
return taskExecutor;
}
}
自定义的线程池装饰器:解决子线程traceId丢失的问题
public class ContextCopyingDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
try {
RequestAttributes context = RequestContextHolder.currentRequestAttributes(); //1
Map<String,String> previous = MDC.getCopyOfContextMap(); //2
return () -> {
try {
RequestContextHolder.setRequestAttributes(context); //1
MDC.setContextMap(previous); //2
runnable.run();
} finally {
RequestContextHolder.resetRequestAttributes(); // 1
MDC.clear(); // 2
}
};
} catch (IllegalStateException e) {
return runnable;
}
}
}
给日志的配置文件加上traceID
[TRACEID:%X{traceId}]
注意:如果在MDC中放的Key叫AAA,那么这个变量名就应该叫{AAA}
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>[TRACEID:%X{traceId}] %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>