1. 程式人生 > >日誌列印的規範以及實現方式

日誌列印的規範以及實現方式

1. WHY

為什麼要做日誌?

  • 原因1:跟蹤應用的警告和錯誤,標識程式執行中的危險操作、錯誤操作,進而便於在出現問題時排查問題
  • 原因2:跟蹤崩潰bug,在開發過程中,日誌可以幫助開發者和軟體測試人員跟蹤程式崩潰的原因
  • 原因3:跟蹤效能下降的問題範圍,產品所反映出來的效能問題,很難在開發過程中暴露出來,這需要進行全方位的測試跟蹤,而通過日誌提供的詳細執行時間記錄可以很方便的找出應用的效能瓶頸
  • 原因4:跟蹤操作流程,通過對日誌跟蹤,你可以獲取操作發生的具體環境,操作的結果
  • 補充原因:
    1. 瞭解專案的執行狀態
    2. 發現潛在的效能問題
    3. 價值化(大資料分析)

2. WHICH

日誌都有什麼級別?

日誌列印通常有四種級別,從高到低分別是:ERROR、WARN、INFO、DEBUG


2.1. ERROR

  • ERROR:該級別的錯誤需要馬上被處理,當ERROR錯誤發生時,已經影響了使用者的正常訪問,是需要馬上得到管理員介入並處理的。常見的ERROR異常有:空指標異常,資料庫不可用,關鍵路徑的用例無法繼續執行等。

2.2. WARN

  • WARN:對於WARN級別的日誌,雖然不需要管理員馬上處理,但是也需要引起重視。WARN日誌有兩種級別:一個是解決方案存在明顯的問題(例如Try Catch語句中由於不確定會出現什麼異常,而用Exception統一捕獲丟擲的問題),另一個是潛在的問題和建議(例如系統性能可能會伴隨著時間的遷移逐漸不能滿足服務需要)。應用程式可以容忍這些資訊,不過它們應該被及時地檢查及修復。某些使用者危險的操作,例如一直採用錯誤密碼嘗試登陸管理員賬號的行為,也可以提升到WARN日誌級別記錄。

2.3. INFO

  • INFO:主要用於記錄系統執行狀態、使用者的操作行為等資訊。該日誌級別,常用於反饋系統當前狀態給終端使用者。

2.4. DEBUG

  • DEBUG:該級別日誌的主要作用是對系統每一步的執行狀態進行精確的記錄。通過該種日誌,可以檢視某一個操作每一步的執行過程,可以準確定位是何種操作,何種引數,何種順序導致了某種錯誤的發生。可以保證在不重現錯誤的情況下,也可以通過DEBUG級別的日誌記錄對問題進行診斷。一般來說,在生產環境中,不會輸出該級別的日誌。

2.5 其他說明

  • ERROR是錯誤的意思,但不代表出現異常的地方就該打ERROR。


    ERROR是相對程式正確執行來說的,如果出現了ERROR那就代表出問題了,開發人員必須要查一下原因,或許是程式問題,或許是環境問題,或許是理論上不該出錯的地方出錯了。總之,如果你覺得某個地方出問題時需要解決,就打ERROR,如果不需要解決就不要打ERROR。
    舉例而言:

    如果有一個介面。呼叫者傳過來的引數不在你的接受範圍內,在這種情況下你不能打ERROR,因為傳什麼值是使用者決定的,並不影響程式正確執行。想象一下,如果你的伺服器上有監控程式的話,檢測到ERROR或WARN就報警,引數錯誤你也打ERROR,那運維人員會瘋掉的。
    這種呼叫者傳入的介面引數錯誤問題,就應該打個INFO了,呼叫者說你的介面總是返回錯誤程式碼,你可以告訴他,是他的哪個引數傳錯了。

  • WARN是指出現了不影響程式正確執行的問題,WARN也是問題但不影響程式正常執行,如果WARN出現的過於頻繁或次數太多,那就代表你要檢查一下程式或環境或依賴程式是否真的出問題了。

    假如你訪問一個介面,設定了一個超時,超時之後會拋異常,你在try塊裡不該打ERROR也不該打INFO來無視它,這時你應該打WARN,緊緊是警告一下,如果超時過多那就該檢查一下了,是不是對方介面有問題了或者是網路環境出問題了。

  • INFO和DEBUG就是指一般的資訊了。在程式出問題時,如果這條log可以幫助你分析問題或檢視程式的執行情況,那就應該打個INFO。如果僅僅是為了在除錯階段檢視程式是否執行正確那就要打DEBUG。

