99国产精品欲av蜜臀,可以直接免费观看的AV网站,gogogo高清免费完整版,啊灬啊灬啊灬免费毛片

網易首頁 > 網易號 > 正文 申請入駐

SpringBoot+MDC實現全鏈路調用日志跟蹤,這才叫優雅!

0
分享至

【文章來源】https://sourl.cn/WbZ9W8

之前有一篇文章簡單的介紹過MDC,這次結合具體的案例、生產中的具體問題深入了解一下MDC。

MDC 介紹

1、簡介:

MDC(Mapped Diagnostic Context,映射調試上下文)是log4jlogbacklog4j2提供的一種方便在多線程條件下記錄日志的功能。MDC可以看成是一個與當前線程綁定的哈希表,可以往其中添加鍵值對。MDC 中包含的內容可以被同一線程中執行的代碼所訪問

當前線程的子線程會繼承其父線程中的 MDC 的內容。當需要記錄日志時,只需要從 MDC 中獲取所需的信息即可。MDC 的內容則由程序在適當的時候保存進去。對于一個 Web 應用來說,通常是在請求被處理的最開始保存這些數據。

2、API說明:

  • clear() :移除所有MDC

  • get (String key) :獲取當前線程MDC中指定key的值

  • getContext() :獲取當前線程MDC的MDC

  • put(String key, Object o) :往當前線程的MDC中存入指定的鍵值對

  • remove(String key) :刪除當前線程MDC中指定的鍵值對

3、優點:

代碼簡潔,日志風格統一,不需要在log打印中手動拼寫traceId,即LOGGER.info("traceId:{} ", traceId)。

MDC 使用

1、添加攔截器

public class LogInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//如果有上層調用就用上層的ID
String traceId = request.getHeader(Constants.TRACE_ID);
if (traceId == null) {
traceId = TraceIdUtil.getTraceId();

MDC.put(Constants.TRACE_ID, traceId);
return true;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
//調用結束后刪除
MDC.remove(Constants.TRACE_ID);
}
}

2、修改日志格式

[TRACEID:%X{traceId}] %d{HH:mm:ss.SSS} %-5level %class{-1}.%M()/%L - %msg%xEx%nproperty>

重點是%X{traceId}traceId和MDC中的鍵名稱一致

簡單使用就這么容易,但是在有些情況下traceId將獲取不到。

MDC 存在的問題

  • 子線程中打印日志丟失traceId

  • HTTP調用丟失traceId

丟失traceId的情況,來一個再解決一個,絕不提前優化。

解決 MDC 存在的問題

子線程日志打印丟失traceId

子線程在打印日志的過程中traceId將丟失,解決方式為重寫線程池,對于直接new創建線程的情況不考略【實際應用中應該避免這種用法】,重寫線程池無非是對任務進行一次封裝。

線程池封裝類:ThreadPoolExecutorMdcWrapper.java

public class ThreadPoolExecutorMdcWrapper extends ThreadPoolExecutor {
public ThreadPoolExecutorMdcWrapper(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);

public ThreadPoolExecutorMdcWrapper(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue workQueue, ThreadFactory threadFactory) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
}

public ThreadPoolExecutorMdcWrapper(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue workQueue, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
}

public ThreadPoolExecutorMdcWrapper(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
}

@Override
public void execute(Runnable task) {
super.execute(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
}

@Override
public Future submit(Runnable task, T result) {
return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()), result);
}

@Override
public Future submit(Callable task) {
return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
}

@Override
public Future submit(Runnable task) {
return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
}
}

說明:

  • 繼承ThreadPoolExecutor類,重新執行任務的方法

  • 通過ThreadMdcUtil對任務進行一次包裝

線程traceId封裝工具類:ThreadMdcUtil.java

