Log日志的实现原理
log.info(" XXX");
打印日志的时候,Log组件会将“写入”动作封装成一个LogEvent事件,而这个事件的具体表现形式由Log Format和MDC共同控制,Format决定了Log的输出格式,而MDC决定了输出什么内容。
LogFormat
Log组件定义了日志输出格式,这和我们平时使用“String.format”的方式差不多,集成了Sleuth后的Log输出格式是下面这个样子
"%5p [sleuth-traceA,%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}]"
MDC
MDC是通过InheritableThreadLocal来实现的,它可以携带当前线程的上下文信息。它的底层是一个Map结构,存储了一系列Key-Value的值。
SpringCloudd的Sleuth就是借助Spring的AOP机制,在方法调用的时候配置了切面,将链路追踪数据加入到了MDC中,这样在打印Log的时候,就能从MDC中获取这些值,
填入到Log Format中的占位符里。
由于MDC基于InheritableThreadLocal而不是ThreadLocal实现,因此假如在当前线程中又开启了新的子线程,那么子线程依然会保留父线程的上下文信息。
InheritableThreadLocal和ThreadLocal相比,可以获取父类的值。重写了getMap、createMap方法。
源码学习
源码可以参考logback组件中LogEvent类的prepareForDeferredProcessing方法,了解MDC和Log Format是如何工作的。可以在打印log的地方打一个断点,本地启动项目后发起一次调用,然后一路跟进去。
/**应在序列化事件之前调用此方法。在使用异步或延迟日志记录时也应该调用它。 请注意,由于性能问题,此方法不提取调用方数据。提取呼叫者信息是呼叫者的责任。 **/ public void prepareForDeferredProcessing() { this.getFormattedMessage(); this.getThreadName(); // fixes http://jira.qos.ch/browse/LBCLASSIC-104 this.getMDCPropertyMap(); }