Loading... 在分布式系统中,一次请求可能会涉及多个服务,这就需要跟踪一次请求的整个过程,以便排查问题和优化性能。而TraceId就是用来唯一标识一次请求,从而方便跟踪和定位问题。 这里我采用的**SpringBoot版本是3.1.3**,其他的版本我没试过,**在2.X的版本当中代码可能略有不同**。 在SpringBoot中,可以通过拦截器来实现TraceId的生成和传递。具体步骤如下: ## 1. 定义TraceId工具类 用来生成唯一的TraceId。这里我使用了Hutool的[唯一ID工具-IdUtil](https://www.hutool.cn/docs/#/core/%E5%B7%A5%E5%85%B7%E7%B1%BB/%E5%94%AF%E4%B8%80ID%E5%B7%A5%E5%85%B7-IdUtil)来生成TraceId。工具类**TraceIdUtils.java**代码如下: ```java package com.dracowyn.common.utils; import cn.hutool.core.util.IdUtil; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.ObjectUtils; import org.slf4j.MDC; /** * TraceId 工具类,用于生成和管理 TraceId。 * TraceId 用于在分布式系统中进行请求追踪和日志记录,方便排查问题。 * 支持生成 TraceId、获取 TraceId、设置 TraceId、销毁 TraceId 等操作。 * * @author Dracowyn * @since 2023/9/15 13:33 */ @Log4j2 public class TraceIdUtils { // TraceId 名称 public static final String TRACE_ID = "traceId"; // TraceId 头部名称 public static final String TRACE_ID_HEADER = "X-Trace-Id"; /** * 创建 TraceId,通过 MDC 将 TraceId 放入线程上下文中。 */ public static void createTraceId() { MDC.put(TRACE_ID, generate()); } /** * 获取当前线程的 TraceId。 * * @return 当前线程的 TraceId,如果不存在则返回空字符串。 */ public static String getTraceId() { String traceId = MDC.get(TRACE_ID); return traceId == null ? "" : traceId; } /** * 从请求头部获取 TraceId。 * * @param request HTTP 请求对象。 * @return TraceId,如果不存在则返回 null。 */ public static String getTraceIdHeader(HttpServletRequest request) { return request.getHeader(TRACE_ID_HEADER); } /** * 设置当前线程的 TraceId。 * * @param traceId TraceId 值。 */ public static void setTraceId(String traceId) { if (ObjectUtils.isNotEmpty(traceId)) { MDC.put(TRACE_ID, traceId); } } /** * 销毁当前线程的 TraceId。 */ public static void destroyTraceId() { MDC.remove(TRACE_ID); } /** * 生成 TraceId。 * * @return TraceId 值。 */ public static String generate() { return IdUtil.randomUUID(); } } ``` ## 2. 定义TraceId拦截器 用来在请求进入时生成TraceId,并将TraceId传递到下游服务。**TraceIdInterceptor.java**代码如下: ```java package com.dracowyn.common.interceptor; import cn.hutool.core.util.StrUtil; import com.exam.utils.TraceIdUtils; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.log4j.Log4j2; import org.jetbrains.annotations.NotNull; import org.springframework.web.servlet.HandlerInterceptor; /** * TraceId 拦截器,用于在每个请求的头部添加或获取 TraceId,并在请求后销毁 TraceId。 * * @author Dracowyn * @since 2023/9/15 14:53 */ @Log4j2 public class TraceIdInterceptor implements HandlerInterceptor { /** * 在处理请求之前,为请求添加或获取 TraceId。 * * @param request HTTP 请求对象 * @param response HTTP 响应对象 * @param handler 处理请求的处理器对象 * @return 如果返回 false,则表示拦截器已经处理了该请求,并且不应该继续执行后续的拦截器和处理器。如果返回 true,则表示继续执行后续的拦截器和处理器。 */ @Override public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler) { try { // 从请求头中获取 TraceId String traceId = TraceIdUtils.getTraceIdHeader(request); if (StrUtil.isBlank(traceId)) { // 如果请求头中没有 TraceId,则创建一个新的 TraceId TraceIdUtils.createTraceId(); String newTraceId = TraceIdUtils.getTraceId(); request.setAttribute("traceId", newTraceId); response.setHeader(TraceIdUtils.TRACE_ID_HEADER, newTraceId); } else { // 如果请求头中有 TraceId,则使用该 TraceId TraceIdUtils.setTraceId(traceId); request.setAttribute("traceId", traceId); response.setHeader(TraceIdUtils.TRACE_ID_HEADER, traceId); } } catch (Exception e) { // 如果添加或获取 TraceId 失败,则记录错误日志 log.error("Failed to add or get TraceId", e); throw e; } // 返回 true,表示继续执行后续的拦截器和处理器 return true; } /** * 在请求处理完成后,销毁 TraceId。 * * @param request HTTP 请求对象 * @param response HTTP 响应对象 * @param handler 处理请求的处理器对象 * @param ex 处理请求时可能抛出的异常 */ @Override public void afterCompletion(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler, Exception ex) { // 销毁 TraceId TraceIdUtils.destroyTraceId(); } } ``` 在上面的代码中,请求进入时生成TraceId,并将TraceId保存到请求的属性中,以便后续使用。同时,将TraceId保存到响应头中,以便下游服务获取。 ## 3. 配置TraceId拦截器 将TraceId拦截器配置到SpringBoot中,以便拦截所有的请求。代码如下: ```java package com.dracowyn.config; import com.exam.common.interceptor.TraceIdInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * WebMvc配置 * * @author Dracowyn * @since 2023/9/15 15:22 */ @Configuration public class WebMvcConfig implements WebMvcConfigurer { @Bean public TraceIdInterceptor traceIdInterceptor() { return new TraceIdInterceptor(); } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(traceIdInterceptor()); } } ``` 通过实现WebMvcConfigurer接口,并重写addInterceptors方法来将TraceIdInterceptor注册到拦截器列表中。 ## 4.自定义logback日志格式 在src/main/resources中的**logback-spring.xml**(如果没有则新建一个)中添加TraceId配置,以便在日志中打印TraceId。代码如下: ```xml <?xml version="1.0" encoding="UTF-8"?> <configuration> <include resource="org/springframework/boot/logging/logback/defaults.xml"/> <property name="FILE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- [%X{traceId}] [%t] %-40.40logger{39} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>${FILE_LOG_PATTERN}</pattern> <charset>utf8</charset> </encoder> </appender> <root level="INFO"> <appender-ref ref="STDOUT" /> </root> </configuration> ``` 主要是`<property name="FILE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- [%X{traceId}] [%t] %-40.40logger{39} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>` 中定义日志输出的格式,包括时间、日志级别、线程 ID、TraceId、线程名、日志名称、日志信息和异常等内容,其他不太重要。 Last modification:October 1, 2023 © Allow specification reprint Support Appreciate the author AliPayWeChat Like If you think my article is useful to you, please feel free to appreciate