当前位置: 首页 > news >正文

Spring Boot中使用 MDC实现请求TraceId全链路透传

前言

在 Spring Boot 项目中排查问题时,我们通常需要查看日志。但当请求量较大时,很难区分日志属于哪一次请求。为了解决这个问题,我们可以给每个请求设置一个 traceId

使用 traceId 时,可以通过某一条日志获取对应的 traceId,然后快速查询同一请求的所有日志,从而大大提高排查效率。

本文主要介绍如何在项目中使用 Spring 自带的 MDC(Mapped Diagnostic Context) 来实现 traceId,并处理以下场景:

  • HTTP 请求
  • MQ(RabbitMQ)
  • 线程池异步任务

MDC实现原理

MDC 是 SLF4J/Logback 提供的 线程级日志上下文存储。它内部通过 ThreadLocal<Map<String, String>> 保存上下文信息。

  • 当在某个线程里执行 MDC.put("traceId", "xxx") 时,traceId 会存入当前线程的 ThreadLocal 中。
  • 日志框架在输出日志时,会自动从 MDC 中获取 traceId 并填入日志模板。
  • 不同线程的 MDC 是独立的,每个线程都有自己的上下文,不会互相干扰。

logback文件的处理

需要在logback-spring.xml文件的property标签中日志输出模板中添加traceId的变量占位符,添加内容[traceId:%X{traceId}]

    <property name="CONSOLE_LOG_PATTERN"value="${DEFAULT_CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} [traceId:%X{traceId}] %clr(${LOG_LEVEL_PATTERN:-%5p})%clr(${PID:- }){magenta} %clr(---){faint} %clr([%t]){faint} %clr(%-40.40logger{39}){cyan}%clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>

根据自己的需要添加就行,我这里的格式是:[traceId:7e01902b18ef4c2b9f49609c57d769fa]
image-20251106161246835

Http请求处理

只需要添加一个过滤器设置traceId即可

import org.slf4j.MDC;
import org.springframework.stereotype.Component;import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.util.UUID;/*** 日志traceId功能** @author: Czw* @create: 2025-11-06 10:31**/
@Component
public class TraceIdFilter implements Filter {private static final String TRACE_ID = "traceId";@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {try {String traceId = UUID.randomUUID().toString().replace("-", "");MDC.put(TRACE_ID, traceId);chain.doFilter(request, response);} finally {MDC.remove(TRACE_ID);}}
}

线程池

需要单独处理Callable和Runnable,在外面包一层。由于线程池中的线程是复用的,所以在用完之后需要在finnally中清除设置的traceId,避免影响下一次任务

