需求

如果大家希望在 每次外部请求进来时,日志中都能带上一个 指定标识(例如:requestId 或 traceId),以便后续排查问题、链路追踪或日志聚合分析。


目标

每次 HTTP 请求进来后,在整个调用链路中,所有通过 log.info()log.error() 等输出的日志都自动带上一个唯一标识(如 requestId),便于追踪请求路径。


实现方式概述

你可以使用 MDC(Mapped Diagnostic Context) 来实现这个功能。MDC 是 log4j、logback 等日志框架提供的线程上下文机制,可以将一些变量绑定到当前线程中,并在日志模板中引用这些变量。

🔧 改造步骤如下:


🧱 一、修改日志格式模板(log4j.properties / logback.xml)

你需要确保日志输出格式中包含 MDC 的字段,比如 %X{requestId}

如果你使用的是 log4j.properties

log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p %c - %X{requestId} %m%n


如果你使用的是 logback.xml

<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %X{requestId} %msg%n</pattern>


这样配置之后,只要你在代码中设置了 MDC.put("requestId", "xxx"),该值就会自动出现在每条日志中。


🧱 二、在请求进入系统前设置 requestId(推荐使用拦截器)

推荐做法:创建一个 Spring 拦截器(Interceptor)

1. 创建拦截器类 RequestLogInterceptor.java

import org.slf4j.MDC;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;

public class RequestLogInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 生成唯一的 requestId
        String requestId = UUID.randomUUID().toString().replace("-", "");
        
        // 将 requestId 存入 MDC 上下文
        MDC.put("requestId", requestId);

        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 请求结束后清理 MDC,避免内存泄漏
        MDC.clear();
    }
}



🧱 三、注册拦截器

在 Spring MVC 配置文件中添加拦截器配置:

<mvc:interceptors>
    <bean class="com.yourpackage.RequestLogInterceptor"/>
</mvc:interceptors>


或者使用 Java Config 方式:

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new RequestLogInterceptor());
    }
}


🧱 四、验证效果

假设你有如下日志输出:

log.info("处理用户登录请求");


改造后日志会变成:

2024-10-18 14:23:45 [http-nio-8080-exec-1] INFO  com.example.LoginController - abcdef123456789 处理用户登录请求


其中 abcdef123456789 就是本次请求的 requestId


🧱 五、可选增强:结合 NDC 做更复杂的链路追踪(可选)

如果你要做更完整的分布式链路追踪(TraceId + SpanId),可以考虑引入:

  • Spring Cloud Sleuth + Zipkin
  • SkyWalking
  • Pinpoint

但如果是单体项目或轻量级改造,使用上面的 MDC 已经足够。


总结

步骤

内容

1️⃣

修改日志格式,加入 %X{requestId}

2️⃣

创建拦截器,在请求开始时设置 MDC.put("requestId", xxx)

3️⃣

请求结束时清理 MDC,防止线程复用导致串号

4️⃣

验证日志是否带上了 requestId


💡 补充建议

  • 可以将 requestId 返回给前端(放在响应头中),便于前后端联调。
  • 使用 UUID 以外,也可以用时间戳+随机数组合生成 requestId。
  • 如果你想让 SQL 日志也带上 requestId,可在 DatabaseUtil 中打印 SQL 时也使用 MDC.get("requestId")