有了日志之后,我们就可以追踪各种线上问题。但是,在分布式系统中,各种无关日志穿行其中,导致我们可能无法直接定位整个操作流程。因此,我们可能需要对一个用户的操作流程进行归类标记,比如使用线程+时间戳,或者用户身份标识等;如此,我们可以从大量日志信息中grep出某个用户的操作流程,或者某个时间的流转记录。
因此,这就有了 Slf4j MDC 方法。
MDC 是一种在多线程环境中处理日志上下文信息的机制,通常用于 Web 应用或分布式系统中,能够将诊断信息(如用户 ID、请求 ID 等)绑定到当前线程。
在日志输出中,你可以轻松地加入 MDC 信息,帮助你更好地追踪每个请求的日志,尤其是在高并发和复杂系统中。
使用 MDC 时需要注意清理线程的上下文信息,避免内存泄漏。
MDC(Mapped Diagnostic Context)是日志记录系统中的一个概念,用于在多线程环境下,管理和追踪与当前线程相关的诊断信息。它是日志框架(如 Logback 和 Log4j)的一部分,允许你在日志事件中附加额外的上下文信息,这些信息能够帮助你在日志中更清晰地标识和过滤特定请求、会话或线程的活动。
为什么需要 MDC
在多线程的应用中,尤其是 Web 应用,通常会有多个请求在同一时间处理,日志记录需要能够标识出每个请求或线程的相关信息(例如请求的唯一 ID、用户信息等)。然而,单个线程可能会在不同的时间执行多个任务,并且这些任务可能会并发执行。因此,日志框架需要一种方法来跟踪和区分不同线程中的上下文信息。
MDC 提供了一个非常方便的机制来解决这个问题,它允许你为当前线程绑定一些键值对的诊断信息,在日志输出时,可以将这些信息自动加到每一条日志记录中。
MDC 的工作原理
MDC 是基于线程局部存储(ThreadLocal)来工作的,意味着每个线程都有一个独立的 MDC。线程可以将一些诊断信息放入 MDC 中,随后所有在该线程中执行的日志事件都会带上这些信息。
- 设置 MDC 信息
在代码中,你可以通过 MDC.put() 来设置信息:
import org.slf4j.MDC;
public class Example {
public void processRequest(String userId, String requestId) {
// 将诊断信息添加到 MDC
MDC.put("userId", userId);
MDC.put("requestId", requestId);
// 执行一些操作
doSomething();
// 移除 MDC 信息,防止泄漏
MDC.remove("userId");
MDC.remove("requestId");
}
}
- 日志记录时自动包含 MDC 信息
当你在日志中记录消息时,MDC 中存储的值会自动添加到日志输出中。如果你使用的是 SLF4J(通常与 Logback 一起使用),你可以在日志格式中指定要输出的 MDC 信息。例如:
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg MDC[userId=%X{userId}, requestId=%X{requestId}] %n</pattern>
</encoder>
在上述配置中,%X{userId} 和 %X{requestId} 会从 MDC 中提取对应的值,并将它们插入到日志输出中。
3. 示例日志输出
假设在执行 processRequest(“1234”, “req-5678”) 后,生成的日志可能是:
2024-11-11 12:34:56 [main] INFO Example - Request processed successfully MDC[userId=1234, requestId=req-5678]
可以看到,userId 和 requestId 作为额外的上下文信息,已经被自动添加到日志中。
在 Web 应用中的应用
在 Web 应用中,通常你希望为每个请求生成一个唯一的标识符(如请求 ID),并将它存储在 MDC 中。这样,可以在整个请求处理过程的日志中自动关联这些信息。
例子:Spring Boot 中使用 MDC
在 Spring Boot 应用中,你可以通过一个 Filter 来为每个请求设置 MDC 信息:
import org.slf4j.MDC;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.UUID;
public class MdcRequestFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, javax.servlet.http.HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 生成唯一的请求 ID
String requestId = UUID.randomUUID().toString();
// 将请求 ID 存储到 MDC 中
MDC.put("requestId", requestId);
// 继续处理请求
filterChain.doFilter(request, response);
// 移除 MDC 中的值
MDC.remove("requestId");
}
}
然后在 logback.xml 中配置 MDC 输出:
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg MDC[requestId=%X{requestId}] %n</pattern>
</encoder>
MDC 的优势
日志追踪:MDC 允许你在日志中追踪每个线程或请求的上下文信息。对于分布式系统,特别是在微服务架构中,它有助于追踪每个请求的处理流程,帮助调试和定位问题。
避免混乱的全局变量:与直接使用全局变量不同,MDC 提供了线程安全的方式来存储上下文信息。每个线程都有自己的 MDC,所以不会互相干扰。
提高可读性和可维护性:通过将诊断信息绑定到 MDC,你可以更方便地控制日志输出的内容,使得日志变得更加结构化,便于在后期的分析和处理。
MDC 的注意事项
MDC 的生命周期:在每次请求处理完毕后,一定要记得调用 MDC.remove() 清理 MDC 中的内容。因为 MDC 使用的是线程局部存储(ThreadLocal),如果不清理,可能会导致内存泄漏或不正确的日志输出。
MDC 信息泄漏:如果你将 MDC 信息传递到异步线程中,可能会出现信息泄漏问题(例如,线程池中的线程可能会复用上一个请求的 MDC 信息)。为避免这种情况,应该在异步线程中手动传递和清理 MDC。
性能影响:虽然 MDC 提供了非常方便的日志上下文功能,但在高并发场景下,过多的 MDC 操作可能对性能有一定影响。为了避免性能瓶颈,应谨慎使用,尤其是在高并发和多线程的场景下。
logback 还内置了过滤器,比如 MDCInsertingServletFilter,说了这么多都不如直接看官网资料,官网地址:
https://logback.qos.ch/manual/mdc.html#autoMDC
3000

被折叠的 条评论
为什么被折叠?



