网站首页 > 技术文章 正文
在复杂的分布式系统中,日志是故障排查和性能分析的关键工具。然而,当请求跨多个线程或服务时,保持日志的上下文信息变得困难。这时,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,这才是正确看日志的方式
- 02-21走进git时代, 你该怎么玩?_gits
- 02-21GitHub是什么?它可不仅仅是云中的Git版本控制器
- 02-21Git常用操作总结_git基本用法
- 02-21为什么互联网巨头使用Git而放弃SVN?(含核心命令与原理)
- 02-21Git 高级用法,喜欢就拿去用_git基本用法
- 02-21Git常用命令和Git团队使用规范指南
- 02-21总结几个常用的Git命令的使用方法
- 02-21Git工作原理和常用指令_git原理详解
- 最近发表
- 标签列表
-
- cmd/c (57)
- c++中::是什么意思 (57)
- sqlset (59)
- ps可以打开pdf格式吗 (58)
- phprequire_once (61)
- localstorage.removeitem (74)
- routermode (59)
- vector线程安全吗 (70)
- & (66)
- java (73)
- org.redisson (64)
- log.warn (60)
- cannotinstantiatethetype (62)
- js数组插入 (83)
- resttemplateokhttp (59)
- gormwherein (64)
- linux删除一个文件夹 (65)
- mac安装java (72)
- reader.onload (61)
- outofmemoryerror是什么意思 (64)
- flask文件上传 (63)
- eacces (67)
- 查看mysql是否启动 (70)
- java是值传递还是引用传递 (58)
- 无效的列索引 (74)