SpringBoot 如何快速過濾出一次請求的所有日志?

前言

在現網出現故障時,我們經常需要獲取一次請求流程里的所有日志進行定位。如果請求只在一個線程里處理,則我們可以通過線程ID來過濾日志,但如果請求包含異步線程的處理,那么光靠線程ID就顯得捉襟見肘了。

華為IoT平臺,提供了接收設備上報數據的能力, 當數據到達平臺后,平臺會進行一些復雜的業務邏輯處理,如數據存儲,規則引擎,數據推送,命令下發等等。由于這個邏輯之間沒有強耦合的關系,所以通常是異步處理。如何將一次數據上報請求中包含的所有業務日志快速過濾出來,就是本文要介紹的。

正文

SLF4J日志框架提供了一個MDC(Mapped Diagnostic Contexts)工具類,谷歌翻譯為映射的診斷上下文,從字面上很難理解,我們可以先實戰一把。

public?class?Main?{

????private?static?final?String?KEY?=?"requestId";
????private?static?final?Logger?logger?=?LoggerFactory.getLogger(Main.class);
????
????public?static?void?main(String[]?args)?{

????????//?入口傳入請求ID
????????MDC.put(KEY,?UUID.randomUUID().toString());
????????
????????//?打印日志
????????logger.debug("log?in?main?thread?1");
????????logger.debug("log?in?main?thread?2");
????????logger.debug("log?in?main?thread?3");

????????//?出口移除請求ID
????????MDC.remove(KEY);

????}

}

我們在main函數的入口調用MDC.put()方法傳入請求ID,在出口調用MDC.remove()方法移除請求ID。配置好log4j2.xml文件后,運行main函數,可以在控制臺看到以下日志輸出:

2018-02-17?13:19:52.606?{requestId=f97ea0fb-2a43-40f4-a3e8-711f776857d0}?[main]?DEBUG?cn.wudashan.Main?-?log?in?main?thread?1
2018-02-17?13:19:52.609?{requestId=f97ea0fb-2a43-40f4-a3e8-711f776857d0}?[main]?DEBUG?cn.wudashan.Main?-?log?in?main?thread?2
2018-02-17?13:19:52.609?{requestId=f97ea0fb-2a43-40f4-a3e8-711f776857d0}?[main]?DEBUG?cn.wudashan.Main?-?log?in?main?thread?3

從日志中可以明顯地看到花括號中包含了(映射的)請求ID(requestId),這其實就是我們定位(診斷)問題的關鍵字(上下文)。有了MDC工具,只要在接口或切面植入put()remove()代碼,在現網定位問題時,我們就可以通過grep requestId=xxx *.log快速的過濾出某次請求的所有日志。

進階

然而,MDC工具真的有我們所想的這么方便嗎?回到我們開頭,一次請求可能涉及多線程異步處理,那么在多線程異步的場景下,它是否還能正常運作呢?Talk is cheap, show me the code。

public?class?Main?{

????private?static?final?String?KEY?=?"requestId";
????private?static?final?Logger?logger?=?LoggerFactory.getLogger(Main.class);

????public?static?void?main(String[]?args)?{

????????//?入口傳入請求ID
????????MDC.put(KEY,?UUID.randomUUID().toString());

????????//?主線程打印日志
????????logger.debug("log?in?main?thread");

????????//?異步線程打印日志
????????new?Thread(new?Runnable()?{
????????????@Override
????????????public?void?run()?{
????????????????logger.debug("log?in?other?thread");
????????????}
????????}).start();

????????//?出口移除請求ID
????????MDC.remove(KEY);

????}

}

代碼里我們新起了一個異步線程,并在匿名對象Runnable的run()方法打印日志。運行main函數,可以在控制臺看到以下日志輸出:

