微服务项目的traceId是很普遍的,后来因为工作原因业务逐渐转向单体项目,起初认为单体项目是不需要traceId的,因为请求只在一个服务里面流转,没必要,但是在工作中慢慢发现还是蛮有必要的,因为工作中经常会有以下场景:

测试:我提交订单失败了

我:什么时候操作的?点的哪个按钮?有什么参数?参数发我一下。 …

麻烦且效率低下,如果加上了traceId,只需要拿到报错时的traceId,就可以拿到所有报错信息,简洁快速

怎么给项目加上traceId呢?

技术方案:MDC(Mapped Diagnostic Context)

MDCLogback/Log4j2 提供的线程上下文日志工具,在请求开始时生成 Trace ID 并放入 MDC,结束时清除,然后在日志模板中加入 %X{traceId} 即可自动输出

第一步:修改logback.xml文件

<configuration>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            // 加上这里的[%X{traceId:-}]
            <pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%thread] [%X{traceId:-}] [%-5level] %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
    </root>
</configuration>

第二步:创建 MDC 拦截器(生成/清除 Trace ID)

@Component
public class TraceIdInterceptor implements HandlerInterceptor {

    private static final String TRACE_ID_KEY = "traceId";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 优先使用请求头中的 traceId
        String traceId = request.getHeader("X-Trace-Id");
        if (traceId == null || traceId.isEmpty()) {
            traceId = UUID.randomUUID().toString().replace("-", ""); // 生成32位唯一ID
        }
        MDC.put(TRACE_ID_KEY, traceId);
        
        // 将 traceId 放入 response header
        response.setHeader("X-Trace-Id", traceId);
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        MDC.clear(); // 必须清除,防止线程复用导致串 traceId
    }
}

第三步:注册拦截器

@Configuration
public class WebConfig implements WebMvcConfigurer {
    private final TraceIdInterceptor traceIdInterceptor;

    public ResourcesConfig(TraceIdInterceptor traceIdInterceptor) {
        this.traceIdInterceptor = traceIdInterceptor;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(traceIdInterceptor)
                .addPathPatterns("/**");
    }
}

为什么要在拦截器中加上traceId,在AOP中加行不行

首先要明确Spring MVC中一个http请求的完整请求流程:

HTTP 请求
Filter (Servlet 层)
DispatcherServlet
拦截器 preHandle()
Controller 方法
AOP Around / Before
Service 方法
AOP After / AfterReturning
拦截器 postHandle()
视图渲染
拦截器 afterCompletion()
HTTP 响应

如果在AOP中加traceId,即使是Around / Before,这个时候controller已经开始执行了,如果controller第一行代码就开始打印日志,但AOP还没有执行,那这条日志就没有traceId,所以最好是一开始就在拦截器中加上