public class ThreadMdcUtil {
public static void setTraceIdIfAbsent() {
if (MDC.get(Constants.TRACE_ID) == null) {
MDC.put(Constants.TRACE_ID, TraceIdUtil.getTraceId());

public static Callable wrap(final Callable callable, final Map context) {
return () -> {
if (context == null) {
MDC.clear();
} else {
MDC.setContextMap(context);
}
setTraceIdIfAbsent();
try {
return callable.call();
} finally {
MDC.clear();
}
};
}

public static Runnable wrap(final Runnable runnable, final Map context) {
return () -> {
if (context == null) {
MDC.clear();
} else {
MDC.setContextMap(context);
}
setTraceIdIfAbsent();
try {
runnable.run();
} finally {
MDC.clear();
}
};
}
}

說明【以封裝Runnable為例】:

  • 判斷當前線程對應MDC的Map是否存在,存在則設置

  • 設置MDC中的traceId值,不存在則新生成,針對不是子線程的情況,如果是子線程,MDC中traceId不為null

  • 執行run方法

代碼等同于以下寫法,會更直觀

public static Runnable wrap(final Runnable runnable, final Map context) {
return new Runnable() {
@Override
public void run() {
if (context == null) {
MDC.clear();
} else {
MDC.setContextMap(context);
setTraceIdIfAbsent();
try {
runnable.run();
} finally {
MDC.clear();


重新返回的是包裝后的Runnable,在該任務執行之前【runnable.run()】先將主線程的Map設置到當前線程中【 即MDC.setContextMap(context)】,這樣子線程和主線程MDC對應的Map就是一樣的了。

HTTP調用丟失traceId

在使用HTTP調用第三方服務接口時traceId將丟失,需要對HTTP調用工具進行改造,在發送時在request header中添加traceId,在下層被調用方添加攔截器獲取header中的traceId添加到MDC中。

HTTP調用有多種方式,比較常見的有HttpClientOKHttpRestTemplate,所以只給出這幾種HTTP調用的解決方式。

1、HttpClient:

實現HttpClient攔截器:

public class HttpClientTraceIdInterceptor implements HttpRequestInterceptor {
@Override
public void process(HttpRequest httpRequest, HttpContext httpContext) throws HttpException, IOException {
String traceId = MDC.get(Constants.TRACE_ID);
//當前線程調用中有traceId,則將該traceId進行透傳
if (traceId != null) {
//添加請求體
httpRequest.addHeader(Constants.TRACE_ID, traceId);

實現HttpRequestInterceptor接口并重寫process方法

如果調用線程中含有traceId,則需要將獲取到的traceId通過request中的header向下透傳下去。

HttpClient添加攔截器:

private static CloseableHttpClient httpClient = HttpClientBuilder.create()
.addInterceptorFirst(new HttpClientTraceIdInterceptor())
.build();

通過addInterceptorFirst方法為HttpClient添加攔截器。

2、OKHttp:

實現OKHttp攔截器:

public class OkHttpTraceIdInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
String traceId = MDC.get(Constants.TRACE_ID);
Request request = null;
if (traceId != null) {
//添加請求體
request = chain.request().newBuilder().addHeader(Constants.TRACE_ID, traceId).build();
Response originResponse = chain.proceed(request);

return originResponse;
}
}

實現Interceptor攔截器,重寫interceptor方法,實現邏輯和HttpClient差不多,如果能夠獲取到當前線程的traceId則向下透傳。

為OkHttp添加攔截器:

private static OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(new OkHttpTraceIdInterceptor())
.build();

調用addNetworkInterceptor方法添加攔截器。

3、RestTemplate:

實現RestTemplate攔截器:

public class RestTemplateTraceIdInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
String traceId = MDC.get(Constants.TRACE_ID);
if (traceId != null) {
httpRequest.getHeaders().add(Constants.TRACE_ID, traceId);

return clientHttpRequestExecution.execute(httpRequest, bytes);
}
}

實現ClientHttpRequestInterceptor接口,并重寫intercept方法,其余邏輯都是一樣的不重復說明。

為RestTemplate添加攔截器:

restTemplate.setInterceptors(Arrays.asList(new RestTemplateTraceIdInterceptor()));

調用setInterceptors方法添加攔截器。

4、第三方服務攔截器:

HTTP調用第三方服務接口全流程traceId需要第三方服務配合,第三方服務需要添加攔截器拿到request header中的traceId并添加到MDC中。

public class LogInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//如果有上層調用就用上層的ID
String traceId = request.getHeader(Constants.TRACE_ID);
if (traceId == null) {
traceId = TraceIdUtils.getTraceId();

MDC.put("traceId", traceId);
return true;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
MDC.remove(Constants.TRACE_ID);
}
}

說明:

  • 先從request header中獲取traceId

  • 從request header中獲取不到traceId則說明不是第三方調用,直接生成一個新的traceId

  • 將生成的traceId存入MDC中

除了需要添加攔截器之外,還需要在日志格式中添加traceId的打印,如下:

[TRACEID:%X{traceId}] %d{HH:mm:ss.SSS} %-5level %class{-1}.%M()/%L - %msg%xEx%nproperty>

注意:需要添加%X{traceId}

END

2022年Java原創面試題庫連載中

更多內容,點擊上方名片查看

特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相關推薦
熱點推薦
75歲港星宣布征婚,自曝37歲兒子內地求學失敗,回家躺平需要他養

75歲港星宣布征婚,自曝37歲兒子內地求學失敗,回家躺平需要他養

探源歷史
2025-07-21 07:29:49
港媒爆料趙雅芝日子挺苦的,被3個兒子長期啃老,71歲還不能老!

港媒爆料趙雅芝日子挺苦的,被3個兒子長期啃老,71歲還不能老!

木子愛娛樂大號
2025-07-05 09:37:33
又現跨省聯動:汕尾市長調往揚州,揚州市長調往寧德

又現跨省聯動:汕尾市長調往揚州,揚州市長調往寧德

觀察者網
2025-07-23 14:39:26
新華社快訊:“十四五”期間,全國公安機關共破獲電信網絡詐騙案件173.9萬起

新華社快訊:“十四五”期間,全國公安機關共破獲電信網絡詐騙案件173.9萬起

新華社
2025-07-23 15:27:09
杭州偷水男“社會性死亡”:正臉曝光,工作毀了,連公司都被牽連

杭州偷水男“社會性死亡”:正臉曝光,工作毀了,連公司都被牽連

嘆知
2025-07-22 15:45:43
汪峰組織兩家人日本度假,森林北9歲女兒首曝光,長得比醒醒漂亮

汪峰組織兩家人日本度假,森林北9歲女兒首曝光,長得比醒醒漂亮

瓜農娟姐
2025-07-23 18:29:01
公職人員下班后兼職送三小時外賣:“像打游戲做任務”一樣快樂|封面頭條

公職人員下班后兼職送三小時外賣:“像打游戲做任務”一樣快樂|封面頭條

封面新聞
2025-07-22 15:48:22
或許遺憾才是人生的常態:C羅本不會離開皇馬,更不應在尤文虛度

或許遺憾才是人生的常態:C羅本不會離開皇馬,更不應在尤文虛度

K唐伯虎
2025-07-21 07:19:39
470000人集體大逃亡!以色列萬萬沒想到歷史會又一次倒置重演

470000人集體大逃亡!以色列萬萬沒想到歷史會又一次倒置重演

南方健哥
2025-07-23 18:37:44
山西、陜西兩地2025年養老金調整都漲2%嗎?養老金4000元漲80元?

山西、陜西兩地2025年養老金調整都漲2%嗎?養老金4000元漲80元?

文雅筆墨
2025-07-23 17:09:26
酷玩樂隊演唱會“出軌”的女方:來自百億老錢家族,捐贈大樓給哈佛大學

酷玩樂隊演唱會“出軌”的女方:來自百億老錢家族,捐贈大樓給哈佛大學

封面新聞
2025-07-22 19:48:45
重慶雙胞胎兄弟分別被清華、北大錄取

重慶雙胞胎兄弟分別被清華、北大錄取

封面新聞
2025-07-23 14:55:04
“絕經和出道同時來?”上海街頭驚現她的巨幅海報!網友:笑著笑著就哭了

“絕經和出道同時來?”上海街頭驚現她的巨幅海報!網友:笑著笑著就哭了

環球網資訊
2025-07-23 10:48:19
遂寧市紀委回應“公職人員兼職送外賣”:已了解該情況

遂寧市紀委回應“公職人員兼職送外賣”:已了解該情況

極目新聞
2025-07-23 11:17:30
郭麒麟,正式開除德云社。

郭麒麟,正式開除德云社。

會說話的舌
2025-07-18 13:40:49
一聲巨響!俄羅斯轟炸波蘭企業,波蘭頭上懸了3年的劍終于落地了

一聲巨響!俄羅斯轟炸波蘭企業,波蘭頭上懸了3年的劍終于落地了

文雅筆墨
2025-07-21 09:10:43
大火燒毀獲賠償,重建2000平洛杉磯豪宅,易建聯要花多少錢?

大火燒毀獲賠償,重建2000平洛杉磯豪宅,易建聯要花多少錢?

東球弟
2025-07-23 14:12:13
小姑子在嫂子訂婚宴上搶走三金,果斷退婚:不敢嫁“強盜”

小姑子在嫂子訂婚宴上搶走三金,果斷退婚:不敢嫁“強盜”

惟來
2025-07-22 18:20:02
足協杯-申花vs河南首發:4外援PK5外援 吳曦、蔣圣龍、王上源先發

足協杯-申花vs河南首發:4外援PK5外援 吳曦、蔣圣龍、王上源先發

直播吧
2025-07-23 17:36:13
三伏天是男人進補的黃金期,多吃3道“扶陽菜”,精力充沛身體壯

三伏天是男人進補的黃金期,多吃3道“扶陽菜”,精力充沛身體壯

鬼菜生活
2025-07-23 17:38:39
2025-07-23 20:07:00
Meta
Meta
關注java進階架構師送架構
1059文章數 9856關注度
往期回顧 全部

科技要聞

別自嗨了!XREAL徐馳:AI眼鏡只有5歲智商

頭條要聞

印度、孟加拉關切雅魯藏布江下游水電站工程 中方回應

頭條要聞

印度、孟加拉關切雅魯藏布江下游水電站工程 中方回應

體育要聞

英格蘭最紅球星 也是加勒比島國驕傲

娛樂要聞

汪峰森林北同游日本 各帶各娃互不耽誤

財經要聞

律師解析娃哈哈遺產案:遺囑是最大變數

汽車要聞

德系大招放盡 場地極限測試全新奧迪A5L

態度原創

房產
本地
時尚
健康
公開課

房產要聞

海南自由貿易港全島封關,2025年12月18日正式啟動!

本地新聞

這雙丑鞋“泰”辣眼,跪求內娛不要抄作業

看來看去還是這些穿搭適合普通人!配色不艷、衣服不花,好得體

呼吸科專家破解呼吸道九大謠言!

公開課

李玫瑾:為什么性格比能力更重要?

無障礙瀏覽 進入關懷版 主站蜘蛛池模板: 青冈县| 即墨市| 南平市| 洪湖市| 阿拉尔市| 汤阴县| 汪清县| 广饶县| 安阳市| 灵宝市| 揭阳市| 贵定县| 新沂市| 兰溪市| 泽州县| 油尖旺区| 乐清市| 砚山县| 乐都县| 白河县| 大英县| 寻乌县| 杭锦后旗| 永泰县| 新丰县| 舞钢市| 吉木萨尔县| 静乐县| 新丰县| 东源县| 桓台县| 黎平县| 荣昌县| 浦城县| 日照市| 南部县| 昆明市| 龙门县| 襄汾县| 兴文县| 中山市|