一个简单的springboot日志链路追踪demo。
思路:filter生成traceId,然后埋点到MDC和ThreadLocal,在日志和接口记录traceId。

生成traceId

使用阿里的sofa生成traceId。

1
2
3
4
<dependency>
<groupId>com.alipay.sofa</groupId>
<artifactId>tracer-sofa-boot-starter</artifactId>
</dependency>

埋点

创建一个filter生成traceId,埋点到MDC和ThreadLocal。这里注意要使用TransmittableThreadLocal,能传递到其他线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class WebFilter extends GenericFilterBean {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
initContext((HttpServletRequest) servletRequest);
mdc();
filterChain.doFilter(servletRequest, servletResponse);
}

private void mdc() {
MDC.put("traceId", ContextUtils.get().getTraceId());
}

private String getTraceId(HttpServletRequest request) {
return Optional.ofNullable(SofaTraceContextHolder.getSofaTraceContext())
.map(SofaTraceContext::getCurrentSpan)
.map(SofaTracerSpan::getSofaTracerSpanContext)
.map(context -> context.getTraceId())
.orElse(UUID.randomUUID().toString());
}

private void initContext(HttpServletRequest request) {
String traceId = getTraceId(request);
Context context = new Context();
context.setTraceId(traceId);
ContextUtils.set(context);
}

}

日志打印

logback配置打印出traceId

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!-- 控制台输出 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %X{traceId} %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 文件输出 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app-all.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/app.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>15</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %X{traceId} %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
</configuration>

接口出参

使用ResponseBodyAdvice进行拦截,在返回值中设置traceId。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RestControllerAdvice
public class GlobalResponseBodyAdvice implements ResponseBodyAdvice<ResponseData> {

@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return returnType.getParameterType().equals(ResponseData.class);
}

@Override
public ResponseData beforeBodyWrite(ResponseData body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
body.setTraceId(ContextUtils.get().getTraceId());
return body;
}
}

总结

这样就是一个简单的demo,实现了从请求到日志再到接口返回,通过traceId进行链路追踪。

补充

http请求traceId如何实现从一个服务传递到另一个?

发起请求时

设置traceId到请求头。
以 RestTemplate 为例,RestTemplateInterceptor 会自动设置上下文到请求头。

1
RestTemplate restTemplate =SofaTracerRestTemplateBuilder.buildRestTemplate();

接收请求时

SpringMvcSofaTracerFilter 会从请求头中提取上下文信息。
具体方法是 getSpanContextFromRequest() 从请求头中读取 X-B3-TraceId 等字段。