MDC 实现 traceId 记录
目的
- 在web请求中记录实现 traceId追踪
- 能线程池中也可以传递 traceId
通用
ThreadPoolTaskExecutor 实现 MDC 传递
public class MdcTaskExecutorCustomizer implements TaskExecutorCustomizer {
@Override
public void customize(ThreadPoolTaskExecutor taskExecutor) {
taskExecutor.setTaskDecorator(runnable -> {
Map<String, String> context = MDC.getCopyOfContextMap();
return () -> {
MDC.setContextMap(context);
try {
runnable.run();
} finally {
MDC.clear();
}
};
});
}
}
应用到 所有 ThreadPoolTaskExecutor
public class MDCBeanPostProcessor implements InstantiationAwareBeanPostProcessor, ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
if (bean instanceof ThreadPoolTaskExecutor) {
MdcTaskExecutorCustomizer mdcTaskExecutorCustomizer = new MdcTaskExecutorCustomizer();
ThreadPoolTaskExecutor executor = (ThreadPoolTaskExecutor) bean;
mdcTaskExecutorCustomizer.customize(executor);
return true;
}
return InstantiationAwareBeanPostProcessor.super.postProcessAfterInstantiation(bean, beanName);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
MDCUtil
public abstract class MDCUtil {
public static final String TRACE_ID = "traceId";
public static String getTraceId() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
public static void startTrace() {
MDC.put(TRACE_ID, UUID.randomUUID().toString().replaceAll("-", ""));
}
public static void startTrace(String traceId) {
MDC.put(TRACE_ID, traceId);
}
public static void endTrace() {
MDC.clear();
}
}
servlet 环境
自定义 HandlerInterceptor
public class MDCMvcHandlerInterceptorInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
MDCUtil.startTrace();
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
MDCUtil.endTrace();
}
}
reactive 环境(webflux)
public class MDCWebFilter implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
String traceId = MDCUtil.getTraceId();
MDCUtil.startTrace(traceId);
return chain.filter(exchange).subscriberContext(Context.of(MDCUtil.TRACE_ID, traceId)).doOnTerminate(MDCUtil::endTrace);
}
}
自动装配
@Configuration
@ConditionalOnClass(ThreadPoolTaskExecutor.class)
public class MDCAutoConfiguration {
//
// @Bean
// public MdcTaskExecutorCustomizer mdcTaskExecutorCustomizer() {
// return new MdcTaskExecutorCustomizer();
// }
@Bean
public MDCBeanPostProcessor mdcBeanPostProcessor() {
return new MDCBeanPostProcessor();
}
//====================================reactive=================================//
@Bean
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
public MDCWebFilter mdcWebFilter() {
return new MDCWebFilter();
}
//=======================================servlet================================//
@Configuration
@ConditionalOnClass(DispatcherServlet.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
public class InterceptorConfig implements WebMvcConfigurer {
@Bean
public HandlerInterceptor mdcMvcHandlerInterceptorInterceptor() {
return new MDCMvcHandlerInterceptorInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(mdcMvcHandlerInterceptorInterceptor());
}
}
}
自定义注解
@EnabledTraceId
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(MDCTracerImportSelector.class)
public @interface EnabledTraceId {
}
MDCTracerImportSelector
public class MDCTracerImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.x.z.stater.mdc.MDCAutoConfiguration"};
}
}
使用
@EnabledTraceId
@SpringBootApplication
public class MvcApplication {
public static void main(String[] args) {
SpringApplication.run(MvcApplication.class, args);
}
}
日志上加入 traceId
<?xml version="1.0" encoding="UTF-8" ?>
<configuration scan="true" scanPeriod="30 seconds">
<!--0. 日志格式和颜色渲染 -->
<!-- 彩色日志依赖的渲染类 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
<conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
<!-- 彩色日志格式 -->
<property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} [%X{traceId}]: %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<logger name="org.springframework.web" level="ERROR"/>
<logger name="org.springboot.sample" level="ERROR" />
<logger name="org.mybatis" level="INFO" />
<logger name="org.springframework.data.mongodb.core" level="DEBUG"/>
<!--1. 输出到控制台-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>debug</level>
</filter>
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<!-- 设置字符集 -->
<charset>UTF-8</charset>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="CONSOLE" />
</root>
</configuration>
测试
@RestController
@Slf4j
public class MdcController {
@Autowired
private ThreadPoolTaskExecutor executor;
@GetMapping("/run/mdc3")
public String runMdc3() throws InterruptedException {
log.info(Thread.currentThread().getName() + ": " + MDC.get("traceId"));
executor.execute(() -> {
log.info(Thread.currentThread().getName() + ": " + MDC.get("traceId"));
executor.execute(() -> {
log.info(Thread.currentThread().getName() + ": " + MDC.get("traceId"));
});
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info(Thread.currentThread().getName() + ": " + MDC.get("traceId"));
});
TimeUnit.SECONDS.sleep(3);
log.info(Thread.currentThread().getName() + ": " + MDC.get("traceId"));
return "success";
}
访问http://localhost:8088/run/mdc3
:
c.y.m.m.controller.MdcController : [4cd81770e58f42b8a3f40ab937ea394a]: http-nio-8088-exec-1: 4cd81770e58f42b8a3f40ab937ea394a
c.y.m.m.controller.MdcController : [4cd81770e58f42b8a3f40ab937ea394a]: task-1: 4cd81770e58f42b8a3f40ab937ea394a
c.y.m.m.controller.MdcController : [4cd81770e58f42b8a3f40ab937ea394a]: task-2: 4cd81770e58f42b8a3f40ab937ea394a
c.y.m.m.controller.MdcController : [4cd81770e58f42b8a3f40ab937ea394a]: task-1: 4cd81770e58f42b8a3f40ab937ea394a
c.y.m.m.controller.MdcController : [4cd81770e58f42b8a3f40ab937ea394a]: http-nio-8088-exec-1: 4cd81770e58f42b8a3f40ab937ea394a
可以发现,线程池中也可以传递traceId