SLF4J MDC在全鏈路跟蹤中的應用
經常做線上問題排查的可能會有感受,由於日誌列印一般是無序的,多執行緒下想要序列拿到一次請求中的相關日誌簡直是大海撈針。那麼MDC是一種很好的解決辦法。
SLF4J的MDC
SLF4J 提供了MDC ( Mapped Diagnostic Contexts )功能,它的實現也是利用了 ThreadLocal 機制。 在程式碼中,只需要將指定的值 put 到執行緒上下文的 Map 中,然後在對應的地方使用 get 方法獲取對應的值,從而達到自定義和修改日誌輸出格式內容的目的。
例如以下受log4j2.xml模板:
<Pattern>%d %p [%c] [%X{key1},%X{key2}]- %m%n</Pattern>
在日誌模板log4j2.xml中,使用 %X{} 來佔位,內容會替換為對應MDC 中 key的值,以達到自定義日誌格式的效果。
MDC在鏈路跟蹤中的應用
在鏈路跟蹤框架中,其實擴充套件MDC很簡單,只需在log span的before方法中塞入traceId與spanId,在after方法中進行清理邏輯即可。
private void beforeStartSpan(Span span){ MDC.put(TraceKeys.TRACE_ID, span.getTraceId()); MDC.put(TraceKeys.SPAN_ID, span.getSpanId()); } private void afterEndSpan(Span span){ MDC.remove(TraceKeys.TRACE_ID); MDC.remove(TraceKeys.SPAN_ID); if (span != null) { MDC.put(TraceKeys.TRACE_ID, currentSpan.getTraceId()); MDC.put(TraceKeys.SPAN_ID, currentSpan.getParentId()); //此處需要塞回parent span的spanId } }
那麼在log4j2.xml中配置:
<Pattern>%d %p [%c] [%X{TraceId},%X{SpanId}]- %m%n</Pattern>//在合適的地方加入 [%X{TraceId},%X{SpanId}] 即可
這樣輸出日誌即為:
2019-01-29 19:06:15,482 INFO [com.fredal.TestController] [e9b84d301f73f6e1a6386f216fa0120d,9296f83b058675d2]- this is a test in test 2019-01-29 19:06:15,489 INFO [com.fredal.TestController] [e9b84d301f73f6e1a6386f216fa0120d,f435c1cb819db821]- this is a test in test/provider
非同步中的MDC
由於MDC是基於Threadlocal的,那麼如果一個請求中有非同步的邏輯,那麼非同步過程中的日誌是取不到MDC中的值的。
這也是個老生常談的問題了,由於我們的全鏈路跟蹤框架已經使用Transmittable ThreadLocal改造過了,見呼叫鏈跨執行緒傳遞THREADLOCAL物件 ,所以在非同步執行緒中也是同樣能獲得的MDC的值的。