3. What

日誌該列印什麼資訊?

  • 時間 程序|執行緒 級別 模組 Filter 操作者 入參 內容

    • 時間,包含時區資訊和毫秒,建議使用ISO-8601時間格式
    • 程序ID:
    • 執行緒ID:執行緒ID相當重要,在解決多執行緒問題的時候,這一項資訊是必不可少的
    • 級別,例如 warn、info 和 error
    • 模組:哪個類,假如是分散式服務的話,需要指明是哪個服務的哪個類
    • Filter:主要用於查詢某一類LOG時候方便,這個Filter是開發人員在開發過程中自己定義的,可以是
      • 開發人員:便於尋找程式碼中的問題
      • 請求會話話標識:便於追蹤使用者的操作行為歷史
    • 內容:LOG資訊

    以下內容也在列印考慮範圍內:

    1. 會話標識。能知道是哪個客戶端或者是哪個使用者觸發、登陸賬號、seesion資訊等。
    2. 其他資訊:
      場景資訊(誰,什麼功能等);
      狀態資訊(開始,中斷,結束)以及重要引數;
      版本號、執行緒號等。
  • 日誌格式:日誌格式統一使用主語+謂語+賓語+狀語的格式,日誌列印應該遵從人類的自然語言,任何沒有開發經驗或是外行人都能從你的日誌中捕獲到有用的資訊。

    • 主語:就是會話的發起者
    • 謂語:就是這條日誌將要具體進行什麼樣的操作
    • 賓語:行為物件
    • 狀語:行為產生的結果
  • 可以加入分隔符,使用分隔符使每個域都能夠清晰識別,分隔符可以提高日誌可分析性。

  • 使用[]進行引數變數隔離,這樣的格式寫法,可讀性更好,對於排查問題更有幫助


3.1. 需要列印的ERROR級別的日誌

  • 主要型別有:
  1. 讀寫配置檔案失敗
  2. 網路斷線
  3. 所有第三方對接的異常(包括第三方返回錯誤碼)
  4. 所有影響功能使用的異常

3.2. 需要列印的WARN級別的日誌

  • 異常:由於在程式執行之前不能明確異常引發的原因,異常只進行了簡單的捕獲丟擲,需要將這種籠統處理的異常列印為WARN格式的日誌,提醒管理員干預處理
  • 有容錯機制的時候出現的錯誤情況
  • 找不到配置檔案,但是系統能自動建立配置檔案
  • 效能即將接近臨界值的時候
  • 業務異常的記錄,危險操作

3.3. 需要列印的INFO級別的日誌

  • Request && Response
  • 系統操作行為:讀寫檔案、定時任務等
  • 不符合業務邏輯預期:列印關鍵的引數,要能從這些引數中清楚地看出,誰的操作與預期不符,為什麼與預期不符。並且唯一定位到這條日誌,要包含使用者id或者流水號
  • 對外提供的介面入口處:列印介面的唯一標識和簡短描述,並且要將呼叫方傳入的引數原樣打印出來,這樣當系統出現問題時,就能很容易的判斷出是否是呼叫方出現了問題
  • 呼叫其它系統介面的前後:列印所呼叫介面的系統名稱/介面名稱和傳入引數/響應引數,這樣能方便做問題定界,通過這兩條日誌可以清楚地看出是否是所呼叫的系統出現了問題
  • 系統模組的入口與出口處:可以是重要方法級或模組級,記錄它的輸入與輸出,方便定位
  • 非預期執行:為程式在“有可能”執行到的地方列印日誌
    • switch case語句塊中的default
    • if…else if…else中很少出現的else情況
    • try catch語句塊中catch分支。
  • 服務狀態變化(儘可能記錄線索):程式中重要的狀態資訊的變化應該記錄下來,方便查問題時還原現場,推斷程式執行過程
  • 一些可能很耗時的業務處理:批處理,IO操作
    • 程式執行耗時:通過它可以跟蹤為什麼系統響應變慢或者太快
    • 大批量資料的執行進度

3.4. 需要列印的DEBUG級別的日誌

注意: 在生產環境中不能列印DEBUG級別的日誌,DEBUG級別的日誌只能用於開發除錯或測試環節,同時在輸出DEBUG級別的日誌的時候,也應該依據專案組的開發需求列印日誌,儘量做到:

  1. 開發人員和測試人員都能看懂
  2. 通過閱讀DEBUG級別的日誌後不需要重現問題,就能準確的定位解決問題

