微服务项目的traceId是很普遍的,后来因为工作原因业务逐渐转向单体项目,起初认为单体项目是不需要traceId的,因为请求只在一个服务里面流转,没必要,但是在工作中慢慢发现还是蛮有必要的,因为工作中经常会有以下场景:
测试:我提交订单失败了
我:什么时候操作的?点的哪个按钮?有什么参数?参数发我一下。 …
麻烦且效率低下,如果加上了traceId,只需要拿到报错时的traceId,就可以拿到所有报错信息,简洁快速
怎么给项目加上traceId呢?
技术方案:MDC(Mapped Diagnostic Context)
MDC
是 Logback
/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,所以最好是一开始就在拦截器中加上