一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術(shù)|正則表達(dá)式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務(wù)器之家 - 編程語言 - Java教程 - java 如何實(shí)現(xiàn)日志追蹤MDC

java 如何實(shí)現(xiàn)日志追蹤MDC

2022-01-12 10:37holly_wang_王小飛 Java教程

這篇文章主要介紹了java 實(shí)現(xiàn)日志追蹤MDC方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

java 日志追蹤MDC

MDC ( Mapped Diagnostic Contexts ) 有了日志之后,我們就可以追蹤各種線上問題。

但是,在分布式系統(tǒng)中,各種無關(guān)日志穿行其中,導(dǎo)致我們可能無法直接定位整個(gè)操作流程。

因此,我們可能需要對(duì)一個(gè)用戶的操作流程進(jìn)行歸類標(biāo)記,比如使用線程+時(shí)間戳,或者用戶身份標(biāo)識(shí)等;如此,我們可以從大量日志信息中g(shù)rep出某個(gè)用戶的操作流程,或者某個(gè)時(shí)間的流轉(zhuǎn)記錄。其目的是為了便于我們?cè)\斷線上問題而出現(xiàn)的方法工具類。

雖然,Slf4j 是用來適配其他的日志具體實(shí)現(xiàn)包的,但是針對(duì) MDC功能,目前只有l(wèi)ogback 以及 log4j 支持。 MDC

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
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;
    }
}

簡(jiǎn)單的demo

?
1
2
3
4
5
6
7
8
9
10
11
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級(jí)別日志");
    }
}

logback.xml 配置

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<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

對(duì)應(yīng)的輸出日志 可以看到輸出了THREAD_ID

2016-12-08 14:59:32.855 [main] INFO com.alibaba.otter.canal.common.LogTest THREAD_ID 1 - 純字符串信息的info級(jí)別日志

slf4j只是起到適配的作用 故查看實(shí)現(xiàn)類LogbackMDCAdapter屬性

final InheritableThreadLocal<Map<String, String>> copyOnInheritThreadLocal = new InheritableThreadLocal<Map<String, String>>();

InheritableThreadLocal 該類擴(kuò)展了 ThreadLocal,為子線程提供從父線程那里繼承的值:在創(chuàng)建子線程時(shí),子線程會(huì)接收所有

可繼承的線程局部變量的初始值,以獲得父線程所具有的值。通常,子線程的值與父線程的值是一致的;但是,通過重寫這個(gè)類中的 childValue 方法,子線程的值可以作為父線程值的一個(gè)任意函數(shù)。

當(dāng)必須將變量(如用戶 ID 和 事務(wù) ID)中維護(hù)的每線程屬性(per-thread-attribute)自動(dòng)傳送給創(chuàng)建的所有子線程時(shí),應(yīng)盡可能地采用可繼承的線程局部變量,而不是采用普通的線程局部變量

驗(yàn)證一下

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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()); 
    
}

分別運(yùn)行得到的輸出結(jié)果

ThreadLocal 存貯輸出結(jié)果
Thread[main,5,main],a
Thread[Thread-0,5,main],null
Thread[Thread-0,5,main],b
Thread[main,5,main],a
InheritableThreadLocal存貯輸出結(jié)果
Thread[main,5,main],a
Thread[Thread-0,5,main],a
Thread[Thread-0,5,main],b
Thread[main,5,main],a

輸出結(jié)果說明一切 對(duì)于參數(shù)傳遞十分有用 我知道 canal的源碼中用到了MDC

在 CanalServerWithEmbedded 中的 start 和stop等方法中都有用到

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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的介紹及使用

1、MDC是什么?

MDC是(Mapped Diagnostic Context,映射調(diào)試上下文)是 log4j 和 logback 支持的一種方便在多線程條件下記錄追蹤日志的功能。通常打印出的日志會(huì)有線程號(hào)等信息來標(biāo)志當(dāng)前日志屬于哪個(gè)線程,然而由于線程是可以重復(fù)使用的,所以并不能很清晰的確認(rèn)一個(gè)請(qǐng)求的日志范圍。處理這種情況一般有兩種處理方式:

1)手動(dòng)生成一個(gè)唯一序列號(hào)打印在日志中;

2)使用日志控件提供的MDC功能,生成一個(gè)唯一序列標(biāo)記一個(gè)線程的日志;

兩種方法的區(qū)別在于:

方法一只能標(biāo)記一條日志,線程內(nèi)其他日志需要人肉去篩選;

方法二標(biāo)記整個(gè)線程的所有日志,方便grep命令查詢;

對(duì)比可見,使用MDC功能更好。

2、MDC的原理

MDC 可以看成是一個(gè)與當(dāng)前線程綁定的哈希表,可以往其中添加鍵值對(duì)。MDC 中包含的內(nèi)容可以被同一線程中執(zhí)行的代碼所訪問。當(dāng)前線程的子線程會(huì)繼承其父線程中的 MDC 的內(nèi)容。當(dāng)需要記錄日志時(shí),只需要從 MDC 中獲取所需的信息即可。MDC 的內(nèi)容則由程序在適當(dāng)?shù)臅r(shí)候保存進(jìn)去。對(duì)于一個(gè) Web 應(yīng)用來說,通常是在請(qǐng)求被處理的最開始保存這些數(shù)據(jù)。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@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的配置:

