Skip to content

业务背景

当需要在线上排查某次请求异常时,就急需一个traceId能跟踪到该异常请求的全链路。

通过接口固定传参的方式虽也可行,但未免不太优雅

MDC介绍

MDC,Mapped Diagnostic Context,映射调试上下文。底层基于ThreadLocal,MDC中的内容可以在同一线程中任何地方轻松访问,非常适合追踪请求链路。

[ThreadLocal 实现原理](/docs/Java 基础进阶/ThreadLocal 实现原理.html)

列出几个常用静态方法

  • void put(String key, String val)
    在当前线程调试上下文设置一个值
  • String get(String key)
    获取当前线程调试上下文的指定值
  • void clear()
    清空当前线程的调试上下文
  • Map<String, String> getCopyOfContextMap()
    获取当前线程调试上下文的拷贝
  • void setContextMap(Map<String, String> contextMap)
    设置当前线程的调试上下文

SpringBoot接入

编写Filter

java
@WebFilter
public class TraceFilter implements Filter {

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws IOException, ServletException {
        String traceId = UUID.randomUUID().toString().replaceAll("-", "");
        // 设置本次请求的traceId
        MDC.put("traceId", traceId);
        // 并放到response header中,便于排查问题
        ((HttpServletResponse) res).setHeader("traceId", traceId);
        filterChain.doFilter(req, res);
    }
}

注意需要开启@ServletComponentScan注解。

配置Log

增加%X{traceId},会自动从MDC上下文中获取;

xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <contextName>myAppName</contextName>
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{traceId}] [%thread] %level %logger - %msg%n</pattern>
        </encoder>
    </appender>
    <root level="info">
        <appender-ref ref="console"/>
    </root>
</configuration>

上述以logback为例,log4j等其他同理。

多线程使用

服务内部通常会使用线程池提高程序执行效率,MDC在此场景下就难以适用。

可以在任务实际执行前设置MDC与父线程保持一致,需要对Callable进行包装;

java
public abstract class TraceTask<Object> implements Callable<Object> {

    private Map<String, String> contextMap = null;

    public TraceTask() {
        // 暂存父线程的MDC上下文
        contextMap = MDC.getCopyOfContextMap();
    }

    @Override
    public Object call() throws Exception {
        // 执行前设置到当前线程中
        MDC.setContextMap(contextMap);
        return doProcess();
    }

    // 实际任务执行逻辑
    public abstract Object doProcess() throws Exception;
}

上述以callable为例,runnable同理。

基于 MIT 许可发布