前后端拦截器+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. 安装依赖

bash 复制代码
npm install axios nanoid
# 或
yarn add axios nanoid

2. 配置 axios 拦截器

js 复制代码
// src/utils/traceId.js
import { customAlphabet } from 'nanoid';

// 生成16位纯数字 traceId
export const generateTraceId = customAlphabet('0123456789', 16);
js 复制代码
// 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 实例

javascript 复制代码
// src/api/demo.js
import axios from '../utils/axios';

export function getHello() {
  return axios.get('/api/hello');
}

四、后端实现:Spring Boot 拦截器统一处理 traceId

1. 新建 TraceIdInterceptor

java 复制代码
// 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 {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String traceId = request.getHeader("traceId");
        if (traceId == null || traceId.isEmpty()) {
            traceId = generateServerTraceId();
        }
        // 设置到 MDC,所有日志自动带 traceId
        MDC.put("traceId", traceId);
        // 也可以设置到 request attribute,后续 Controller 需要时可用
        request.setAttribute("traceId", traceId);
        return true;
    }

    @Override
    public 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.注册拦截器

java 复制代码
// 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 {

    @Autowired
    private TraceIdInterceptor traceIdInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(traceIdInterceptor).addPathPatterns("/**");
    }
    
}

3.Controller 示例

java 复制代码
// 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 配置

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. 日志输出效果

bash 复制代码
[1234567890123456] 2024-06-11 10:00:00.123 INFO  com.example.demo.controller.HelloController - 业务处理逻辑

七、效果演示

  • 前端所有 axios 请求自动带 traceId
  • 后端所有日志自动带 traceId
  • 全链路追踪,定位问题极其方便

前端控制台示例:

bash 复制代码
[traceId=1234567890123456] GET /api/hello

后端日志示例:

bash 复制代码
[1234567890123456] 2024-06-11 10:00:00.123 INFO  com.example.demo.controller.HelloController - 业务处理逻辑

八、常见问题与建议

  1. traceId 长度:建议 12~20 位,太短有碰撞风险,太长不便于人工输入。
  2. 唯一性:nanoid 生成的 ID 唯一性很高,足够绝大多数场景。
  3. 全链路传递:如果有多级服务(如微服务),traceId 要在各服务间传递和打印。
  4. 日志格式统一:建议所有日志都加上 traceId,方便检索。
  5. 前端异常处理:可在 axios 响应拦截器中统一处理 traceId 相关异常日志。
  6. 后端异常处理:建议全局异常处理器也带上 traceId,方便排查。
  7. MDC 清理:一定要在请求结束后清理 MDC,防止线程复用导致 traceId 串日志。

九、流程图

bash 复制代码
┌──────────────┐
│  用户操作    │
└──────┬───────┘
       │
       ▼
┌──────────────┐
│ 前端 axios   │
│ 拦截器生成   │
│ 纯数字traceId│
└──────┬───────┘
       │
       ▼
┌──────────────┐
│ 发送请求     │
│ traceId放header│
└──────┬───────┘
       │
       ▼
┌──────────────┐
│ 后端拦截器   │
│ 获取/生成    │
│ traceId      │
│ 放入MDC      │
└──────┬───────┘
       │
       ▼
┌──────────────┐
│ 日志系统     │
│ 自动带traceId│
└──────────────┘

十、总结

  • 前端用 axios 拦截器自动生成并添加 traceId
  • 后端用 Spring Boot 拦截器统一处理 traceId
  • 日志集成 MDC,所有日志自动带 traceId
  • 代码更优雅,日志更统一,排查问题更高效
相关推荐
掘金安东尼几秒前
革新Web部署:Amazon Amplify Hosting!
后端·面试·github
调试人生的显微镜22 分钟前
iOS App性能测试工具全解析:开发者必备的实战工具指南
后端
安思派Anspire22 分钟前
LangGraph + MCP + Ollama:构建强大代理 AI 的关键(二)
人工智能·后端·python
找不到、了31 分钟前
分布式理论:CAP、Base理论
java·分布式
天天摸鱼的java工程师33 分钟前
2025已过半,Java就业大环境究竟咋样了?
java·后端
人生在勤,不索何获-白大侠38 分钟前
day16——Java集合进阶(Collection、List、Set)
java·开发语言
货拉拉技术38 分钟前
OceanBase向量检索在货拉拉的探索和实践
后端
Zedthm44 分钟前
LeetCode1004. 最大连续1的个数 III
java·算法·leetcode
艺杯羹1 小时前
MyBatis之核心对象与工作流程及SqlSession操作
java·mybatis
转转技术团队1 小时前
多代理混战?用 PAC(Proxy Auto-Config) 优雅切换代理场景
前端·后端·面试