/*** 异步执行** @param task 任务*/public void execute(Runnable task) {defaultThreadPoolExecutor.execute(wrap(task, MDC.getCopyOfContextMap()));}/*** 提交一个有返回值的异步任务*/public <T> Future<T> submit(Callable<T> task) {return defaultThreadPoolExecutor.submit(wrap(task, MDC.getCopyOfContextMap()));} /*** 封装 Runnable,复制 MDC 上下文*/private Runnable wrap(Runnable task, Map<String, String> contextMap) {return () -> {Map<String, String> previous = MDC.getCopyOfContextMap();if (contextMap != null) {MDC.setContextMap(contextMap);} else {MDC.clear();}try {task.run();} finally {// 恢复线程池线程原来的 MDC,避免影响下一次任务if (previous != null) {MDC.setContextMap(previous);} else {MDC.clear();}}};}/*** 封装 Callable,复制 MDC 上下文*/private <T> Callable<T> wrap(Callable<T> task, Map<String, String> contextMap) {return () -> {Map<String, String> previous = MDC.getCopyOfContextMap();if (contextMap != null) {MDC.setContextMap(contextMap);} else {MDC.clear();}try {return task.call();} finally {// 恢复线程池线程原来的 MDC,避免影响下一次任务if (previous != null) {MDC.setContextMap(previous);} else {MDC.clear();}}};}

mq(RabbitMq)

MQ的的话需要在sender时统一获取发送时的traceId,然后设置到mq的header中,然后利用Spring AMQP 提供了 RabbitListenerAdvice 机制,可以对所有消费者统一处理,不需要在每一个consumer进行处理

消息生产者处理:

    /*** 同步发送mq (不管消费者有没有消费到,发出去消息就结束)** @param typeEnum* @param message*/public <T> void sendMq(MqEnum.TypeEnum typeEnum, MqMessage<T> message) {rabbitTemplate.convertAndSend(MqEnum.Exchange.EXCHANGE_NAME, typeEnum.getRoutingKey(), message,msg -> {String traceId = MDC.get(TRACE_ID);if (traceId == null) {traceId = UUID.randomUUID().toString().replace("-", "");MDC.put(TRACE_ID, traceId);}msg.getMessageProperties().getHeaders().put(TRACE_ID, traceId);return msg;});}

利用Advice机制获取发送来的traceId然后设置到当前消费者的线程中

/*** 透传MDC* sendMq时设置MDC到header中,消费端** @return {@link Advice }* @author Czw* @date 2025/11/06*/@Beanpublic Advice traceIdAdvice() {return (MethodInterceptor) invocation -> {Object[] args = invocation.getArguments();String traceId = null;for (Object arg : args) {if (arg instanceof Message message) {traceId = (String) message.getMessageProperties().getHeaders().get(TRACE_ID);break;}}if (traceId != null) {MDC.put(TRACE_ID, traceId);}try {return invocation.proceed();} finally {MDC.remove(TRACE_ID);}};}/*** 设置自定义的traceIdAdvice** @param connectionFactory connectionFactory* @param traceIdAdvice     traceIdAdvice* @return {@link SimpleRabbitListenerContainerFactory }* @author Czw* @date 2025/11/06*/@Beanpublic SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory,Advice traceIdAdvice) {SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();factory.setConnectionFactory(connectionFactory);factory.setAdviceChain(traceIdAdvice);return factory;}

效果

测试接口

@GetMapping(value = "/test/traceId/async")
public Result<NullResult> traceId() {log.info("主 traceId");asyncExecutors.execute(() -> log.info("execute traceId"));asyncExecutors.submit(() -> {log.info("submit traceId");return "ok";});List<Runnable> list = new ArrayList<>();list.add(() -> log.info("execute list traceId"));asyncExecutors.execute(list);return Result.buildSuccess();
}@GetMapping(value = "/test/traceId/mq")
public Result<NullResult> mq() {log.info("主mq traceId");MqMessage<String> message = new MqMessage<>();message.setData(JSON.toJSONString(Collections.emptyList()));mqSender.sendMq(MqEnum.TypeEnum.PROP_SEND, message);return Result.buildSuccess();
}

请求接口时

image-20251106163929375

mq的效果,发送时的traceId与consumer中的traceId一致

image-20251106164134442

线程池的效果,四种线程池的使用时发送方与内部的traceId一致

image-20251106164319890

http://www.dtcms.com/a/577334.html

相关文章:

  • Spring + Spring Boot + Spring Cloud 常见面试题清单
  • 对接大疆上云api---实现直播效果
  • ffmpeg学习记录
  • 建站网站破解版什么是网站建设需求分析
  • Lua 面向对象编程完全指南:从元表到私密性,解锁灵活封装技巧
  • linux用户及权限管理
  • 北京手机网站建设外包WordPress里面自定义功能
  • 网站建设怎么更改图片网站服务器建设合同
  • 快速理解卷积神经网络CNN
  • IPD PDT 核心组成员来源及扩展组配置说明
  • 51项目分享:基于51单片机仓库环境检测系统
  • Vivado 2015在WIN11电脑综合一直卡在Translating synthesized netlist不动。
  • 绘制软件的状态机图
  • 基于python与streamlit构建的内网聊天应用
  • 对于数据结构:堆的超详细保姆级解析—上
  • linux网站建设论文针对网站做搜索引擎做优化
  • 基于超像素和基于图论的图像分割方法
  • 【算法训练营 · 补充】LeetCode Hot100(中)
  • 新能源网站开发网站没有做301定向
  • 【Ubuntu】新服务器配置完全指南
  • 2026年PMI-PBA商业分析师报考时间+条件全解析
  • 计算机图形学·9 几何学
  • 基于MATLAB的梯度下降法实现
  • dw制作简单网站模板下载网站建设工作会议讲话
  • 如何优化多表查询sql?
  • 64QAM信号的数字预失真处理(MATLAB实现)
  • 网站模板下载之后如何修改公司官网怎么设计
  • 崇信县门户网站留言首页杭州做商务网站
  • 只出现一次的数字 II(二)
  • Linux系统编程:(六)深入理解 Linux 软件包管理器——从原理到 yum 实战全攻略