2018-02-17?14:05:43.487?{requestId=e6099c85-72be-4986-8a28-de6bb2e52b01}?[main]?DEBUG?cn.wudashan.Main?-?log?in?main?thread
2018-02-17?14:05:43.490?{}?[Thread-1]?DEBUG?cn.wudashan.Main?-?log?in?other?thread

不幸的是,請求ID在異步線程里不打印了。這是怎么回事呢?要解決這個問題,我們就得知道MDC的實現原理。

由于篇幅有限,這里就暫不詳細介紹,MDC之所以在異步線程中不生效是因為底層采用ThreadLocal作為數據結構,我們調用MDC.put()方法傳入的請求ID只在當前線程有效。感興趣的小伙伴可以自己深入一下代碼細節。

知道了原理那么解決這個問題就輕而易舉了,我們可以使用裝飾器模式,新寫一個MDCRunnable類對Runnable接口進行一層裝飾。在創建MDCRunnable類時保存當前線程的MDC值,在執行run()方法時再將保存的MDC值拷貝到異步線程中去。

代碼實現如下:

public?class?MDCRunnable?implements?Runnable?{

????private?final?Runnable?runnable;

????private?final?Map?map;

????public?MDCRunnable(Runnable?runnable)?{
????????this.runnable?=?runnable;
????????//?保存當前線程的MDC值
????????this.map?=?MDC.getCopyOfContextMap();
????}

????@Override
????public?void?run()?{
????????//?傳入已保存的MDC值
????????for?(Map.Entry?entry?:?map.entrySet())?{
????????????MDC.put(entry.getKey(),?entry.getValue());
????????}
????????//?裝飾器模式,執行run方法
????????runnable.run();
????????//?移除已保存的MDC值
????????for?(Map.Entry?entry?:?map.entrySet())?{
????????????MDC.remove(entry.getKey());
????????}
????}
????
}

接著,我們需要對main函數里創建的Runnable實現類進行裝飾:

public?class?Main?{

????private?static?final?String?KEY?=?"requestId";
????private?static?final?Logger?logger?=?LoggerFactory.getLogger(Main.class);
????private?static?final?ExecutorService?EXECUTOR?=?Executors.newSingleThreadExecutor();

????public?static?void?main(String[]?args)?{

????????//?入口傳入請求ID
????????MDC.put(KEY,?UUID.randomUUID().toString());

????????//?主線程打印日志
????????logger.debug("log?in?main?thread");

????????//?異步線程打印日志,用MDCRunnable裝飾Runnable
????????new?Thread(new?MDCRunnable(new?Runnable()?{
????????????@Override
????????????public?void?run()?{
????????????????logger.debug("log?in?other?thread");
????????????}
????????})).start();

????????//?異步線程池打印日志,用MDCRunnable裝飾Runnable
????????EXECUTOR.execute(new?MDCRunnable(new?Runnable()?{
????????????@Override
????????????public?void?run()?{
????????????????logger.debug("log?in?other?thread?pool");
????????????}
????????}));
????????EXECUTOR.shutdown();

????????//?出口移除請求ID
????????MDC.remove(KEY);

????}

}

執行main函數,將會輸出以下日志:

2018-03-04?23:44:05.343?{requestId=5ee2a117-e090-41d8-977b-cef5dea09d34}?[main]?DEBUG?cn.wudashan.Main?-?log?in?main?thread
2018-03-04?23:44:05.346?{requestId=5ee2a117-e090-41d8-977b-cef5dea09d34}?[Thread-1]?DEBUG?cn.wudashan.Main?-?log?in?other?thread
2018-03-04?23:44:05.347?{requestId=5ee2a117-e090-41d8-977b-cef5dea09d34}?[pool-2-thread-1]?DEBUG?cn.wudashan.Main?-?log?in?other?thread?pool

Congratulations!經過我們的努力,最終在異步線程和線程池中都有requestId打印了!

總結

