java springboot 初体验 (七)对接链路追踪
- 上一篇
- java springboot 初体验 (六)添加统一的入参出参打印日志(使用切面)
- https://www.cnblogs.com/zwjvzwj/p/16612094.html
- MDC介绍
- MDC(Mapped Diagnostic Context,映射调试上下文)是 log4j 、logback及log4j2 提供的一种方便在多线程条件下记录日志的功能。MDC 可以看成是一个与当前线程绑定的哈希表,可以往其中添加键值对。MDC 中包含的内容可以被同一线程中执行的代码所访问。当前线程的子线程会继承其父线程中的 MDC 的内容。当需要记录日志时,只需要从 MDC 中获取所需的信息即可。
-
- clear() => 移除所有MDC
- get (String key) => 获取当前线程MDC中指定key的值
- getContext() => 获取当前线程MDC的MDC
- put(String key, Object o) => 往当前线程的MDC中存入指定的键值对
- remove(String key) => 删除当前线程MDC中指定的键值对
- 添加拦截器
- 拦截所有的请求,并在请求中添加traceId
-
package com.zwj.zwjproject.interceptor; import com.ctrip.framework.apollo.core.utils.StringUtils; import com.sun.istack.internal.NotNull; import lombok.extern.slf4j.Slf4j; import org.springframework.web.servlet.HandlerInterceptor; import org.slf4j.MDC; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.UUID; /** * @ClassName: LogInterceptor * @Author zhangwujie * @Date 2022/8/22 3:13 下午 * @Description: 日志拦截。统一在日志中添加链路追踪id */ @Slf4j public class TraceInterceptor implements HandlerInterceptor { // 链路中统一的id public static final String TRACE_ID = "requestId"; // 链路中每个服务的id public static final String SPAN_ID = "spanId"; public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler) { try { //如果有上层调用就用上层的ID String traceId = request.getHeader(TRACE_ID); if (StringUtils.isEmpty(traceId)) { MDC.put(TRACE_ID, UUID.randomUUID().toString()); } else { MDC.put(TRACE_ID, traceId); } MDC.put(SPAN_ID, UUID.randomUUID().toString()); } catch (Exception e) { log.error("LogInterceptor preHandle catch error msg={}", e.getMessage(), e); } return true; } public void postHandle(@NotNull HttpServletRequest httpServletRequest, @NotNull HttpServletResponse httpServletResponse, @NotNull Object o, ModelAndView modelAndView) { } public void afterCompletion(@NotNull HttpServletRequest httpServletRequest, @NotNull HttpServletResponse httpServletResponse, @NotNull Object o, Exception e) { //调用结束后删除 MDC.clear(); } }
- 注册拦截器
-
package com.zwj.zwjproject.configuration; import com.sun.istack.internal.NotNull; import com.zwj.zwjproject.interceptor.TraceInterceptor; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * @ClassName: TraceIdInterceptorConfiguration * @Author zhangwujie * @Date 2022/8/22 3:38 下午 * @Description: TraceID的拦截器\生成請求的唯一值 */ @Configuration public class TraceIdInterceptorConfiguration implements WebMvcConfigurer { /** * 添加拦截器链路的请求路径 */ private String[] addPathPatterns = new String[]{"/**"}; /** * 不添加拦截器链路的请求路径 */ private String[] excludePathPatterns = new String[]{}; @Override public void addInterceptors(@NotNull InterceptorRegistry interceptorRegistry) { // 链路追踪日志拦截器 TraceInterceptor traceInterceptor = new TraceInterceptor(); interceptorRegistry .addInterceptor(traceInterceptor) .addPathPatterns(addPathPatterns) .excludePathPatterns(excludePathPatterns); } }
-
-
添加日志格式配置
-
application.yml添加配置日志(与apollo添加配置的效果是一样的,二者配置一个就行)
logging: pattern: console: "%date{yyyy-MM-dd HH:mm:ss.SSS} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }) %highlight([requestId: %X{requestId}]) %highlight([spanId: %X{spanId}]) {magenta}%clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}"
- apollo添加配置(与application.yml添加配置的效果是一样的,二者配置一个就行)
logging.pattern.console = %date{yyyy-MM-dd HH:mm:ss.SSS} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }) %highlight([requestId: %X{requestId}]) %highlight([spanId: %X{spanId}]) {magenta}%clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}
-
- 添加一个任务适配器,用于主线程和子线程的traceId传递
-
package com.zwj.zwjproject.decorator; import org.slf4j.MDC; import org.springframework.core.task.TaskDecorator; import java.util.Map; /** * @ClassName: MdcTaskDecorator * @Author zhangwujie * @Date 2022/8/22 4:55 下午 * @Description: 任务适配器 */ public class MdcTaskDecorator implements TaskDecorator { /** * @apiNote 使异步线程池获得主线程的上下文 * @param runnable runnable * @return Runnable */ @Override public Runnable decorate(Runnable runnable) { /** * 为了线程池中的线程在复用的时候也能获得父线程的MDC中的信息, * 子线程第一次初始化的时候没事,因为通过InheritableThreadLocal * 已经可以获得MDC中的内容了 */ Map<String, String> map = MDC.getCopyOfContextMap(); return () -> { try { // 线程重用的时候,把父线程中的context map内容带入当前线程的context map中, // 因为线程已经初始化过了,不会像初始化时那样通过拷贝父线程inheritableThreadLocals到子线程 // 的inheritableThreadLocals操作来完成线程间context map的传递。 // 真正执行到这个run方法的时候,已经到了子线程中了,所以要在初始化的时候用 // MDC.getCopyOfContextMap()来获得父线程contest map,那时候还在父线程域中 MDC.setContextMap(map); runnable.run(); } finally { MDC.clear(); } }; } }
-
-
配置线程池,用于链路追踪MDC的线程池\在多线程情况下会将主线程的上下文传递给子线程
-
package com.zwj.zwjproject.configuration; import com.zwj.zwjproject.decorator.MdcTaskDecorator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import java.util.concurrent.ThreadPoolExecutor; /** * @ClassName: MdcThreadPoolConfiguration * @Author zhangwujie * @Date 2022/8/22 4:52 下午 * @Description: 用于链路追踪MDC的线程池\在多线程情况下会将主线程的上下文传递给子线程 */ @EnableAsync @Configuration public class MdcThreadPoolConfiguration { // 核心线程池数 private final int corePoolSize = 50; // 最大线程池数 private final int maxPoolSize = 200; // 任务队列的容量 private final int queueCapacity = 1000; // 非核心线程的存活时间 private final int keepAliveSeconds = 300; @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()); // 线程池对拒绝任务(无线程可用)的处理策略 // - AbortPolicy // 用于被拒绝任务的处理程序,它将抛出RejectedExecutionException。- CallerRunsPolicy // 用于被拒绝任务的处理程序,它直接在execute方法的调用线程中运行被拒绝的任务。- DiscardOldestPolicy // 用于被拒绝任务的处理程序,它放弃最旧的未处理请求,然后重试execute。- DiscardPolicy // 用于被拒绝任务的处理程序,默认情况下它将丢弃被拒绝的任务。 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); return executor; } }
-
-
启动服务
-
调用接口
-
下一篇
-
java springboot 初体验 (八)对接链路追踪
-
https://www.cnblogs.com/zwjvzwj/p/16614128.html
-
本文来自博客园,作者:zwjvzwj,转载请注明原文链接:https://www.cnblogs.com/zwjvzwj/p/16613085.html