通过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快速定位到日志中对应的每次请求:
项目的结构如下:
热门推荐
如何理解投资钻石的策略?投资钻石有哪些风险和策略?
钻石4C等级表:标准钻石4C参数对照图
“一带一路”下的漳州港:从月港到世界级石化基地
漳州月港:海上丝绸之路的璀璨明珠
摄影修图必会的局部调整技巧:从对比度到白平衡的全面解析
去除婴儿湿疹的方法
返乡还是出游,座驾安排上了吗?
如何操作国债以优化投资组合?这些操作策略有哪些风险和收益?
冬季去云南旅游应该注意什么
鹦鹉爱吃什么?探索它们最爱的美味食物与营养需求
揭秘虎皮鹦鹉养殖成功之道,专业技术咨询指南
蔡明《机器人趣话》:一部映照人性的春晚经典
蔡明:中国喜剧黄金时代的见证者
蔡明新作爆笑揭秘:安徽元素大放异彩
远离非法集资,建设美好生活
连云港冬季免票:花果山&东海温泉必打卡!
连云港冬季三日游攻略:打卡热门景点!
三国演义中的职场生存法则:司马懿教你如何面对失败
重温《三国演义》:司马懿名言中的现代智慧
重磅研究:阿托伐他汀仿制药美达信疗效与安全性全面胜出
张阿姨教你如何正确服用阿托伐他汀
2024年1-2月湛江市经济数据报告:多项指标增速位居全省前列
冬日泰山摄影攻略:捕捉冰雪奇观
2024年居民养老保险“提档补缴”,一次性补缴15年总计3万元,60
5 种妙法,让无窗房间通风不再难
培育爱科学勇探索的“科创娃”!浙大科普基地落户温州这所中学……
三文鱼:维生素B族的优质来源
双十一护肝神器:维生素B族的全方位健康价值
维生素B复合片:拯救你的疲惫日常!
你真的了解如何通过饮食补充维生素B吗?