通过MDC+traceId实现全局请求追踪并快速定位日志
创作时间:
作者:
@小白创作中心
通过MDC+traceId实现全局请求追踪并快速定位日志
引用
CSDN
1.
https://blog.csdn.net/qq_39354140/article/details/145107805
在大型分布式系统中,如何快速定位某次请求或方法调用的日志信息是一个常见的挑战。本文将介绍一种基于MDC(Mapped Diagnostic Context)和traceId的解决方案,通过在Spring Boot项目中实现全局请求追踪,帮助开发者快速定位日志信息。
背景
在大型分布式系统中,日志量往往非常庞大,如何在海量的日志堆栈中快速定位到某次请求或方法调用的日志信息是一个常见的挑战。本文将介绍一种基于MDC(Mapped Diagnostic Context)和traceId的解决方案,通过在Spring Boot项目中实现全局请求追踪,帮助开发者快速定位日志信息。
环境
JDK:1.8
Spring boot:2.6.1
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.36</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
方法
- 在服务端入口处生成一个唯一的traceId
- 日志中输出traceId的值
- 接口返回中,添加一个通用字段traceId
实现过程
1. 创建TraceFilter
通过实现一个Filter来拦截所有请求,生成唯一的TraceId并将其存入ThreadLocal和MDC。MDC是logback提供的一个拓展入口,可以向里面放入一些键值对,然后在logback日志中可以通过对应的键取出值。更多MDC的使用方法请参考MDC的官方文档:MDC(Mapped Diagnostic Context)
@Component
public class TraceFilter implements Filter {
private static final ThreadLocal<String> ThreadLocal_TRACE_ID = new ThreadLocal<>();
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化操作
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 生成唯一的 traceId
String traceId = UUID.randomUUID().toString();
// 将 traceId 存储到 ThreadLocal 中
ThreadLocal_TRACE_ID.set(traceId);
// 将 traceId 存入 MDC
MDC.put("traceId", traceId);
try {
chain.doFilter(request, response);
} finally {
// 请求结束后清除 MDC 中的 traceId
MDC.remove("traceId");
}
}
@Override
public void destroy() {
// 销毁操作
}
/**
* 获取 ThreadLocal 中的 traceId
* @return traceId
*/
public static String getTraceId() {
return ThreadLocal_TRACE_ID.get();
}
}
2. 配置日志输出
在logback-spring.xml中配置日志输出格式,使其能够输出traceId:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 定义日志文件的存储路径 -->
<property name="LOG_PATH" value="D:/data/logs/logs"/>
<property name="LOG_FILE_NAME" value="testLog"/>
<!-- 控制台日志输出配置 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] [traceId:%X{traceId}] - %msg%n</pattern>
</encoder>
</appender>
<!-- 文件日志输出配置 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${LOG_FILE_NAME}.log</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] [traceId:%X{traceId}] - %msg%n</pattern>
</encoder>
<!-- 日志文件滚动策略 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 每天生成一个日志文件 -->
<fileNamePattern>${LOG_PATH}/${LOG_FILE_NAME}-%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 保留最近30天的日志文件 -->
<maxHistory>30</maxHistory>
</rollingPolicy>
</appender>
<!-- 日志级别配置 -->
<root level="DEBUG">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
</root>
</configuration>
3. 统一返回格式
为了在接口响应中返回traceId,需要定义一个统一的响应类ApiResponse:
@Data
public class ApiResponse<T> {
private String traceId;
private int code;
private String message;
private T data;
public static <T> ApiResponse<T> success(T data) {
ApiResponse<T> response = new ApiResponse<>();
response.setCode(200);
response.setMessage("Success");
response.setData(data);
response.setTraceId(TraceFilter.getTraceId());
return response;
}
// 其他静态工厂方法省略
}
4. 全局异常处理
为了在程序报错时也能返回traceId,需要创建统一的错误返回类ErrorResponse和全局异常处理类GlobalExceptionHandler:
@RestControllerAdvice
public class GlobalExceptionHandler {
// 捕获所有异常
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) // 返回 HTTP 500 状态码
public ErrorResponse handleException(Exception ex) {
String traceId = TraceFilter.getTraceId();
return new ErrorResponse(traceId, 500, ex.getMessage());
}
}
@Data
public class ErrorResponse {
private String traceId; // 追踪 ID
private int code; // 错误码
private String message; // 错误信息
public ErrorResponse(String traceId, int code, String message) {
this.traceId = traceId;
this.code = code;
this.message = message;
}
}
测试
创建两个接口用来测试:
@RequestMapping("/api")
@RestController
public class TestController {
@GetMapping("/test1")
public ApiResponse<String> test1() {
// 业务逻辑
return ApiResponse.success("Hello, World!");
}
@GetMapping("/test2")
public ApiResponse<String> test2() {
// 业务逻辑
int a = 1 / 0;
return ApiResponse.success("Hello, World!");
}
}
启动项目,分别向两个接口发送请求:
现在,可以通过前端得到的traceId快速定位到日志中对应的每次请求:
项目的结构如下:
热门推荐
微信转账记录查询:入口、查询、退款全攻略
“姥姥牌”小棉袄:保暖时尚二合一,元旦送礼佳选
尼康D750拍摄建水古城:5个必拍景点及实拍技巧
避开人流,静享建水:冬季古城深度游完全攻略
流光溢彩紫陶街,建水夜生活的璀璨中心
冬季供暖欠费追诉时效知多少?
Vue类名绑定完全指南:从基础到高级技巧
避免厨房装修坑:从空间布局到通风系统全解析
共享菜园收益AB面
医生详解:吃鸡肉有哪些营养价值?
新研究:每天多摄入一口这类肉,糖尿病、心脏病风险显著增加
术后伤口愈合不良怎么办?中医来支招
聊天技巧大揭秘:快速识别女生好感信号
叶罗丽:白光莹vs罗丽,谁才是真正的颜值担当?
数字时代下的平面设计新趋势
主流智能音箱将接入Matter协议,实现跨平台音乐播放
AI赋能文化传播:微短剧、数字复原等新模式涌现
生旦净末丑的华美装扮:京剧服饰与化妆的艺术传承与创新
大连机场打车防坑指南:正规出租车识别与路线攻略
福州三江口植物园:打造国内最大兰花温室,年底开园迎客
打喷嚏竟是呼吸道的“清道夫”!
冬天来了,打喷嚏竟然是好事?
大连周水子机场交通攻略:大巴公交出租任选,价格路线全解析
大连4天3夜1000元攻略:景点、住宿、餐饮全规划
大连机场到市区:地铁最便捷,打车30元起
宝宝肠绞痛,这几招让你秒变育儿达人
婴儿肠绞痛护理秘籍大揭秘
宝宝肠绞痛的新疗法:从喂养到特殊配方奶的全面解决方案
婴儿腹部按摩:缓解肠绞痛的温柔之手
最新动车行李标准:超130厘米需托运,附打包技巧