通过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快速定位到日志中对应的每次请求:
项目的结构如下:
热门推荐
泰国教育体系和中国有什么不同?
听君一席话,胜读十年书
"听君一席话,胜读十年书"的出处与故事
病毒感冒用什么药比较好
怀孕早期应该怎样饮食才能保胎?
汇总十款热门鱼油测评,鱼油该怎么选?介绍鱼油的功效作用以及选购攻略
构成合同诈骗罪的几个要件
阑尾炎疼痛的症状和表现是什么
情绪激动是否会导致中风?专家解读情绪与中风的关系
供热客服知识库建设:在线报修与即时沟通的重要性
羲字取名的寓意和象征,羲字取名男孩有寓意
"羲"字起名:搭配组合与文化内涵
细干柔条才数尺,千寻起自微因。
从西安市区到兵马俑怎么走最方便,西安市区到兵马俑最便捷路线攻略
孕妇一天需要睡多久?如何安排孕妇睡眠时间?科学规划,轻松入睡
旧金山湾区的城市概览:从旧金山到奥克兰
糖友出现“泡沫尿”要警惕这个病!
法院限制高消费措施详解:限高后多久会抓人?
硫酸阿托品是“近视神药”?专家提示使用需谨慎
地暖设计规范(地暖安装的图解和全部过程)
采暖季来临,三种地暖类型你该如何选择?
嫦娥五号月壤样品研究成果发表70余项
该不该穿?该怎么穿?你家有校服的烦恼吗?
新时代校服不仅安全,更要发挥美育功能
阳台种植西瓜的时间和方法步骤(轻松在家种出甜美西瓜)
农业种植气象站:实时监测和记录农田温度变化
水果玉米种植时间和方法
脑卒中后肢体功能恢复的锻炼方法
脑梗后遗症应对:提升全身力量的康复策略
老旧小区加装电梯申请规划许可,业主能否“一票否决”