可观测性落地:如何在 Java 项目中统一埋点 Trace ID?(一)

前言

在分布式或并发环境下,当用户反馈接口报错时,单靠时间线查找日志无异于大海捞针。由于请求是并发的,不同请求的日志会交错在一起,我们很难还原某个特定请求的完整执行链路。

为了解决这个问题,我们需要引入 Trace ID(链路追踪标识)

实现思路

  • 唯一标识:为每一个进入系统的请求分配一个全局唯一的 ID。
  • 全链路串联:通过 Trace ID,可以将从 Controller 到 Service、再到 DAO 甚至异步线程的所有日志汇聚在一起。
  • 快速排查:前端在报错时只需提供这个 ID,开发者即可在成千上万行日志中瞬间定位到问题的始末。

我们通常利用日志框架提供的 MDC(Mapped Diagnostic Context) 机制来实现。在请求进入系统时(如拦截器或过滤器中)生成 ID 并存入 MDC,后续该请求触发的所有日志都会自动携带此标识,无需手动修改原有的 Log 代码。

MDC

MDC 是一个"基于线程的日志上下文容器",它的核心原理是(以 loggack 为例):

java 复制代码
public class MDC {
    private static final ThreadLocal<Map<String, String>> contextMap;
}

每一个请求都是独立的一份 MDC 容器,并发请求不会互相污染。

常用方法

  • 设置:MDC.put("traceId", traceId);
  • 清理:MDC.remove("traceId");MDC.clear()

日志配置

通过 MDC 配置的内容想要在日志中打印,还需要我们在日志文件中进行配置,关键配置如下:

xml 复制代码
<pattern>
    [%X{traceId:--}] %d{HH:mm:ss.SSS} %-5level %logger - %msg%n
</pattern>
  • %X{key}: key 为在 MDC 中设定的key,这样在打印日志时,就会从 MDC 工具中相应的值了。
  • %X{traceId:--}: :- 后面是默认值,这里表示 traceId 不存在时打印-。(注意" :- "后面还有个" - ")

请求链路追踪过滤器

配置文件

使用配置文件,我们可以很方便的控制链路追踪的启停,同时更规范的管理使用到的 key:

java 复制代码
@Data
@ConfigurationProperties(prefix = "x-polaris.web")
public class PolarisWebProperties {

    /**
     * 链路追踪配置
     */
    private Trace trace = new Trace();

    @Data
    public static class Trace {
        /**
         * 是否开启链路追踪,默认 true
         */
        private boolean enabled = true;

        /**
         * 链路追踪 ID 的 Header 名称,默认 X-Trace-Id
         */
        private String headerName = "X-Trace-Id";

        /**
         * MDC 中的 Key 名称,默认 traceId
         */
        private String mdcKey = "traceId";
    }
}

过滤器

java 复制代码
@RequiredArgsConstructor
public class TraceIdFilter extends OncePerRequestFilter {

    private final PolarisWebProperties properties;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        
        PolarisWebProperties.Trace traceConfig = properties.getTrace();
        if (!traceConfig.isEnabled()) {
            // 未启用时,直接放行
            filterChain.doFilter(request, response);
            return;
        }

        // 获取请求头已有的 traceId,如果不存在才新建一个
        String traceId = request.getHeader(traceConfig.getHeaderName());
        if (!StringUtils.hasText(traceId)) {
            traceId = UUID.randomUUID().toString().replace("-", "");
        }

        try {
            // 记录 traceId 到 MDC,方便后续日志打印
            MDC.put(traceConfig.getMdcKey(), traceId);
            response.setHeader(traceConfig.getHeaderName(), traceId);
            filterChain.doFilter(request, response);
        } finally {
            // 移除 MDC 中的 traceId,防止内存泄漏
            MDC.remove(traceConfig.getMdcKey());
        }
    }
}

日志配置(logback)

下面是使用 logback 的简单的日志配置:

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- 定义日志格式,增加 Trace ID [%X{traceId}] -->
    <property name="CONSOLE_LOG_PATTERN" value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr([%X{traceId:--}]){yellow} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>

    <!-- 引入 Spring Boot 官方预定义的 Logback 配置 -->
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    <include resource="org/springframework/boot/logging/logback/console-appender.xml"/>

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

简单测试

现在我们启动项目,此时可以看到控制台打印了 [-],我们请求接口(接口中有日志打印的话)就能看到对应的 traceId 了,类似 [d7dc27eba473456cafcf6dfe1ff2836f]

问题

MDC 是线程独立的,那么如果请求中开启了新的线程,又怎么处理呢??

相关推荐
一点程序1 天前
基于SpringBoot的选课调查系统
java·spring boot·后端·选课调查系统
怪兽源码1 天前
基于SpringBoot的选课调查系统
java·spring boot·后端·选课调查系统
csdn_aspnet1 天前
ASP.NET Core 中的依赖注入
后端·asp.net·di·.net core
昊坤说不出的梦1 天前
【实战】监控上下文切换及其优化方案
java·后端
疯狂踩坑人1 天前
【Python版 2026 从零学Langchain 1.x】(二)结构化输出和工具调用
后端·python·langchain
橘子师兄1 天前
C++AI大模型接入SDK—ChatSDK封装
开发语言·c++·人工智能·后端
@ chen1 天前
Spring事务 核心知识
java·后端·spring
一点技术1 天前
基于SpringBoot的选课调查系统
java·spring boot·后端·选课调查系统
RANCE_atttackkk1 天前
Springboot+langchain4j的RAG检索增强生成
java·开发语言·spring boot·后端·spring·ai·ai编程
好好研究1 天前
Spring Boot - Thymeleaf模板引擎
java·spring boot·后端·thymeleaf