MDC实现日志追踪
MDC使用
一、MDC简介
MDC(Mapped Diagnostic Context,映射调试上下文)是 log4j 和 logback 提供的一种方便在多线程条件下记录日志的功能,也可以说是一种轻量级的日志跟踪工具。MDC是应用内的线程级别,不是分布式的应用层级别,所以仅靠它无法做到分布式应用调用链路跟踪的需求。它要解决的问题主要是让我们可以在海量日志数据中快速捞到可用的日志信息。
使用场景:既然我们知道MDC可以让我们快速的捞到可用的日志信息,那具体怎么捞呢?我们先来看这样的一个场景:很多时候,程序调用链会很复杂,并且在调用链的各个环节中,会对一些关键的操作做日志埋点,比如说入参出参、复杂计算后的结果等等信息,但在线上环境是很多用户使用我们功能的,比如说A程序,每个用户都在使用了A程序后,打印了A程序方法调用链内的所以日志,那我怎么就知道这一堆相同日志中,哪些是同一次请求所打印的呢?可能大家会说:可以看它的线程名啊,HTTP在同一请求中会用同一个线程。一定程度上看线程是可以的,但我们也知道,web服务器不可能无限创建线程的,它内部有个线程池,用于HTTP线程的创建、回收等管理,如果该程序使用频率是很高,那完全有可能短时间内的几次请求用的都是同一个线程,这样的话就解决不了上述所说的:“把一次请求中调用链内的所以日志找出来”的需求了。
解决方案: 针对以上的场景,可以在一次请求进来的时候,创建一个全局唯一的标识符,该标识符可以没有业务含义,我们就叫它做“REQUEST_ID”吧,因为这仅仅只是为了区分每次请求打印了什么信息,接下来,我们知道ThreadLocal这个类是可以共享线程内的数据的,所以我们就可以利用它来实现这个需求了。把traceId放入到ThreadLocal中,然后在我们程序调用链中输出日志时,就可以带上这个traceId了,比如以下代码:
public class RequestWrapperFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { //请求日志中设置唯一值,便于后续日志排查 MDC.put(Constants.REQUEST_ID, UUID.randomUUID().toString()); //将servletRequest封装成ContentCachingRequestWrapper后,body被读取时,会被它缓存 filterChain.doFilter(RequestUtils.createContentCachingRequestWrapper(request),response); } }
以上方法虽然解决了我们的问题,但是我们每次打印日志都要自己拿一下traceId,这无形增加了我们的工作量和降低了代码的美观度,所以我们肯定得想办法封装这部分重复的代码了。而这个封装的事情MDC就帮我们做了,我们只管在请求最开始时,生成一个traceId,然后放到MDC中就可以了,之后的事情就是按照我们原来的方式打印日志,不用新增其他额外的重复代码,这个traceId也一直跟随这个线程的执行完所有的任务。
二、MDC原理
通过以上的实现,我们发现MDC使用起来非常简单,就只有两个步骤:
- 1、定义日志格式,其中
%X{}
代表去MDC取值 - 2、通过拦截器或者AOP在方法调用链最开始,设置MDC的值
参考文档:https://blog.csdn.net/a183400826/article/details/101519219
https://blog.csdn.net/xubin320121/article/details/93857126
http://www.xuetimes.com/archives/1944
https://tlog.yomahub.com/docs/