本文講述了如何使用MDC工具來快速過濾一次請求的所有日志,并通過裝飾器模式使得MDC工具在異步線程里也能生效。有了MDC,再通過AOP技術對所有的切面植入requestId,就可以將整個系統的任意流程的日志過濾出來。

使用MDC工具,在開發自測階段,可以極大地節省定位問題的時間,提升開發效率;在運維維護階段,可以快速地收集相關日志信息,加快分析速度。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/718160.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/718160.shtml
英文地址,請注明出處:http://en.pswp.cn/news/718160.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

《自然》:人工智能在創造性思維方面超越人類

發散性思維被認為是創造性思維的指標。ChatGPT-4 在三項有151名人類參與的**發散思維測試中,**展現出比人類更高水平的創造力,結果顯示人工智能在創意領域持續發展。 發散性思維的特點是能夠針對沒有預期解決方案的問題提出獨特的解決方案,例…

TOMCAT的安裝與基本信息

一、TOMCAT簡介 Tomcat 服務器是一個免費的開放源代碼的Web 應用服務器,屬于輕量級應用服務器,在中小型系統和并發訪問用戶不是很多的場合下被普遍使用,是開發和調試JSP 程序的首選。對于一個初學者來說,可以這樣認為&#xff0c…

IO 與 NIO

優質博文:IT-BLOG-CN 一、阻塞IO / 非阻塞NIO 阻塞IO:當一條線程執行read()或者write()方法時,這條線程會一直阻塞直到讀取到了一些數據或者要寫出去的數據已經全部寫出,在這期間這條線程不能做任何其他的事情。 非阻塞NIO&…

記錄踩過的坑-macOS下使用VS Code

目錄 切換主題 安裝插件 搭建Python開發環境 裝Python插件 配置解釋器 打開項目 打開終端 切換主題 安裝插件 方法1 方法2 搭建Python開發環境 裝Python插件 配置解釋器 假設解釋器已經通過Anaconda建好,只需要在VS Code中關聯。 打開項目 打開終端

ArmV8架構

Armv8/armv9架構入門指南 — Armv8/armv9架構入門指南 v1.0 documentation 上面只是給了一個比較好的參考文檔 其他內容待補充

網絡-httpclient調用https服務端繞過證書的方法

httpclient調用https服務端繞過證書的方法 在日常開發或者測試中,通常會遇到需要用httpclient客戶端調用對方http是服務器的場景,由于沒有證書,所以直接是無法調用的。采用下面的方法可以繞過證書驗證: TrustManager[] trustAll…

AutoSAR(基礎入門篇)13.5-Mcal Mcu時鐘的配置

目錄 一、EB的Mcu模塊結構 二、時鐘的配置 對Mcu的配置主要就是其時鐘樹的配置,但是EB將GTM、ERU等很多重要的模塊也都放在了Mcu里面做配置,所以這里的Mcu是一個很龐大的模塊, 我們目前只講時鐘樹的部分 一、EB的Mcu模塊結構 1. 所有的模塊都基本上是這么些配置類別,Mc…

單詞級文本攻擊—論文閱讀

TAAD2.2論文概覽 0.前言1-101.Bridge the Gap Between CV and NLP! A Gradient-based Textual Adversarial Attack Frameworka. 背景b. 方法c. 結果d. 論文及代碼 2.TextHacker: Learning based Hybrid Local Search Algorithm for Text Hard-label Adversarial Attacka. 背景b…

閱讀筆記 | Transformers in Time Series: A Survey

閱讀論文: Wen, Qingsong, et al. “Transformers in time series: A survey.” arXiv preprint arXiv:2202.07125 (2022). 這篇綜述主要對基于Transformer的時序建模方法進行介紹。論文首先簡單介紹了Transformer的基本原理,包括位置編碼、多頭注意力機…

OPENAI SORA:未來視頻創作的新引擎——淺析其背后的人工智能算法