4. WHERE

  1. 程式入口:在入口列印日誌是因為這個時候傳遞進來的引數沒有經過任何處理,將它列印在日誌檔案中能一眼就知道程式的原始資料是否符合我們的預期,是不是傳遞進來的原始資料就出現 的問題。
  2. 計算結果,測試關心的程式的輸出結果是否符合預期,那麼對於計算過程不應該關心,僅給出計算結果就能判斷是否符合預期。
  3. 重要資訊:這一點可能很寬泛,因為不同的業務邏輯重點可能並不一樣,例如在有的重要引數不能為空,此時就需要判斷是否為空,如果為空則記錄到日誌中;還有的例如傳遞進來的引數經過一系列的演算法處理過後,此時也需要列印日誌來檢視是否計算正確。但切記,儘量不要直接在for迴圈中列印日誌,特別是for迴圈特別大時,這樣你的日誌可能分分鐘被衝得不見蹤跡,甚至帶來效能上的影響。
  4. 異常捕獲:在異常打印出詳細的日誌能讓你快速定位錯誤在哪裡,例如在程式丟擲異常捕獲時,在平時我們經常就是直接在控制檯打印出堆疊資訊e.printStackTrace(),但在實際的生產環境更加艱苦,更別說有IDE來讓你檢視控制檯資訊,此時就需要我們將堆疊資訊記錄在日誌中,以便發生異常時我們能準確定位程式在哪裡出錯。

4.1常見的日誌列印處

  1. 函式開始結束處
    重要函式的開始結束出應該打上log ,這樣在看log時會比較直觀,什麼時候開始什麼時候結束就會一目瞭然,萬一中間出異常導致程式退出了,也知道是在哪個函式突然中斷的。也同樣適用於一個重要邏輯塊的開始結束。
  2. 返回結果時
    儘量在重要函式或web介面的每個返回分支列印返回結果。在出現不好分析的異常時,從細節下手,這時log會派上用場。如果跟合作方在資料方面出現爭議也可以及時拿出證據。特別是在呼叫外部系統介面時,一定要列印結果。
  3. 在多執行緒中
    日誌最好要記錄執行緒ID日誌還要記錄執行緒ID,否則可能不知道是哪個執行緒的作業,也無法有條理的來觀察一個執行緒。
  4. 需要記錄程式耗時處
    訪問一個第三方介面、上傳下載檔案等可能耗時的操作,都要記錄完成這個操作所耗的時間。否則程式效能出了問題,你不知道是網路原因呢,還是你呼叫的第三方介面效能出現問題呢,還是你自己程式的問題呢。
  5. 批量操作
    涉及到數量的操作要列印log,比如查詢資料庫和批量拷貝檔案、上傳下載、批量格式轉換等批量操作,設計到的數量要打印出來。
  6. 分散式系統追蹤請求
    RequestID:我們通常用RequestID來對請求進行唯一的標記,目的是可以通過RequestID將一個請求在系統中的執行過程串聯起來,這在分散式系統中的威力是巨大的。該RequestID通常會隨著響應返回給呼叫者,如果調用出現問題,呼叫者也可以通過提供RequestID幫助服務提供者定位問題。
    如何使用requestId在分散式系統追蹤請求

5.WHEN

  1. 當你遇到問題的時候,只能通過debug功能來確定問題,你應該考慮打日誌,良好的系統,是可以通過日誌進行問題定位的。
  2. 經常以功能為核心進行開發,你應該在提交程式碼前,可以確定通過日誌可以看到整個流程。
  3. 和一個外部系統進行通訊時,要記錄下你的系統傳出和讀入的資料,這樣以便查明是否為外部系統的問題。系統整合是一件苦差事,而診斷兩個應用間的問題(想像下不同的公司,環境,技術團隊)尤其困難。

6. NOTICE

使用Slf4j來記錄日誌

使用門面模式的日誌框架,有利於維護和各個類的日誌處理方式統一。

