logback的MDC機(jī)制

logback的MDC機(jī)制

1. MDC 介紹:

? MDC(Mapped Diagnostic Context,映射調(diào)試上下文)磅废,即將一些運(yùn)行時(shí)的上下文數(shù)據(jù)通過logback打印出來纳像,是 一種方便在多線程條件下記錄日志的功能。和SiftingAppender一起拯勉,可以實(shí)現(xiàn)根據(jù)運(yùn)行時(shí)的上下文數(shù)據(jù)爹耗,將日志保存到不同的文件中。

2. demo:

2.1 logback配置文件:logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder charset="UTF-8">
            <pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level [%class:%line] -  %X{logFileName} %X{logSign} %msg%n</pattern>
        </encoder>
    </appender>

    <appender name="FILE-INSERT" class="ch.qos.logback.classic.sift.SiftingAppender">
        <discriminator>
            <key>logFileName</key>
            <defaultValue>defName</defaultValue>
        </discriminator>
        <sift>
            <appender name="FILE-${logFileName}" class="ch.qos.logback.core.rolling.RollingFileAppender">
                <File>
                    D:/temp/downloadLogs/${logFileName}/gps.log
                </File>
                <layout class="ch.qos.logback.classic.PatternLayout">
                    <pattern>%msg%n</pattern>
                </layout>
                <append>true</append>
                <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                    <fileNamePattern>
                        D:/temp/downloadLogs/${logFileName}/gps-%d{yyyyMMdd}.log
                    </fileNamePattern>
                    <maxHistory>2</maxHistory>
                </rollingPolicy>
            </appender>
        </sift>
    </appender>

    <logger name="com.chelaile.log.insert" level = "INFO">
        <appender-ref ref="FILE-INSERT"/>
        <appender-ref ref="console"/>
    </logger>

</configuration>

2.2 寫文件的線程:

package com.chelaile.log.insert;

import org.slf4j.MDC;

import java.util.Map;

public abstract class MdcRunnable implements Runnable {

    /**
     * 為了線程池中的線程在復(fù)用的時(shí)候也能獲得父線程的MDC中的信息谜喊,
     * 子線程第一次初始化的時(shí)候沒事,因?yàn)橥ㄟ^ThreadLocal
     * 已經(jīng)可以獲得MDC中的內(nèi)容了
     */
    private final Map mdcContext = MDC.getCopyOfContextMap();
    //ExecutorService.execute(new Runnable())的時(shí)候倦始,在Runnable構(gòu)造的時(shí)候斗遏,用這個(gè)方法得到一個(gè)Map,保存起來鞋邑,這時(shí)的context是父線程的诵次。
    //然后在執(zhí)行run方法的時(shí)候账蓉,放到MDC中去——子線程的context map中去

    @Override
    public final void run() {
        // 線程重用的時(shí)候,把父線程中的context map內(nèi)容帶入當(dāng)前線程的context map中逾一,
        // 因?yàn)榫€程已經(jīng)初始化過了铸本,不會(huì)像初始化時(shí)那樣通過拷貝父線程ThreadLocal到子線程
        // 的ThreadLocal操作來完成線程間context map的傳遞。
        // 真正執(zhí)行到這個(gè)run方法的時(shí)候遵堵,已經(jīng)到了子線程中了箱玷,所以要在初始化的時(shí)候用
        // MDC.getCopyOfContextMap()來獲得父線程contest map,那時(shí)候還在父線程域中
        if (mdcContext != null) {
            MDC.setContextMap(mdcContext);//有了這一步其實(shí)不用MDC.clear陌宿,因?yàn)檫@一步會(huì)將子線程中的context map重置為父線程的context map
//            //源代碼:
//            public void setContextMap(Map<String, String> contextMap) {
//                lastOperation.set(WRITE_OPERATION);
//                //將原來的
//                Map<String, String> newMap = Collections.synchronizedMap(new HashMap<String, String>());
//                newMap.putAll(contextMap);
//
//                // the newMap replaces the old one for serialisation's sake
//                copyOnThreadLocal.set(newMap);
//            }
        }
        try {
            runWithMdc();
        } finally {
//            MDC.clear();//如果都是用new Thread方法建立的線程可以不用加锡足,因?yàn)橹缶€程會(huì)消亡。
            //如果用ThreadPool線程池的話壳坪,線程是可以重用的舶得,如果之前的線程的MDC內(nèi)容沒有清除掉的話,
            // 再次重線程池中獲取到這個(gè)線程爽蝴,會(huì)取出之前的數(shù)據(jù)(臟數(shù)據(jù))沐批,會(huì)導(dǎo)致一些不可預(yù)期的錯(cuò)誤,
            // 所以當(dāng)前線程結(jié)束后一定要清掉蝎亚。
        }
    }