Sora - 探索AI視頻模型的無限可能 隨著人工智能技術的飛速發展,AI視頻模型已成為科技領域的新熱點。而在這個浪潮中,OpenAI推出的首個AI視頻模型Sora,以其卓越的性能和前瞻性的技術,引領著AI視頻領域的創新發展。本文將探討SORA的…

回歸預測 | Matlab實現RIME-BP霜冰算法優化BP神經網絡多變量回歸預測

回歸預測 | Matlab實現RIME-BP霜冰算法優化BP神經網絡多變量回歸預測 目錄 回歸預測 | Matlab實現RIME-BP霜冰算法優化BP神經網絡多變量回歸預測預測效果基本描述程序設計參考資料 預測效果 基本描述 1.Matlab實現RIME-BP霜冰算法優化BP神經網絡多變量回歸預測(完整…

自動化測試介紹、selenium用法(自動化測試框架+爬蟲可用)

文章目錄 一、自動化測試1、什么是自動化測試?2、手工測試 vs 自動化測試3、自動化測試常見誤區4、自動化測試的優劣5、自動化測試分層6、什么項目適合自動化測試 二、Selenuim1、小例子2、用法3、頁面操作獲取輸入內容模擬點擊清空文本元素拖拽frame切換窗口切換/標…

十五 超級數據查看器 講解稿 外觀設置

十五 超級數據查看器 講解稿 外觀設置 視頻講座地址 講解稿全文: 大家好,今天講解超級數據查看器,詳情界面的外觀設置。 首先,我們打開超級數據查看器。 本節課以成語詞典為例來做講述。 我們打開成語詞典這個表,隨便選一條記錄點擊&#x…

AutoSAR(基礎入門篇)13.4-Mcal Dio代碼分析

目錄 一、文件結構 二、動態代碼 1、arxml文件 2、Dio_Cfg.h 3、Dio_PBCfg.c 4、小結

【虛擬機安裝centos7后找不到網卡問題】

最近開始學習linux,看著傳智播客的教學視頻學習,里面老師用的是centos6.5,我這邊裝的是centos7最新版的 結果到了網絡配置的這一節,卡了我好久。 我在centos一直找不到我的網卡eth0,只有一個回環網口,在/…

什么是MVC和MVVM

**MVC和MVVM是兩種流行的軟件架構模式,它們在前端開發中被廣泛采用來組織代碼和管理應用程序的復雜性**。具體如下: MVC(Model-View-Controller): 1. 模型(Model):負責管理數據和業…

軟考高級:主動攻擊和被動攻擊概念和例題

作者:明明如月學長, CSDN 博客專家,大廠高級 Java 工程師,《性能優化方法論》作者、《解鎖大廠思維:剖析《阿里巴巴Java開發手冊》》、《再學經典:《Effective Java》獨家解析》專欄作者。 熱門文章推薦&am…

第五套CCF信息學奧賽c++練習題 CSP-J認證初級組 中小學信奧賽入門組初賽考前模擬沖刺題(選擇題)

第五套中小學信息學奧賽CSP-J考前沖刺題 1、不同類型的存儲器組成了多層次結構的存儲器體系,按存取速度從快到慢排列的是 A、快存/輔存/主存 B、外存/主存/輔存 C、快存/主存/輔存 D、主存/輔存/外存 答案:C 考點分析:主要考查計算機相關知識&…

靜態鏈表(3)

尾插函數 尾插就比頭插多了一步找尾巴,其他均一樣 尾插步驟畫圖 1.找到空閑結點3 2.空鏈踢空點,穿透刪除 先綁后面 再接前面,就完成插入了 綜上所述,靜態鏈表就是處理兩條鏈表,靜態鏈表總的執行一次插入或刪除&#…

Netty NIO ByteBuffer 簡單實驗

1.概要 準備學一下Netty,先從NIO的三大組件開始。先ByteBuffer 2.代碼 2.1 主函數 package com.xjc.springcloundtest;import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.ByteBuffer; import java.nio…