MDC实现日志链路追踪
MDC是基于@Slf4j的
MDC是什么:(简单理解)线程上下文
日志链路追踪解决了什么:1:增强了代码的调试机制2:重点:实现了 多线程环境下 代码链路追踪
基础版本(不涉及异步)
1:引入日志依赖(slf4j的)
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId>
</dependency>
2:引入日志配置
📎logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.6.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.wm.file</groupId><artifactId>ExportExcelData</artifactId><version>1.0-SNAPSHOT</version><dependencies><!--Spring Boot 实现日志链路追踪,无需引入组件,让日志定位更方便!--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></dependency><dependency><groupId>cn.afterturn</groupId><artifactId>easypoi-annotation</artifactId><version>4.1.0</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.24</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>transmittable-thread-local</artifactId><version>2.14.2</version></dependency><!--测试--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.1</version><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>
3:编写拦截器,基于MDC给请求添加TriceID
public class LogInterceptor implements HandlerInterceptor {private static final String TRACE_ID = "TRACE_ID";@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {String tid = UUID.randomUUID().toString().replace("-", "");//可以考虑让客户端传入链路ID,但需保证一定的复杂度唯一性;如果没使用默认UUID自动生成if (!StringUtils.isEmpty(request.getHeader("TRACE_ID"))) {tid = request.getHeader("TRACE_ID");}MDC.put(TRACE_ID, tid);return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,@Nullable Exception ex) {MDC.remove(TRACE_ID);}}
原理详解
基础版问题(异步导致子线程triceID失效)
不涉及异步正常打印trice
异步方式会导致triceID失效
优化版:线程池解决异步问题(多加个线程池)
@Configuration
public class WebConfigurerAdapter implements WebMvcConfigurer {@Beanpublic LogInterceptor logInterceptor() {return new LogInterceptor();}@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(logInterceptor()).addPathPatterns("/**");//可以具体制定哪些需要拦截,哪些不拦截,其实也可以使用自定义注解更灵活完成// .excludePathPatterns("/testxx.html");}
}
package com.wm.file.config;import org.slf4j.MDC;import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;public final class ThreadMdcUtil {private static final String TRACE_ID = "TRACE_ID";// 获取唯一性标识public static String generateTraceId() {return UUID.randomUUID().toString();}public static void setTraceIdIfAbsent() {if (MDC.get(TRACE_ID) == null) {MDC.put(TRACE_ID, generateTraceId());}}/*** 用于父线程向线程池中提交任务时,将自身MDC中的数据复制给子线程** @param callable* @param context* @param <T>* @return*/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();}};}/*** 用于父线程向线程池中提交任务时,将自身MDC中的数据复制给子线程** @param runnable* @param context* @return*/public static Runnable wrap(final Runnable runnable, final Map<String, String> context) {return () -> {if (context == null) {MDC.clear();} else {MDC.setContextMap(context);}setTraceIdIfAbsent();try {runnable.run();} finally {MDC.clear();}};}
}
package com.wm.file.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;import java.util.concurrent.Executor;@Configuration
@EnableAsync
public class ThreadPoolConfig {/*** 声明一个线程池** @return 执行器*/@Bean("MyExecutor")public Executor asyncExecutor() {MyThreadPoolTaskExecutor executor = new MyThreadPoolTaskExecutor();//核心线程数5:线程池创建时候初始化的线程数executor.setCorePoolSize(5);//最大线程数5:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程executor.setMaxPoolSize(5);//缓冲队列500:用来缓冲执行任务的队列executor.setQueueCapacity(500);//允许线程的空闲时间60秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁executor.setKeepAliveSeconds(60);//线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池executor.setThreadNamePrefix("asyncJCccc");executor.initialize();return executor;}
}
@Configuration
public class WebConfigurerAdapter implements WebMvcConfigurer {@Beanpublic LogInterceptor logInterceptor() {return new LogInterceptor();}@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(logInterceptor()).addPathPatterns("/**");//可以具体制定哪些需要拦截,哪些不拦截,其实也可以使用自定义注解更灵活完成// .excludePathPatterns("/testxx.html");}
}
加入这四个线程操作文件,
异步执行父线程将triceID传递给子线程