您好,登錄后才能下訂單哦!
這篇文章主要為大家展示了“java怎么實現日志追蹤MDC”,內容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領大家一起研究并學習一下“java怎么實現日志追蹤MDC”這篇文章吧。
MDC ( Mapped Diagnostic Contexts ) 有了日志之后,我們就可以追蹤各種線上問題。
但是,在分布式系統中,各種無關日志穿行其中,導致我們可能無法直接定位整個操作流程。
因此,我們可能需要對一個用戶的操作流程進行歸類標記,比如使用線程+時間戳,或者用戶身份標識等;如此,我們可以從大量日志信息中grep出某個用戶的操作流程,或者某個時間的流轉記錄。其目的是為了便于我們診斷線上問題而出現的方法工具類。
雖然,Slf4j 是用來適配其他的日志具體實現包的,但是針對 MDC功能,目前只有logback 以及 log4j 支持。 MDC
package org.slf4j; import java.io.Closeable; import java.util.Map; import org.slf4j.helpers.NOPMDCAdapter; import org.slf4j.helpers.BasicMDCAdapter; import org.slf4j.helpers.Util; import org.slf4j.impl.StaticMDCBinder; import org.slf4j.spi.MDCAdapter; public class MDC { static final String NULL_MDCA_URL = "http://www.slf4j.org/codes.html#null_MDCA"; static final String NO_STATIC_MDC_BINDER_URL = "http://www.slf4j.org/codes.html#no_static_mdc_binder"; static MDCAdapter mdcAdapter; public static class MDCCloseable implements Closeable { private final String key; private MDCCloseable(String key) { this.key = key; } public void close() { MDC.remove(this.key); } } private MDC() { } static { try { mdcAdapter = StaticMDCBinder.SINGLETON.getMDCA(); } catch (NoClassDefFoundError ncde) { mdcAdapter = new NOPMDCAdapter(); String msg = ncde.getMessage(); if (msg != null && msg.indexOf("StaticMDCBinder") != -1) { Util.report("Failed to load class \"org.slf4j.impl.StaticMDCBinder\"."); Util.report("Defaulting to no-operation MDCAdapter implementation."); Util.report("See " + NO_STATIC_MDC_BINDER_URL + " for further details."); } else { throw ncde; } } catch (Exception e) { // we should never get here Util.report("MDC binding unsuccessful.", e); } } public static void put(String key, String val) throws IllegalArgumentException { if (key == null) { throw new IllegalArgumentException("key parameter cannot be null"); } if (mdcAdapter == null) { throw new IllegalStateException("MDCAdapter cannot be null. See also " + NULL_MDCA_URL); } mdcAdapter.put(key, val); } public static MDCCloseable putCloseable(String key, String val) throws IllegalArgumentException { put(key, val); return new MDCCloseable(key); } public static String get(String key) throws IllegalArgumentException { if (key == null) { throw new IllegalArgumentException("key parameter cannot be null"); } if (mdcAdapter == null) { throw new IllegalStateException("MDCAdapter cannot be null. See also " + NULL_MDCA_URL); } return mdcAdapter.get(key); } public static void remove(String key) throws IllegalArgumentException { if (key == null) { throw new IllegalArgumentException("key parameter cannot be null"); } if (mdcAdapter == null) { throw new IllegalStateException("MDCAdapter cannot be null. See also " + NULL_MDCA_URL); } mdcAdapter.remove(key); } public static void clear() { if (mdcAdapter == null) { throw new IllegalStateException("MDCAdapter cannot be null. See also " + NULL_MDCA_URL); } mdcAdapter.clear(); } public static Map<String, String> getCopyOfContextMap() { if (mdcAdapter == null) { throw new IllegalStateException("MDCAdapter cannot be null. See also " + NULL_MDCA_URL); } return mdcAdapter.getCopyOfContextMap(); } public static void setContextMap(Map<String, String> contextMap) { if (mdcAdapter == null) { throw new IllegalStateException("MDCAdapter cannot be null. See also " + NULL_MDCA_URL); } mdcAdapter.setContextMap(contextMap); } public static MDCAdapter getMDCAdapter() { return mdcAdapter; } }
package com.alibaba.otter.canal.common; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; public class LogTest { private static final Logger logger = LoggerFactory.getLogger(LogTest.class); public static void main(String[] args) { MDC.put("THREAD_ID", String.valueOf(Thread.currentThread().getId())); logger.info("純字符串信息的info級別日志"); } }
logback.xml 配置
<configuration scan="true" scanPeriod=" 5 seconds"> <jmxConfigurator /> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} %X{THREAD_ID} - %msg%n </pattern> </encoder> </appender> <root level="INFO"> <appender-ref ref="STDOUT"/> </root> </configuration
對應的輸出日志 可以看到輸出了THREAD_ID
2016-12-08 14:59:32.855 [main] INFO com.alibaba.otter.canal.common.LogTest THREAD_ID 1 - 純字符串信息的info級別日志
slf4j只是起到適配的作用 故查看實現類LogbackMDCAdapter屬性
final InheritableThreadLocal<Map<String, String>> copyOnInheritThreadLocal = new InheritableThreadLocal<Map<String, String>>();
InheritableThreadLocal 該類擴展了 ThreadLocal,為子線程提供從父線程那里繼承的值:在創建子線程時,子線程會接收所有
可繼承的線程局部變量的初始值,以獲得父線程所具有的值。通常,子線程的值與父線程的值是一致的;但是,通過重寫這個類中的 childValue 方法,子線程的值可以作為父線程值的一個任意函數。
當必須將變量(如用戶 ID 和 事務 ID)中維護的每線程屬性(per-thread-attribute)自動傳送給創建的所有子線程時,應盡可能地采用可繼承的線程局部變量,而不是采用普通的線程局部變量
驗證一下
package com.alibaba.otter.canal.parse.driver.mysql; import org.junit.Test; public class TestInheritableThreadLocal { @Test public void testThreadLocal() { final ThreadLocal<String> local = new ThreadLocal<String>(); work(local); } @Test public void testInheritableThreadLocal() { final ThreadLocal<String> local = new InheritableThreadLocal<String>(); work(local); } private void work(final ThreadLocal<String> local) { local.set("a"); System.out.println(Thread.currentThread() + "," + local.get()); Thread t = new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread() + "," + local.get()); local.set("b"); System.out.println(Thread.currentThread() + "," + local.get()); } }); t.start(); try { t.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread() + "," + local.get()); } }
分別運行得到的輸出結果
ThreadLocal 存貯輸出結果
Thread[main,5,main],a
Thread[Thread-0,5,main],null
Thread[Thread-0,5,main],b
Thread[main,5,main],a
InheritableThreadLocal存貯輸出結果
Thread[main,5,main],a
Thread[Thread-0,5,main],a
Thread[Thread-0,5,main],b
Thread[main,5,main],a
輸出結果說明一切 對于參數傳遞十分有用 我知道 canal的源碼中用到了MDC
在 CanalServerWithEmbedded 中的 start 和stop等方法中都有用到
public void start(final String destination) { final CanalInstance canalInstance = canalInstances.get(destination); if (!canalInstance.isStart()) { try { MDC.put("destination", destination); canalInstance.start(); logger.info("start CanalInstances[{}] successfully", destination); } finally { MDC.remove("destination"); } } } public void stop(String destination) { CanalInstance canalInstance = canalInstances.remove(destination); if (canalInstance != null) { if (canalInstance.isStart()) { try { MDC.put("destination", destination); canalInstance.stop(); logger.info("stop CanalInstances[{}] successfully", destination); } finally { MDC.remove("destination"); } } } }
MDC是(Mapped Diagnostic Context,映射調試上下文)是 log4j 和 logback 支持的一種方便在多線程條件下記錄追蹤日志的功能。通常打印出的日志會有線程號等信息來標志當前日志屬于哪個線程,然而由于線程是可以重復使用的,所以并不能很清晰的確認一個請求的日志范圍。處理這種情況一般有兩種處理方式:
1)手動生成一個唯一序列號打印在日志中;
2)使用日志控件提供的MDC功能,生成一個唯一序列標記一個線程的日志;
兩種方法的區別在于:
方法一只能標記一條日志,線程內其他日志需要人肉去篩選;
方法二標記整個線程的所有日志,方便grep命令查詢;
對比可見,使用MDC功能更好。
MDC 可以看成是一個與當前線程綁定的哈希表,可以往其中添加鍵值對。MDC 中包含的內容可以被同一線程中執行的代碼所訪問。當前線程的子線程會繼承其父線程中的 MDC 的內容。當需要記錄日志時,只需要從 MDC 中獲取所需的信息即可。MDC 的內容則由程序在適當的時候保存進去。對于一個 Web 應用來說,通常是在請求被處理的最開始保存這些數據。
@RunWith(SpringRunner.class) @SpringBootTest(classes=CreditAppApplication.class) publicclassMDCTest{ @Test publicvoidmdcTest1(){ MDC.put("first","thefirst1"); Loggerlogger=LoggerFactory.getLogger(MDCTest.class); MDC.put("last","thelast1"); logger.info("checkenclosed."); logger.debug("themostbeautifultwowordsinenglish."); MDC.put("first","thefirst2"); MDC.put("last","thelast2"); logger.info("iamnotacrook."); logger.info("AttributedtotheformerUSpresident.17Nov1973."); } }
logback的配置:
@Component @Order(Ordered.HIGHEST_PRECEDENCE) publicclassGlobalLogTagConfigextendsOncePerRequestFilter{ privatestaticfinalStringGLOBAL_LOG_TAG="GLOG_TAG"; privatestaticStringgenerateSeqNo(){ returnUUID.randomUUID().toString().replace("-","").substring(0,12); } @Override protectedvoiddoFilterInternal(HttpServletRequesthttpServletRequest,HttpServletResponsehttpServletResponse,FilterChainfilterChain)throwsServletException,IOException{ try{ StringseqNo; if(httpServletRequest!=null){ seqNo=httpServletRequest.getHeader(GLOBAL_LOG_TAG); if(StringUtils.isEmpty(seqNo)){ seqNo=generateSeqNo(); } }else{ seqNo=generateSeqNo(); } MDC.put(GLOBAL_LOG_TAG,seqNo); filterChain.doFilter(httpServletRequest,httpServletResponse); }finally{ MDC.remove(GLOBAL_LOG_TAG); } } }
注意:
OncePerRequestFilter的作用是為了讓每個請求只經過這個過濾器一次(因為web container的不同,有些過濾器可能被多次執行)
logback配置:
以上是“java怎么實現日志追蹤MDC”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。