Java是面向物件程式設計。
反例:UserServiceImpl interface = new UserServiceImpl();
應該面向介面的物件程式設計,而不是面向實現,這也是軟體設計模式的原則,
正確的做法應該是:
UserService interface = new UserServiceImpl();
日誌框架裡面也是如此,日誌有門面介面,有具體實現的實現框架,所以大家不要面向實現程式設計。


  1. 考慮日誌對效能的影響:日誌的頻繁列印會對模組的效能產生極大的影響。當日志產生的速度大於日誌檔案寫磁碟的速度,會導致日誌內容積壓在記憶體中,導致記憶體洩漏。

  2. 不列印重複的日誌,避免重複列印日誌,浪費磁碟空間,務必在log4j.xml中設定 additivity = false【阿里開發手冊規約中有強調】
    正例:

    <logger name="com.taobao.dubbo.config" additivity="false">
    
  3. 不打無用的、無意義、不完全的日誌。

    無用、無意義例子:(不要這樣做)

    if(message instanceof TextMessage){
    	//do sth
    }else{    
    	log.warn("Unknown message type");
    }
    

    上面列印的warn資訊,看日誌根本無法清楚識別訊息型別是什麼,上述例子只是充當了提示資訊,對解決問題毫無幫助。
    正確的列印內容應該是: 訊息型別;訊息ID;上下文資訊;出問題的原因。
    無用、無意義例子:(不要這樣做)

    log.info("呼叫客戶系統開始...");
    ...
    log.info("呼叫客戶系統結束...");
    ...
    log.info("使用者簽約失敗...");
    

    當我們看到這樣的日誌的時候,沒有包含任何有意義的資訊。呼叫開始,我想知道誰呼叫的,入參是啥;呼叫結束,我想知道呼叫結果,出參是啥,呼叫成功還是失敗;同樣,使用者簽約失敗,我想知道的是哪個使用者失敗,為什麼失敗;顯然都沒展示,所以這樣的日誌是沒有任何意義的。
    不完全的日誌例子:(不要這樣做)

     log.error('XX 發生異常', e.getMessage());
    

    e.getMessage()只會記錄記錄錯誤基本描述資訊,不會記錄詳細的堆疊異常資訊。
    正確的列印方式應該是:

    log.error('XX 發生異常', e);//這樣就會列印堆疊資訊
    

    在異常捕獲程式碼中務必列印堆疊資訊
    示例:

    		try {
    	            int i=1/0;
    	        } catch (Exception ex) {
    	        	logger.info("--- getMessage ---");
    	            logger.error(ex.getMessage());
    	            System.out.println();
    	            logger.info("--- print Stack Trace ---");
    	            logger.error("列印異常堆疊資訊",ex);
    	        }
    

    控制檯輸出為:

    2018-12-27 11:02:16,737 INFO main com.Test [//] — getMessage —
    2018-12-27 11:02:16,739 ERROR main com.Test [//] / by zero

    2018-12-27 11:02:16,739 INFO main com.Test [//] — print Stack Trace —
    2018-12-27 11:02:16,740 ERROR main com.Test [//] 列印異常堆疊資訊
    java.lang.ArithmeticException: / by zero
    at com.Test.main(Test.java:17)

    列印了異常堆疊資訊才能快速定位問題。

4.預期會被正常處理的異常,僅需要列印基本資訊留作記錄,不需要去列印異常堆疊資訊,使用堆疊的跟蹤是一個巨大的開銷,要謹慎使用。

5.不列印混淆資訊的日誌:所打印出來的日誌都應該是清楚準確的表達,當你不能確定具體原因的時候,不能在日誌中只是展示一種可能的情況,這樣會影響對問題的判斷,這樣的日誌千萬不能打。
反例:

	Connection connection = ConnectionFactory.getConnection();
 	if (connection == null) {
  		log.warn("System initialized unsuccessfully"); 
 	} 
  1. 一般不在迴圈裡列印日誌

  2. 日誌檔案推薦至少儲存 15 天,因為有些異常具備以“周”為頻次發生的特點【阿里開發手冊規約中有強調】

  3. 應用中的擴充套件日誌【阿里開發手冊規約中有強調】

    • 命名方式:appName_logType_logName.log
    • logType:日誌型別
    • logName:日誌描述

    這種命名的好處:通過檔名就可知道日誌檔案屬於什麼應用,什麼型別,什麼目的,也有利於歸類查詢。推薦對日誌進行分類,如將錯誤日誌和業務日誌分開存放,便於開發人員檢視,也便於通過日誌對系統進行及時監控。
    正例: mppserver 應用中單獨監控時區轉換異常,如:mppserver _ monitor _ timeZoneConvert . log

  4. 謹慎地選擇記錄日誌的級別,生產環境原則上禁止輸出 debug 日誌;有選擇地輸出info日誌。
    特殊情況:
    有可能有些問題只會在線上出現,那麼此時就需要用到debug來進行問題定位和分析。
    如果有上述特殊情況,在生產情況下需要開啟DEBUG,需要使用開關進行管理,不能一直開啟。

  5. 日誌列印的時候不能因為列印日誌引入新的異常,例如空指標異常
    反例:(不要這麼做)

    log.debug("Processing request with id: {}", request.getId());
    //request物件如果是null,那麼就會出現NPE。
    
  6. 日誌列印的時候,假如需要處理列表、陣列類的資料,最好是隻輸出物件的大小,或者某些關鍵欄位,例如:編號(Commons Beanutils),如果將全部內容打印出來可能會佔用大量的資源

  7. 寫完功能,進行測試時,儘量不用編輯器的除錯,而是通過日誌來檢視功能實現和進行異常定位、分析。
    你需要進行功能邏輯判斷的要點也可能是你打日誌的要點,會出現的異常正是日誌需要去記錄的。沒有編輯器的debug除錯,你也可以清楚的知道功能實現的邏輯,異常的原因和快速定位問題。

  8. 需要明確物件的toString方法是否輸出的東西就是你想要的東西,是否需要重寫物件的toString方法
    另外,建議列印物件資訊時,可以採用json格式列印,這樣檢視起來更清晰。

    log.info("使用者綁卡,實名校驗,使用者資訊:{}", JSONObject.toJSONString(userInfo));
    
  9. 使用引數化資訊的方式:不要進行字串拼接,那樣會產生很多String物件,佔用空間,影響效能。
    反例:(不要這麼做)

    logger.debug("Processing trade with id: " + id + " symbol: " + symbol);
    

    正例:

    logger.debug("UserName is:[{}] and age is : [{}] ", name, age);
    //加一個"[]"可讀性會更強一些。
    
  10. 記錄非預期執行情況
    為程式在“有可能”執行到的地方列印日誌。如果我想刪除一個檔案,結果返回成功。但事實上,那個檔案在你想刪除之前就不存在了。最終結果是一致的,但程式得讓我們知道這種情況,要查清為什麼檔案在刪除之前就已經不存在。

  11. 如果要丟擲異常,就把異常留給最終處理的方法進行列印,不要在丟擲異常的地方進行列印。
    反例:(不要這麼做)

    try {
    	//do sth	
    } catch (Exception e) {
    	logger.error('XX 發生異常', e);
    	throw new IOException();
    }
    
  12. 如果你在方法的開始和結束都記錄了日誌,那麼你可以人工找出效率不高的程式碼。
    因為列印的日誌有時間戳,你可以根據開始和結束時間獲得執行時間,如果執行時間較長,可能就是程式碼效率有問題。
    如果你的方法名的含義很清晰,看日誌將是一件愉快的事情。同樣的,分析異常也更得更簡單了,因為你知道每一步都在幹些什麼。所以:方法的命名很重要

  13. 作為一個webserver,建議將請求者的ip和請求時間,列印到日誌中

  14. 在和資源打交道的時候,也要記錄關鍵的資訊,比如說磁碟訪問,比如資料庫訪問,比如請求網路伺服器,這些都算是與小系統的互動,必須要將輸入和輸出寫入日誌。而且這些內容都會伴有異常,遇到異常更是要以error寫入logger。

  15. 異常捕獲後除了丟擲異常、列印日誌外,還可以做哪些操作?

    1. 可以提示問題原因。
    2. 可以提供可能的解決辦法。
    3. 嘗試解決問題,比如:

      比如說碰到了一個主機訪問異常,你可以在異常體中嘗試重啟主機。
      比如連線不成功,可以嘗試重新連線。

7. HOW

實現進入方法前列印引數,退出方法時列印輸出的方式

  • 程式碼裡要記錄的方法很多的話,可以用AOP切面、攔截器、過濾器來實現引數和結果的日誌列印。這樣減少了重複的程式碼,不過使用它得特別小心,不注意的話可能會導致輸出大量的日誌,這種日誌最合適的級別就是DEBUG了。如果你發現某個方法呼叫的太頻繁,記錄它的日誌可能會影響效能的話,只需要調低它的日誌級別就可以了,或者把日誌直接刪了(或者整個方法呼叫只留一個?)不過日誌多了總比少了要強。把日誌記錄當成單元測試來看,你的程式碼應該佈滿了日誌就像它的單元測試到處都是一樣。系統沒有任何一部分是完全不需要日誌的。記住,有時候要知道你的系統是不是正常工作,你只能檢視不斷刷屏的日誌。

7.1. 攔截器

  • Spring Boot中通過配置攔截器Interceptor,可以攔截某個指定的方法或某一類指定的方法,在方法執行之前列印引數和請求資訊,在方法執行完畢之後列印方法的執行結果
  • 實現方式:
    • 首先需要配置一個註解
    • 其次需要設計該註解的實現(即攔截器具體要執行的操作)
    • 最後配置該攔截器
/**
 * 定義一個註解,一般為方法級別,在需要列印日誌的方法上面使用
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogPrint {
}

注意:

  • @Target
    • ElementType.TYPE:介面、類、列舉、註解
    • ElementType.FIELD:欄位、列舉的常量
    • ElementType.METHOD:方法
    • ElementType.PARAMETER:方法引數
    • ElementType.CONSTRUCTOR:建構函式
    • ElementType.LOCAL_VARIABLE:區域性變數
    • ElementType.ANNOTATION_TYPE:註解
    • ElementType.PACKAGE:包
  • @Retention
    • RetentionPolicy.SOURCE:這種型別的Annotations只在原始碼級別保留,編譯時就會被忽略
    • RetentionPolicy.CLASS —— 這種型別的Annotations編譯時被保留,在class檔案中存在,但JVM將會忽略
    • RetentionPolicy.RUNTIME —— 這種型別的Annotations將被JVM保留,所以他們能在執行時被JVM或其他使用反射機制的程式碼所讀取和使用.
/**
 * 攔截器方法具體要實現的功能,日誌列印就在該部分實現
 */
@Service
public class LogPrintInterceptor implements HandlerInterceptor {

    /**
     * 請求處理之前進行呼叫(Controller方法呼叫之前)
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        /**
         * 在方法執行之前需要列印的日誌,可以從servlet和handler中獲取請求的引數資訊
         */
        // 千萬千萬要返回true,否則將不進入方法執行
        return true;
    }

    /**
     * 請求處理之後進行呼叫(Controller方法呼叫之後)
     */
    @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 {
        /**
         * 在方法執行完之後需要列印的日誌
         */
    }
}
/**
 * 配置攔截器方法
 */
@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    @Autowired
    private LogPrintInterceptor logPrintInterceptor;

    /**
     * 配置註解攔截器
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(logPrintInterceptor)
                .addPathPatterns("/**");
        super.addInterceptors(registry);
    }
}


7.2. 過濾器

  • 同樣的,也可以通過使用過濾器Filter來實現方法執行前後的引數回覆資訊的列印
  • 實現方式:
    • 首先建立一個Filter的實現類,在Filter的實現類的doFilter方法中實現方法執行前後的日誌列印
    • 最後配置該過濾器

@Service
public class LogPrintFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        /**
         * 方法執行前日誌列印
         */
        // 實際業務
        chain.doFilter(request, response);
        /**
         * 方法執行後日志列印
         */

    }

    @Override
    public void destroy() {
    }
}

@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
    @Bean
    public FilterRegistrationBean logPrintFilterRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        // 這個位置只能採用建立新物件的方式來設定攔截器,採用注入的方式,實驗失敗
        registration.setFilter(new LogPrintFilter());
        registration.addUrlPatterns("/**");
        registration.addInitParameter("paramName", "paramValue");
        registration.setName("logPrint");
        registration.setOrder(1);
        return registration;
    }
}

7.3. 切面

  • 同樣的,也可以通過切面的思想來實現方法執行前後的日誌列印
  • 實現方式:
    • 實現切面Aspect類

@Aspect
@Component
public class LogPrintAspect {

    /**
     * 方法切點
     */
    @Pointcut(value = "execution(* com.cmft.logplat.base.controller.*.*(..))")
    private void pointcut(){

    }

    /**
     * 方法執行之前
     */
    @Before(value = "pointcut()")
    public void before(JoinPoint joinPoint){
        // 
    }

    /**
     * 方法執行之後
     */
    @After(value = "pointcut()")
    public void after(JoinPoint joinPoint){
        // 
    }
}

注意:

  1. 如何配置切入點
  2. 配置切入點的時候儘可能約束到目標方法群,否則範圍太寬的話,會造成某些外部外掛的bean無法自動注入


如果對您有幫助,請支援下吧。一分不嫌少,一百不嫌多。
在這裡插入圖片描述