前后端拦截器+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
  • 代码更优雅,日志更统一,排查问题更高效
相关推荐
2501_906150561 天前
私有部署问卷系统操作实战记录-DWSurvey
java·运维·服务器·spring·开源
better_liang1 天前
每日Java面试场景题知识点之-TCP/IP协议栈与Socket编程
java·tcp/ip·计算机网络·网络编程·socket·面试题
VX:Fegn08951 天前
计算机毕业设计|基于springboot + vue校园社团管理系统(源码+数据库+文档)
前端·数据库·vue.js·spring boot·后端·课程设计
niucloud-admin1 天前
java服务端——controller控制器
java·开发语言
To Be Clean Coder1 天前
【Spring源码】通过 Bean 工厂获取 Bean 的过程
java·后端·spring
Fortunate Chen1 天前
类与对象(下)
java·javascript·jvm
程序员水自流1 天前
【AI大模型第9集】Function Calling,让AI大模型连接外部世界
java·人工智能·llm
‿hhh1 天前
综合交通运行协调与应急指挥平台项目说明
java·ajax·npm·json·需求分析·个人开发·规格说明书
小徐Chao努力1 天前
【Langchain4j-Java AI开发】06-工具与函数调用
java·人工智能·python
无心水1 天前
【神经风格迁移:全链路压测】33、全链路监控与性能优化最佳实践:Java+Python+AI系统稳定性保障的终极武器
java·python·性能优化