    protected abstract void runWithMdc();
}
package com.chelaile.log.insert;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

public class LogRunnable extends MdcRunnable {

    private volatile boolean flag = true;

    private static final Logger log = LoggerFactory.getLogger(LogRunnable.class);

    private String code;

    // 阻塞隊(duì)列
    private LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<>();

    public LogRunnable(String code) {
        this.code = code;
    }

    @Override
    protected void runWithMdc() {
        String str;
        System.out.println("code=" + code + ",啟動(dòng),logFileName=" + MDC.get("logFileName"));
        while (flag || queue.size() > 0) {
            try {
                str = queue.poll(5, TimeUnit.SECONDS);
                if (str == null || "null".equals(str) || "".equals(str)) {
                    continue;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                continue;
            }
            log.info(str);
            MDC.put("logFileName",code);
        }
        System.out.println("code=" + code + ",結(jié)束,logFileName=" + MDC.get("logFileName"));
    }

    public void close() {
        this.flag = false;
    }

    public void setQueue(String str) {
        queue.add(str);
    }

    public String getCode() {
        return code;
    }
}

2.3 測(cè)試代碼:

package com.chelaile.test;

import com.chelaile.SpringStart;
import com.chelaile.log.insert.LogRunnable;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.MDC;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.*;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = SpringStart.class)
public class LogRunnableTest {

    private Map<Integer, LogRunnable> logRunnableMap = new HashMap<>();

    @Test
    public void testLog() {
        ExecutorService logExecService = Executors.newFixedThreadPool(1);
        MDC.put("logFileName","默認(rèn)");
        MDC.put("logSign","標(biāo)記");

        for (int i = 0; i < 5; i++) {
            LogRunnable logRunnable = new LogRunnable("test" + i);
            logExecService.execute(logRunnable);
            logRunnableMap.put(i,logRunnable);
        }
        for (int i = 0; i < 5; i++) {
            LogRunnable logRunnable = logRunnableMap.get(i);
            logRunnable.setQueue("測(cè)試數(shù)據(jù)" + i);
            logRunnable.setQueue("我是測(cè)試" + i);
            logRunnable.setQueue("測(cè)試測(cè)試" + i);
            logRunnable.close();
        }
        while(true){} //測(cè)試的時(shí)候九孩,不加  while(true){}  的話,由于主線程結(jié)束颖对,日志沒有寫入文件線程就終止了
    }

}

2.4輸出:

