一个简单的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 等字段。