java 如何實(shí)現(xiàn)日志追蹤MDC

3、MDC的使用

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@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的作用是為了讓每個(gè)請(qǐng)求只經(jīng)過這個(gè)過濾器一次(因?yàn)閣eb container的不同,有些過濾器可能被多次執(zhí)行)

logback配置:

java 如何實(shí)現(xiàn)日志追蹤MDC

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持服務(wù)器之家。

原文鏈接:https://www.jianshu.com/p/06b1d35526c2

延伸 · 閱讀

精彩推薦
  • Java教程Java BufferWriter寫文件寫不進(jìn)去或缺失數(shù)據(jù)的解決

    Java BufferWriter寫文件寫不進(jìn)去或缺失數(shù)據(jù)的解決

    這篇文章主要介紹了Java BufferWriter寫文件寫不進(jìn)去或缺失數(shù)據(jù)的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望...

    spcoder14552021-10-18
  • Java教程Java使用SAX解析xml的示例

    Java使用SAX解析xml的示例

    這篇文章主要介紹了Java使用SAX解析xml的示例,幫助大家更好的理解和學(xué)習(xí)使用Java,感興趣的朋友可以了解下...

    大行者10067412021-08-30
  • Java教程升級(jí)IDEA后Lombok不能使用的解決方法

    升級(jí)IDEA后Lombok不能使用的解決方法

    最近看到提示IDEA提示升級(jí),尋思已經(jīng)有好久沒有升過級(jí)了。升級(jí)完畢重啟之后,突然發(fā)現(xiàn)好多錯(cuò)誤,本文就來介紹一下如何解決,感興趣的可以了解一下...

    程序猿DD9332021-10-08
  • Java教程Java8中Stream使用的一個(gè)注意事項(xiàng)

    Java8中Stream使用的一個(gè)注意事項(xiàng)

    最近在工作中發(fā)現(xiàn)了對(duì)于集合操作轉(zhuǎn)換的神器,java8新特性 stream,但在使用中遇到了一個(gè)非常重要的注意點(diǎn),所以這篇文章主要給大家介紹了關(guān)于Java8中S...

    阿杜7482021-02-04
  • Java教程xml與Java對(duì)象的轉(zhuǎn)換詳解

    xml與Java對(duì)象的轉(zhuǎn)換詳解

    這篇文章主要介紹了xml與Java對(duì)象的轉(zhuǎn)換詳解的相關(guān)資料,需要的朋友可以參考下...

    Java教程網(wǎng)2942020-09-17
  • Java教程Java實(shí)現(xiàn)搶紅包功能

    Java實(shí)現(xiàn)搶紅包功能

    這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)搶紅包功能,采用多線程模擬多人同時(shí)搶紅包,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙...

    littleschemer13532021-05-16
  • Java教程小米推送Java代碼

    小米推送Java代碼

    今天小編就為大家分享一篇關(guān)于小米推送Java代碼,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧...

    富貴穩(wěn)中求8032021-07-12
  • Java教程20個(gè)非常實(shí)用的Java程序代碼片段

    20個(gè)非常實(shí)用的Java程序代碼片段

    這篇文章主要為大家分享了20個(gè)非常實(shí)用的Java程序片段,對(duì)java開發(fā)項(xiàng)目有所幫助,感興趣的小伙伴們可以參考一下 ...

    lijiao5352020-04-06
主站蜘蛛池模板: 果冻传媒在线视频播放观看 | 黄蓉h系列 | 日本成年片高清在线观看 | 国内精品久久久久影院中国 | 女子张腿让男人桶免费 | 亚洲国产天堂在线观看 | 极品手交handjobtattoo | 国产成人精品一区二区仙踪林 | 国产美女屁股直流白浆视频无遮挡 | 久久国产主播福利在线 | 免费观看日本 | 久久天天躁狠狠躁夜夜躁 | 日韩视频一区 | 妹妹你插的我好爽 | 激情三级做爰在线观看激情 | 亚洲系列国产系列 | 四虎影院入口 | 无码AV毛片色欲欧洲美洲 | 四虎国产精品免费久久麻豆 | xxxxxx日本处大片免费看 | 免费高清资源黄网站在线观看 | 激情影院免费观看 | 婷婷色在线观看 | 国产精品理论片在线观看 | 日韩欧美中文字幕出 | 国产日韩欧美在线一二三四 | 7mav视频| 日本人与黑人做爰视频网站 | 日处女b | 亚洲国产精品久久无套麻豆 | bl双性肉文 | 秋霞色| 国自产在线精品免费 | 草莓香蕉绿巨人丝瓜榴莲污在线观看 | 国自产拍在线天天更新91 | 白丝尤物的下面被疯狂蹂躏 | 97精品久久天干天天蜜 | 精品AV亚洲乱码一区二区 | 成人动漫在线免费看 | 超碰97| 亚洲视频在线看 |