2.4.1控制臺(tái)輸出:
code=test0,啟動(dòng),logFileName=默認(rèn)
2018-07-19 15:44:12 INFO  [com.chelaile.log.insert.LogRunnable:38] -  默認(rèn) 標(biāo)記 測(cè)試數(shù)據(jù)0
2018-07-19 15:44:12 INFO  [com.chelaile.log.insert.LogRunnable:38] -  test0 標(biāo)記 我是測(cè)試0
2018-07-19 15:44:12 INFO  [com.chelaile.log.insert.LogRunnable:38] -  test0 標(biāo)記 測(cè)試測(cè)試0
code=test0,結(jié)束,logFileName=test0
code=test1,啟動(dòng),logFileName=默認(rèn)
2018-07-19 15:44:12 INFO  [com.chelaile.log.insert.LogRunnable:38] -  默認(rèn) 標(biāo)記 測(cè)試數(shù)據(jù)1
2018-07-19 15:44:12 INFO  [com.chelaile.log.insert.LogRunnable:38] -  test1 標(biāo)記 我是測(cè)試1
2018-07-19 15:44:12 INFO  [com.chelaile.log.insert.LogRunnable:38] -  test1 標(biāo)記 測(cè)試測(cè)試1
code=test1,結(jié)束,logFileName=test1
code=test2,啟動(dòng),logFileName=默認(rèn)
2018-07-19 15:44:12 INFO  [com.chelaile.log.insert.LogRunnable:38] -  默認(rèn) 標(biāo)記 測(cè)試數(shù)據(jù)2
2018-07-19 15:44:12 INFO  [com.chelaile.log.insert.LogRunnable:38] -  test2 標(biāo)記 我是測(cè)試2
2018-07-19 15:44:12 INFO  [com.chelaile.log.insert.LogRunnable:38] -  test2 標(biāo)記 測(cè)試測(cè)試2
code=test2,結(jié)束,logFileName=test2
code=test3,啟動(dòng),logFileName=默認(rèn)
2018-07-19 15:44:12 INFO  [com.chelaile.log.insert.LogRunnable:38] -  默認(rèn) 標(biāo)記 測(cè)試數(shù)據(jù)3
2018-07-19 15:44:12 INFO  [com.chelaile.log.insert.LogRunnable:38] -  test3 標(biāo)記 我是測(cè)試3
2018-07-19 15:44:12 INFO  [com.chelaile.log.insert.LogRunnable:38] -  test3 標(biāo)記 測(cè)試測(cè)試3
code=test3,結(jié)束,logFileName=test3
code=test4,啟動(dòng),logFileName=默認(rèn)
2018-07-19 15:44:12 INFO  [com.chelaile.log.insert.LogRunnable:38] -  默認(rèn) 標(biāo)記 測(cè)試數(shù)據(jù)4
2018-07-19 15:44:12 INFO  [com.chelaile.log.insert.LogRunnable:38] -  test4 標(biāo)記 我是測(cè)試4
2018-07-19 15:44:12 INFO  [com.chelaile.log.insert.LogRunnable:38] -  test4 標(biāo)記 測(cè)試測(cè)試4
code=test4,結(jié)束,logFileName=test4
2.4.2生成的日志目錄結(jié)構(gòu):
日志目錄結(jié)構(gòu)

3.遇到的問題:

? 第一次logback.xml文件沒有配置layout捻撑,因?yàn)橹幌雽懭雓afka中的數(shù)據(jù),不想加多余的東西缤底,導(dǎo)致測(cè)試內(nèi)容沒能寫入文件顾患,加上之后就OK了

<layout class="ch.qos.logback.classic.PatternLayout">
    <pattern>%msg%n</pattern>
</layout>

4. MDC分析 :

Slf4j MDC :

? 對(duì)外接口,就是對(duì)參數(shù)進(jìn)行校驗(yàn)个唧,然后調(diào)用MDCAdapter的方法實(shí)現(xiàn)江解。

Logback MDC :

? LogbackMDCAdapter類實(shí)現(xiàn)MDCAdapter接口,實(shí)現(xiàn) put徙歼、get犁河、remove等方法。

? copyOnThreadLocal:存儲(chǔ)每個(gè)線程的多個(gè)變量

? 當(dāng)在logback.xml中配置了%X{key} 或 SiftingAppender的<discriminator>的<key>魄梯,在需要輸出日志的時(shí)候桨螺,從MDC中獲取對(duì)應(yīng)的key值,然后append到日志字符串中或生成文件路徑酿秸,然后輸出灭翔。

5. ThreadLocal 實(shí)現(xiàn)分析:

