网站首页 > 技术文章 正文
在复杂的分布式系统中,日志是故障排查和性能分析的关键工具。然而,当请求跨多个线程或服务时,保持日志的上下文信息变得困难。这时,MDC(Mapped Diagnostic Context)变得非常有用。SLF4J 提供了对 MDC 的支持,可以帮助我们在日志中保持上下文信息,实现日志链路追踪。
本文将详解如何使用 SLF4J 的 MDC 功能,实现请求、异步任务、线程池中的日志链路追踪。
一、什么是 MDC?
MDC 是一种用于在日志中添加上下文信息的机制。它允许将一些特定于当前线程的信息(如请求 ID、用户 ID 等)存储起来,并在日志输出时自动添加这些信息。这样可以帮助我们更好地追踪请求的整个生命周期。
二、基本使用
首先,确保你的项目中包含 SLF4J 及其实现的依赖。这里以 Logback 为例:
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
三、设置和清理 MDC
在处理请求的入口处(例如 Spring 的过滤器或拦截器),设置 MDC 信息:
import org.slf4j.MDC;
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
// 设置 MDC 信息
MDC.put("requestId", generateRequestId());
chain.doFilter(request, response);
} finally {
// 清理 MDC 信息
MDC.clear();
}
}
private String generateRequestId() {
return UUID.randomUUID().toString();
}
在日志配置中使用 %X{key} 占位符来包含 MDC 信息,例如在 logback.xml 中:
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg %X{requestId}%n</pattern>
</encoder>
四、在异步任务中传递 MDC
在异步任务中,我们需要确保 MDC 信息能够从父线程传递到子线程。这里有几种常见的方式。
1. 使用装饰器模式
创建一个装饰器类来传递 MDC 信息:
import java.util.concurrent.Callable;
public class MDCTask<V> implements Callable<V> {
private final Callable<V> task;
private final Map<String, String> contextMap;
public MDCTask(Callable<V> task) {
this.task = task;
this.contextMap = MDC.getCopyOfContextMap();
}
@Override
public V call() throws Exception {
if (contextMap != null) {
MDC.setContextMap(contextMap);
}
try {
return task.call();
} finally {
MDC.clear();
}
}
}
2. 使用自定义的线程池
创建一个自定义的线程池来确保 MDC 信息在任务执行时被传递:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
public class MDCExecutorService {
private final ExecutorService executor;
public MDCExecutorService(int poolSize) {
this.executor = Executors.newFixedThreadPool(poolSize, r -> {
Thread thread = Executors.defaultThreadFactory().newThread(new MDCRunnable(r));
thread.setName("MDCThread-" + thread.getId());
return thread;
});
}
public void submit(Runnable task) {
executor.submit(new MDCRunnable(task));
}
public void shutdown() {
executor.shutdown();
}
}
3. 使用 Spring Async 支持
如果使用 Spring 框架,可以通过配置 TaskDecorator 来实现 MDC 的传递:
import org.springframework.core.task.TaskDecorator;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import java.util.concurrent.Executor;
@Component
public class CustomAsyncConfigurer implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setTaskDecorator(new MDCTaskDecorator());
executor.initialize();
return executor;
}
}
class MDCTaskDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
Map<String, String> contextMap = MDC.getCopyOfContextMap();
return () -> {
if (contextMap != null) {
MDC.setContextMap(contextMap);
}
try {
runnable.run();
} finally {
MDC.clear();
}
};
}
}
五、完整示例
结合上述内容,下面是一个完整的示例,展示如何在一个简单的 Spring Boot 应用中实现日志链路追踪。
1. 创建 Spring Boot 应用
@SpringBootApplication
public class MdcLoggingApplication {
public static void main(String[] args) {
SpringApplication.run(MdcLoggingApplication.class, args);
}
}
2. 配置拦截器设置 MDC
@Component
public class MDCInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
MDC.put("requestId", UUID.randomUUID().toString());
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
MDC.clear();
}
}
3. 配置异步任务
@Component
public class AsyncService {
private static final Logger logger = LoggerFactory.getLogger(AsyncService.class);
@Async
public void executeAsyncTask() {
logger.info("Executing async task");
}
}
4. Controller 使用异步任务
@RestController
public class MyController {
private final AsyncService asyncService;
@Autowired
public MyController(AsyncService asyncService) {
this.asyncService = asyncService;
}
@GetMapping("/test")
public ResponseEntity<String> testAsync() {
asyncService.executeAsyncTask();
return ResponseEntity.ok("Async task executed");
}
}
六、总结
通过 MDC,我们可以在分布式系统中实现完整的日志链路追踪,从而更好地进行故障排查和性能分析。本文展示了如何在同步和异步任务中使用 MDC,包括线程池的处理方式。希望这篇博客能够帮助你在实际项目中有效地应用 MDC,实现高效的日志管理。
猜你喜欢
- 2024-12-29 使用Flume同步日志到Kafka flume收集日志到hdfs
- 2024-12-29 Python日志模块logging python logger日志级别
- 2024-12-29 在Spring Boot中通过AOP技术实现日志拦截操作
- 2024-12-29 [编程基础] Python日志记录库logging总结
- 2024-12-29 如何将日志记录到 Windows事件日志 中
- 2024-12-29 SpringBoot集成logback异步日志 springboot集成日志log4j2
- 2024-12-29 Spring Boot中的Logback日志配置详解
- 2024-12-29 Linux 系统日志写入记录命令用法(logger)
- 2024-12-29 Python logging模块日志相关功能及应用示例
- 2024-12-29 Spring Boot搭建 ELK,这才是正确看日志的方式
- 05-16在实际操作过程中如何避免出现SQL注入漏洞
- 05-16MySQL中 in数量限制
- 05-16一文讲懂SQL筛选子句HAVING子句
- 05-16性能调优实战:Spring Boot 多线程处理SQL IN语句大量值的优化方案
- 05-16sqlserver数据库中的模糊查询like和通配符的使用
- 05-16SQL必备 和 表关联
- 05-16SQL Server优化50法
- 05-16他们一直都在!最新强军大片来了
- 最近发表
- 标签列表
-
- cmd/c (64)
- c++中::是什么意思 (83)
- 标签用于 (65)
- 主键只能有一个吗 (66)
- c#console.writeline不显示 (75)
- pythoncase语句 (81)
- es6includes (73)
- sqlset (64)
- windowsscripthost (67)
- apt-getinstall-y (86)
- node_modules怎么生成 (76)
- chromepost (65)
- c++int转char (75)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- & (66)
- java (73)
- org.redisson (64)
- js数组插入 (83)
- gormwherein (64)
- linux删除一个文件夹 (65)
- mac安装java (72)
- eacces (67)
- 查看mysql是否启动 (70)
- 无效的列索引 (74)