SpringBoot项目中MDC使用介绍
一、概述
MDC(Mapped Diagnostic Context,映射诊断上下文)默认集成到spring-boot-starter-web项目中,只要引入spring-boot-starter-web依赖即可,可用于分布式链路追踪和日志关联。每个HTTP请求会自动分配唯一的traceId,追踪整个请求链路。
二、快速运用
1. 引入web依赖
<!--web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2. 参数外部化配置
- application.yml参数配置示例:
mdc:
enabled: true # 启用MDC
trace-id-header: X-Trace-Id # 请求头名称
include-trace-id-in-response: true # 响应头返回traceId
response-trace-id-header: X-Trace-Id # 响应头中traceId的名称
- 自定义属性配置类示例:
package com.yotexs.bt.enterprise.ledger.config;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* MDC配置属性类
* 用于配置MDC相关的属性,如traceId请求头名称等
* @author cloud
* {@code @date} 2026-01-21
*/
@Getter
@Setter
@ConfigurationProperties(prefix = "mdc")
public class MdcProperties {
/**
* 是否启用MDC过滤器
* 默认:true
*/
private boolean enabled = true;
/**
* traceId请求头名称
* 默认:X-Trace-Id
*/
private String traceIdHeader = "X-Trace-Id";
/**
* 是否在响应头中返回traceId
* 默认:true
*/
private boolean includeTraceIdInResponse = true;
/**
* 响应头中traceId的名称
* 默认:X-Trace-Id
*/
private String responseTraceIdHeader = "X-Trace-Id";
}
3. 封装MDC工具类、过滤器
- 封装MDC工具类示例:
package com.yotexs.bt.enterprise.ledger.utils;
import org.slf4j.MDC;
import java.util.UUID;
/**
* MDC工具类
* 用于管理Mapped Diagnostic Context(映射诊断上下文)
* 提供traceId的设置、获取和清理功能
* @author cloud
* {@code @date} 2026-01-21
*/
public class MdcUtils {
/**
* MDC中traceId的键名
*/
public static final String TRACE_ID_KEY = "traceId";
/**
* 私有构造函数,防止实例化
*/
private MdcUtils() {
}
/**
* 生成新的traceId
* 使用简化UUID(去除连字符),长度为32字符
* @return traceId字符串
*/
public static String generateTraceId() {
return UUID.randomUUID().toString().replace("-", "");
}
/**
* 设置traceId到MDC
* @param traceId traceId字符串
*/
public static void setTraceId(String traceId) {
if (traceId != null && !traceId.isEmpty()) {
MDC.put(TRACE_ID_KEY, traceId);
}
}
/**
* 获取当前线程的traceId
* @return traceId字符串,如果不存在则返回null
*/
public static String getTraceId() {
return MDC.get(TRACE_ID_KEY);
}
/**
* 从请求头获取或生成traceId并设置到MDC
* 优先使用请求头中的traceId,如果不存在则生成新的
* @param traceId 请求头中的traceId,可为null
* @return 最终使用的traceId
*/
public static String setTraceIdFromRequest(String traceId) {
String finalTraceId = traceId;
// 如果请求头中没有traceId,则生成新的
if (finalTraceId == null || finalTraceId.isEmpty()) {
finalTraceId = generateTraceId();
}
// 设置到MDC
setTraceId(finalTraceId);
return finalTraceId;
}
/**
* 清理MDC中的traceId
* 必须在请求处理完成后调用,防止线程池复用时数据污染
*/
public static void clear() {
MDC.remove(TRACE_ID_KEY);
}
/**
* 清理MDC中所有键值对
* 用于彻底清理MDC上下文
*/
public static void clearAll() {
MDC.clear();
}
}
- 添加拦截器,拦截所有请求,并给每一个请求设置或从请求头获取唯一的traceId,用于日志追踪;响应时按配置是否响应traceId,实现追踪延续:
package com.yotexs.bt.enterprise.ledger.filter;
import com.yotexs.bt.enterprise.ledger.config.MdcProperties;
import com.yotexs.bt.enterprise.ledger.utils.MdcUtils;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
/**
* MDC过滤器
* 负责为每个请求设置traceId,用于分布式链路追踪和日志关联
* 优先级设置为最高(0),确保在所有其他过滤器之前执行
* @author cloud
* {@code @date} 2026-01-21
*/
@Component
public class MdcFilter extends OncePerRequestFilter {
private final MdcProperties mdcProperties;
public MdcFilter(MdcProperties mdcProperties) {
this.mdcProperties = mdcProperties;
}
@Override
protected boolean shouldNotFilter(@NonNull HttpServletRequest request) {
// 如果MDC被禁用,则跳过此过滤器
return !mdcProperties.isEnabled();
}
@Override
protected void doFilterInternal(@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain)
throws ServletException, IOException {
try {
// 1. 从请求头获取traceId,如果不存在则生成新的
String traceId = request.getHeader(mdcProperties.getTraceIdHeader());
traceId = MdcUtils.setTraceIdFromRequest(traceId);
// 2. 如果配置了在响应头中返回traceId,则添加到响应头
if (mdcProperties.isIncludeTraceIdInResponse()) {
response.setHeader(mdcProperties.getResponseTraceIdHeader(), traceId);
}
// 3. 继续过滤器链
filterChain.doFilter(request, response);
} finally {
// 4. 清理MDC,防止线程池复用时数据污染
MdcUtils.clear();
}
}
}
4. logback-spring.xml配置
- 日志格式中需确保日志格式中包含
%X{traceId}:
<!-- appender是configuration的子节点,是负责写日志的组件 -->
<!-- ConsoleAppender:把日志输出到控制台 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!-- 默认情况下,每个日志事件都会立即刷新到基础输出流。这种默认方法更安全,因为如果应用程序在没有正确关闭appender的情况下退出,则日志事件不会丢失 -->
<!-- 但为了显着增加日志记录吞吐量,可以将immediateFlush属性设置为false -->
<immediateFlush>true</immediateFlush>
<encoder>
<!--[TRACEID:%X{traceId}]:用于日志线程跟踪,结合MDC(映射诊断上下文)工具-->
<!-- %37():如果字符没有37个字符长度,则左侧用空格补齐 -->
<!-- %-37():如果字符没有37个字符长度,则右侧用空格补齐 -->
<!-- %15.15():如果记录的线程字符长度小于15(第一个)则用空格在左侧补齐,如果字符长度大于15(第二个),则从开头开始截断多余的字符 -->
<!-- %msg:日志打印详情 -->
<!--%cyan():颜色转换词,用于在控制台中区分不同的日志部分-->
<!--:%line:前面的:为分隔符,%line表示会输出日志代码所在的行号-->
<!-- %-40.40():-表示左对齐(默认是右对齐)如果记录的logger字符长度小于40(第一个)则用空格在右侧补齐,如果字符长度大于40(第二个),则从开头开始截断多余的字符 -->
<!-- %n:换行符 -->
<!-- %highlight():转换说明符以粗体红色显示其级别为ERROR的事件,红色为WARN,BLUE为INFO,以及其他级别的默认颜色 -->
<pattern>[TRACEID:%X{traceId}] %d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level)-[%15.15(%thread)] %cyan(%-40.40(%logger{40}):%line): %msg%n</pattern>
<!-- 控制台也要使用UTF-8,不要使用GBK,否则会中文乱码 -->
<charset>UTF-8</charset>
</encoder>
</appender>
5. 在代码中使用
@RestController
@Slf4j
public class UserController {
@GetMapping("/user/{id}")
public Result<User> getUser(@PathVariable Long id) {
// 直接记录日志,traceId会自动包含
log.info("查询用户,userId={}", id);
User user = userService.getById(id);
return Result.success(user);
}
}
6. 查看日志
[TRACEID:abc123def456abc123def456abc123] 2026-01-22 15:00:00.000 INFO [http-nio-8080-exec-1] c.y.b.e.l.controller.UserController:25: 查询用户,userId=1
三、功能特性
- 自动traceId生成:以UUID为例子,采用去除下划线的UUID,每个请求自动生成32字符的唯一traceId;
- 请求头传递:支持从HTTP请求头获取traceId,便于跨服务追踪;
- 响应头返回:自动在响应头中返回traceId;
- 日志集成:所有日志自动包含traceId;
- 线程安全:使用finally块确保MDC清理,防止线程池复用污染。
四、核心组件
1. MdcFilter过滤器
作用:拦截所有HTTP请求,为每个请求设置traceId
@Component
public class MdcFilter extends OncePerRequestFilter {
private final MdcProperties mdcProperties;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) {
try {
// 获取或生成traceId
String traceId = MdcUtils.setTraceIdFromRequest(
request.getHeader(mdcProperties.getTraceIdHeader())
);
// 设置响应头
if (mdcProperties.isIncludeTraceIdInResponse()) {
response.setHeader(mdcProperties.getResponseTraceIdHeader(), traceId);
}
filterChain.doFilter(request, response);
} finally {
MdcUtils.clear(); // 清理MDC(关键!)
}
}
}
2. MdcUtils工具类
public class MdcUtils {
public static final String TRACE_ID_KEY = "traceId";
// 生成新的traceId
public static String generateTraceId() {
return UUID.randomUUID().toString().replace("-", "");
}
// 获取当前线程的traceId
public static String getTraceId() {
return MDC.get(TRACE_ID_KEY);
}
// 从请求头获取或生成traceId
public static String setTraceIdFromRequest(String traceId) {
String finalTraceId = (traceId == null || traceId.isEmpty())
? generateTraceId()
: traceId;
MDC.put(TRACE_ID_KEY, finalTraceId);
return finalTraceId;
}
// 清理MDC
public static void clear() {
MDC.remove(TRACE_ID_KEY);
}
}
3. 配置类
@Getter
@Setter
@ConfigurationProperties(prefix = "mdc")
public class MdcProperties {
private boolean enabled = true;
private String traceIdHeader = "X-Trace-Id";
private boolean includeTraceIdInResponse = true;
private String responseTraceIdHeader = "X-Trace-Id";
}
4. 过滤器配置类
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<MdcFilter> mdcFilterRegistration() {
FilterRegistrationBean<MdcFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(mdcFilter);
registration.setName("mdcFilter");
registration.setOrder(0); // 最高优先级
registration.addUrlPatterns("/*");
return registration;
}
}
五、完整示例展示
1. 完整示例:
- Controller层:
@RestController
@RequestMapping("/api/demo")
@RequiredArgsConstructor
@Slf4j
public class DemoController {
private final DemoService demoService;
@GetMapping("/hello")
public Result<String> hello() {
log.info("收到hello请求");
String result = demoService.processHello();
log.info("处理完成,返回结果:{}", result);
return Result.success(result);
}
@GetMapping("/user/{id}")
public Result<User> getUser(@PathVariable Long id) {
log.info("查询用户,userId={}", id);
User user = demoService.getUserById(id);
if (user == null) {
log.warn("用户不存在,userId={}", id);
return Result.error("用户不存在");
}
return Result.success(user);
}
}
- Service层:
@Service
@RequiredArgsConstructor
@Slf4j
public class DemoService {
private final UserMapper userMapper;
public String processHello() {
log.info("Service层处理hello请求");
return "Hello World";
}
public User getUserById(Long id) {
log.info("开始查询数据库,userId={}", id);
User user = userMapper.selectById(id);
if (user != null) {
log.info("数据库查询成功,userId={}", id);
} else {
log.warn("数据库中未找到用户,userId={}", id);
}
return user;
}
}
- 日志输出:
[TRACEID:a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d] 2026-01-22 15:00:00.001 INFO [http-nio-8080-exec-1] c.y.b.e.l.controller.DemoController:25: 收到hello请求
[TRACEID:a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d] 2026-01-22 15:00:00.002 INFO [http-nio-8080-exec-1] c.y.b.e.l.service.DemoService:18: Service层处理hello请求
[TRACEID:a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d] 2026-01-22 15:00:00.003 INFO [http-nio-8080-exec-1] c.y.b.e.l.controller.DemoController:27: 处理完成,返回结果:Hello World
- HTTP响应:
HTTP/1.1 200 OK
X-Trace-Id: a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d
Content-Type: application/json
{
"code": 200,
"message": "success",
"data": "Hello World"
}
2. 获取traceId
// 获取当前请求的traceId
String traceId = MdcUtils.getTraceId();
log.info("当前请求的traceId: {}", traceId);
3. 异常处理中使用
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public Result<Void> handleBusinessException(BusinessException e) {
String traceId = MdcUtils.getTraceId();
log.error("业务异常,traceId={}, message={}", traceId, e.getMessage(), e);
return Result.error(e.getMessage());
}
}
六、常见问题
1. 日志中没有traceId
-
原因:MDC过滤器未注册或配置错误;
-
解决:
-
检查
mdc.enabled=true; -
检查FilterConfig配置;
-
检查logback-spring.xml中是否有
%X{traceId}占位符。
-
2. 异步任务中没有traceId
-
原因:新线程未传递MDC上下文;
-
解决:配置TaskDecorator。
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(50);
executor.setTaskDecorator(new MdcTaskDecorator());
executor.initialize();
return executor;
}
}
public class MdcTaskDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
Map<String, String> contextMap = MDC.getCopyOfContextMap();
return () -> {
try {
if (contextMap != null) {
MDC.setContextMap(contextMap);
}
runnable.run();
} finally {
MDC.clear();
}
};
}
}
3. 跨服务调用traceId丢失
-
原因:未配置拦截器传递traceId;
-
解决:
- RestTemplate:
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setInterceptors(List.of((request, body, execution) -> {
String traceId = MdcUtils.getTraceId();
if (traceId != null) {
request.getHeaders().set("X-Trace-Id", traceId);
}
return execution.execute(request, body);
}));
return restTemplate;
}
}
- Feign:
@Configuration
public class FeignConfig {
@Bean
public RequestInterceptor traceIdInterceptor() {
return template -> {
String traceId = MdcUtils.getTraceId();
if (traceId != null && !traceId.isEmpty()) {
template.header("X-Trace-Id", traceId);
}
};
}
}
4. 定时任务中没有traceId
-
原因:定时任务未生成traceId;
-
解决:
@Component
public class ScheduledTask {
@Scheduled(cron = "0 0 2 * * ?")
public void dailyTask() {
String traceId = MdcUtils.generateTraceId();
MdcUtils.setTraceId(traceId);
try {
log.info("开始执行定时任务");
// 业务逻辑
} finally {
// 注意清理,否则会内存泄漏
MdcUtils.clear();
}
}
}
5. traceId在不同请求中相同
-
原因:线程池复用线程时,未清理MDC,导致traceId相同;
-
解决:
-
确保所有线程池都配置了TaskDecorator;
-
确保异步线程在finally块中清理MDC;
-
检查是否有System.exit()导致finally跳过。
-
七、注意事项
1. 线程池复用问题
- MDC是线程本地变量,使用线程池时必须清理:
// 正确做法
public void asyncMethod() {
String traceId = MdcUtils.getTraceId();
MdcUtils.setTraceId(traceId);
try {
// 业务逻辑
} finally {
MdcUtils.clear(); // 务必清理
}
}
2. 异步任务传递traceId
- 推荐使用TaskDecorator自动传递MDC上下文,避免手动操作;
3. 内存泄漏
- 务必在finally块中清理MDC,防止内存泄漏;
4. 性能影响
- MDC对性能影响很小,可以放心使用。避免频繁调用
MdcUtils.getTraceId();
5. 日志检索
- 通过traceId快速关联同一请求的所有日志:
grep "TRACEID:abc123def456abc123def456abc123" logs/info/info.log

浙公网安备 33010602011771号