? 在每個(gè)線程Thread內(nèi)部有一個(gè)ThreadLocal.ThreadLocalMap類型的成員變量threadLocals,這個(gè)threadLocals就是用來存儲(chǔ)實(shí)際的變量副本的辣苏,鍵值為當(dāng)前ThreadLocal變量肝箱,value為變量副本(即T類型的變量)哄褒。

/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

? 初始時(shí),在Thread里面煌张,threadLocals為空呐赡,當(dāng)通過ThreadLocal變量調(diào)用get()方法或者set()方法,就會(huì)對(duì)Thread類中的threadLocals進(jìn)行初始化骏融,并且以當(dāng)前ThreadLocal變量為鍵值链嘀,以ThreadLocal要保存的副本變量為value,存到threadLocals绎谦。

ThreadLocal類:

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);//key用this管闷,是因?yàn)橥粋€(gè)線程中如果有多個(gè)ThreadLocal,都會(huì)存儲(chǔ)在該map中
        else
            createMap(t, value);
    }

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }
ThreadLocal總結(jié):

1.實(shí)際的通過ThreadLocal創(chuàng)建的副本是存儲(chǔ)在每個(gè)線程自己的threadLocals中的窃肠;

2.為何threadLocals的類型ThreadLocalMap的鍵值為ThreadLocal對(duì)象包个,因?yàn)槊總€(gè)線程中可有多個(gè)threadLocal變量

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市冤留,隨后出現(xiàn)的幾起案子碧囊,更是在濱河造成了極大的恐慌,老刑警劉巖纤怒,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件糯而,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡泊窘,警方通過查閱死者的電腦和手機(jī)熄驼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來烘豹,“玉大人瓜贾,你說我怎么就攤上這事⌒酰” “怎么了祭芦?”我有些...
    開封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)憔鬼。 經(jīng)常有香客問我龟劲,道長(zhǎng),這世上最難降的妖魔是什么轴或? 我笑而不...
    開封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任昌跌,我火速辦了婚禮,結(jié)果婚禮上照雁,老公的妹妹穿的比我還像新娘避矢。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開白布审胸。 她就那樣靜靜地躺著,像睡著了一般卸勺。 火紅的嫁衣襯著肌膚如雪砂沛。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天曙求,我揣著相機(jī)與錄音碍庵,去河邊找鬼。 笑死悟狱,一個(gè)胖子當(dāng)著我的面吹牛静浴,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播挤渐,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼苹享,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了浴麻?” 一聲冷哼從身側(cè)響起理肺,我...
    開封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤栗柒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體摩桶,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年词疼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了饮怯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡榛泛,死狀恐怖蝌蹂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情挟鸠,我是刑警寧澤叉信,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站艘希,受9級(jí)特大地震影響硼身,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜覆享,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一佳遂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧撒顿,春花似錦丑罪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽跪另。三九已至,卻和暖如春煤搜,著一層夾襖步出監(jiān)牢的瞬間免绿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來泰國打工擦盾, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留嘲驾,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓迹卢,卻偏偏與公主長(zhǎng)得像辽故,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子腐碱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理誊垢,服務(wù)發(fā)現(xiàn),斷路器喻杈,智...
    卡卡羅2017閱讀 134,659評(píng)論 18 139
  • 原文地址為 https://github.com/logstash/logstash-logback-encode...
    飛來來閱讀 18,495評(píng)論 1 10
  • 姓名:周小蓬 16019110037 轉(zhuǎn)載自:http://blog.csdn.net/YChenFeng/art...
    aeytifiw閱讀 34,722評(píng)論 13 425
  • 記得迷迷糊糊是凌晨后才睡著彤枢,可其實(shí)10點(diǎn)半我就在準(zhǔn)備睡覺了…可現(xiàn)在才3點(diǎn)40已經(jīng)被爸媽上廁所的噪音弄醒后再也睡不下...
    H2閱讀 204評(píng)論 0 1