前后端拦截器+MDC实现纯数字 traceId 全链路日志追踪(axios + Spring Boot 超详细实战)
前后端拦截器+MDC实现纯数字 traceId 全链路日志追踪(axios + Spring Boot 超详细实战)
前言
在现代前后端分离的项目中,traceId(追踪 ID) 是定位线上问题、串联日志链路的利器。合理设计和自动化传递 traceId,可以极大提升排查效率和开发体验。本文将手把手教你如何用 axios 和 Spring Boot 的拦截器,在前后端自动生成、传递和打印纯数字 traceId,并结合日志框架+MDC,实现真正的全链路日志追踪。
一、什么是 traceId?为什么要用 traceId?
traceId 是一次请求在系统中流转的唯一标识。无论是前端页面操作、后端接口调用,还是微服务之间的链路追踪,traceId 都能把相关日志串联起来。
主要作用
- 快速定位问题:出错时只需查 traceId,就能串联起前后端、数据库、第三方服务的所有日志。
- 方便检索:纯数字 traceId 在日志平台、数据库、监控系统中检索更友好。
- 开发调试:本地调试、线上排查都能一键定位。
二、技术选型与方案设计
1. 纯数字 traceId 的优势
- 易于检索:在 Kibana、ELK、数据库等平台,纯数字比字母+符号更好查找。
- 便于人工输入:有时候需要手动输入 traceId 检索,纯数字更友好。
- 兼容性好:部分系统或中间件对 header 字段有字符集限制,纯数字更保险。
2. 技术选型
- 前端:用 nanoid 生成纯数字 traceId,axios 拦截器自动添加到每个请求头。
- 后端:Spring Boot 拦截器统一获取 traceId,自动打印到日志,必要时生成后端 traceId。
- 日志框架+MDC:用 logback/log4j2 等日志框架,结合 MDC(Mapped Diagnostic Context),让所有日志自动带上 traceId。
三、前端实现:axios 拦截器自动添加 traceId
1. 安装依赖
npm install axios nanoid
# 或
yarn add axios nanoid
2. 配置 axios 拦截器
// src/utils/traceId.js
import { customAlphabet } from 'nanoid';// 生成16位纯数字 traceId
export const generateTraceId = customAlphabet('0123456789', 16);
// src/utils/axios.js
import axios from 'axios';
import { generateTraceId } from './traceId';// 创建 axios 实例
const instance = axios.create({// baseURL: 'http://your-api-url.com', // 可配置timeout: 10000
});// 请求拦截器:自动添加 traceId
instance.interceptors.request.use(config => {const traceId = generateTraceId();config.headers['traceId'] = traceId;// 也可以在这里打印日志,方便调试console.log(`[traceId=${traceId}] ${config.method?.toUpperCase()} ${config.url}`);// 可选:把 traceId 挂到 config 方便响应拦截器用config.traceId = traceId;return config;
}, error => {return Promise.reject(error);
});// 响应拦截器:可统一处理 traceId 相关逻辑
instance.interceptors.response.use(response => {// 可选:打印响应日志// console.log(`[traceId=${response.config.traceId}] 响应:`, response.data);return response;
}, error => {// 可选:打印错误日志// if (error.config && error.config.traceId) {// console.error(`[traceId=${error.config.traceId}] 请求出错:`, error);// }return Promise.reject(error);
});export default instance;
3. 使用 axios 实例
// src/api/demo.js
import axios from '../utils/axios';export function getHello() {return axios.get('/api/hello');
}
四、后端实现:Spring Boot 拦截器统一处理 traceId
1. 新建 TraceIdInterceptor
// src/main/java/com/example/demo/interceptor/TraceIdInterceptor.java
package com.example.demo.interceptor;import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;@Component
public class TraceIdInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {String traceId = request.getHeader("traceId");if (traceId == null || traceId.isEmpty()) {traceId = generateServerTraceId();}// 设置到 MDC,所有日志自动带 traceIdMDC.put("traceId", traceId);// 也可以设置到 request attribute,后续 Controller 需要时可用request.setAttribute("traceId", traceId);return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {// 请求结束后清理 MDC,防止线程复用污染MDC.remove("traceId");}private String generateServerTraceId() {long timestamp = System.currentTimeMillis();int random = (int)(Math.random() * 1000000);return String.format("%d%06d", timestamp, random);}
}
2.注册拦截器
// src/main/java/com/example/demo/config/WebConfig.java
package com.example.demo.config;import com.example.demo.interceptor.TraceIdInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class WebConfig implements WebMvcConfigurer {@Autowiredprivate TraceIdInterceptor traceIdInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(traceIdInterceptor).addPathPatterns("/**");}}
3.Controller 示例
// src/main/java/com/example/demo/controller/HelloController.java
package com.example.demo.controller;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;@RestController
public class HelloController {private static final Logger logger = LoggerFactory.getLogger(HelloController.class);@GetMapping("/api/hello")public String hello(HttpServletRequest request) {String traceId = (String) request.getAttribute("traceId");// 业务日志logger.info("业务处理逻辑");return "{\"code\":0, \"msg\":\"ok\", \"traceId\":\"" + traceId + "\"}";}}
五、什么是 MDC?为什么要用 MDC?
1. MDC(Mapped Diagnostic Context)简介
MDC(映射诊断上下文)是日志框架(如 logback、log4j2)提供的一种机制,可以为当前线程绑定一些上下文信息(如 traceId、userId、ip 等),这些信息会自动出现在日志输出中。
主要特点
- 线程隔离:MDC 绑定在当前线程,不会串日志,适合 Web 请求等多线程场景。
- 自动注入:只需在请求入口(如拦截器)设置一次 traceId,后续所有日志都自动带上 traceId。
- 日志格式统一:所有日志(包括第三方库、异常栈等)都能自动带上 traceId,方便检索和分析。
2. MDC 的好处
- 自动化:避免每条日志手动拼接 traceId,减少遗漏和冗余代码。
- 统一性:日志格式统一,便于团队协作和平台检索。
- 易于检索:方便后续用 ELK、Kibana、Sentry 等平台检索和分析。
- 线程安全:MDC 绑定在当前线程,不会出现 traceId 串日志的问题。
3. 为什么不用手动拼接 traceId?
- 手动拼接容易遗漏,代码冗余。
- 用 MDC 后,日志格式统一,所有日志都能自动带上 traceId。
- 只需在请求入口设置一次 traceId,后续所有日志都自动带上。
六、日志框架配置 traceId(以 logback 为例)
1. logback-spring.xml 配置
<!-- src/main/resources/logback-spring.xml -->
<configuration><appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"><encoder><!-- %X{traceId} 就是 MDC 里的 traceId --><pattern>[%X{traceId}] %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger{36} - %msg%n</pattern></encoder></appender><root level="INFO"><appender-ref ref="STDOUT"/></root>
</configuration>
2. 日志输出效果
[1234567890123456] 2024-06-11 10:00:00.123 INFO com.example.demo.controller.HelloController - 业务处理逻辑
七、效果演示
- 前端所有 axios 请求自动带 traceId
- 后端所有日志自动带 traceId
- 全链路追踪,定位问题极其方便
前端控制台示例:
[traceId=1234567890123456] GET /api/hello
后端日志示例:
[1234567890123456] 2024-06-11 10:00:00.123 INFO com.example.demo.controller.HelloController - 业务处理逻辑
八、常见问题与建议
- traceId 长度:建议 12~20 位,太短有碰撞风险,太长不便于人工输入。
- 唯一性:nanoid 生成的 ID 唯一性很高,足够绝大多数场景。
- 全链路传递:如果有多级服务(如微服务),traceId 要在各服务间传递和打印。
- 日志格式统一:建议所有日志都加上 traceId,方便检索。
- 前端异常处理:可在 axios 响应拦截器中统一处理 traceId 相关异常日志。
- 后端异常处理:建议全局异常处理器也带上 traceId,方便排查。
- MDC 清理:一定要在请求结束后清理 MDC,防止线程复用导致 traceId 串日志。
九、流程图
┌──────────────┐
│ 用户操作 │
└──────┬───────┘│▼
┌──────────────┐
│ 前端 axios │
│ 拦截器生成 │
│ 纯数字traceId│
└──────┬───────┘│▼
┌──────────────┐
│ 发送请求 │
│ traceId放header│
└──────┬───────┘│▼
┌──────────────┐
│ 后端拦截器 │
│ 获取/生成 │
│ traceId │
│ 放入MDC │
└──────┬───────┘│▼
┌──────────────┐
│ 日志系统 │
│ 自动带traceId│
└──────────────┘
十、总结
- 前端用 axios 拦截器自动生成并添加 traceId
- 后端用 Spring Boot 拦截器统一处理 traceId
- 日志集成 MDC,所有日志自动带 traceId
- 代码更优雅,日志更统一,排查问题更高效