老生常談SpringAop日誌收集與處理做的工具包

AopLog是基於Spring Aop 和ThreadLocal實現的一個專門對請求方法內容日誌的攔截與處理的日誌工具包。

場景 :

  1. 使用Spring Aop攔截參數日誌目前大部分做法都基本上大同小異,不想日後每個項目工程都寫一份這樣的Aop攔截處理日誌的程式碼,甚至程式碼侵入。
  2. 我想知道一些相對重要的請求方法的請求參數,響應參數,請求頭,以及內部耗時,方法是成功還是失敗等等資訊。發生錯誤時我也不知道執行到哪一步發生了異常,是不是某個參數導致出的邏輯問題。
  3. 普通的log.info或warn資訊沒有所屬請求的上下關係,並不方便查看和分析。
  4. 正式環境中,我並不想列印太多無意義的info日誌(有些只是為了排查問題列印的日誌,程式正常運行時其實毫無意義),只希望在發生異常時記錄日誌或者只希望每次請求只記錄一條次關鍵的請求資訊。
  5. 日誌的收集,我希望將這些請求的日誌記錄下來,記錄的實現方式我自己決定,比如正常的日誌列印,常見的日誌寫入資料庫,日誌寫入到文件,日誌入隊列等等。
  6. 整個日誌的記錄完全不干擾正常請求方法的流程,日誌的收集處理非同步化,完全不影響正常請求方法的性能與響應。
  7. 只需要通過@AopLog註解決定是否記錄。

快速開始

項目通過maven的pom.xml引入


<dependency>
    <groupId>com.github.ealenxie</groupId>
    <artifactId>aop-log</artifactId>
    <version>2.1</version>
</dependency>

或者通過gradle引入

compile group: 'com.github.ealenxie', name: 'aop-log', version: '2.1'

@AopLog註解使用,進行日誌記錄

直接在類(作用類的所有方法)或類方法(作用於方法)上加上註解@AopLog,進行日誌記錄

例如 :

import com.github.AopLog;
import name.ealen.infra.base.resp.RespBody;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author EalenXie create on 2020/6/22 14:28
 */
@AopLog(type = "測試",stackTraceOnErr = true)
@RestController
public class AppController {

    @GetMapping("/app/sayHello")
    public RespBody<String> sayHello() {
        return RespBody.ok("hello EalenXie");
    }

}

自定義全局的日誌收集器實現收集 LogCollector

例如只是簡單列印,或寫入到庫等等。


import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.LogData;
import com.github.collector.LogCollector;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * @author EalenXie create on 2020/9/15 13:46
 * 此為樣例參考
 * 配置一個簡單的日誌收集器 這裡只是做了一個log.info列印一下,可以在這裡寫入到資料庫中或者寫入
 */
@Slf4j
@Component
public class AopLogCollector implements LogCollector {
    private ObjectMapper objectMapper = new ObjectMapper();
    @Override
    public void collect(LogData logData) {
        try {
            log.info(objectMapper.writeValueAsString(logData));
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }
}

配置@Component的全局日誌收集器只能配置一個。

介面調用 /say/hello 測試即可看看到控制台列印出結果 :

2020-09-16 16:01:04.782  INFO 2012 --- [AsyncExecutor-2] name.ealen.infra.advice.AopLogCollector  : {"appName":"app-template","host":"127.0.0.1","port":8080,"clientIp":"192.168.110.1","reqUrl":"//localhost:8080/app/sayHello","httpMethod":"GET","headers":{"User-Agent":"Apache-HttpClient/4.5.10 (Java/11.0.5)"},"type":"測試","content":"","method":"name.ealen.api.facade.AppController#sayHello","args":null,"respBody":{"code":"200","desc":"OK","message":"請求成功","dateTime":"2020-09-16 16:01:04","body":"hello EalenXie"},"logDate":1600243264780,"costTime":1,"threadName":"http-nio-8080-exec-3","threadId":33,"success":true}

記錄的日誌對象LogData屬性說明

LogData 記錄的內容

欄位 類型 注釋
appName String 應用名稱
host String 主機
port int 埠號
clientIp String 請求客戶端的Ip
reqUrl String 請求地址
headers Object 請求頭部資訊(可選擇記錄) 默認記錄user-agent,content-type
type String 操作類型,默認值undefined
content String 方法步驟內容,默認是空,可使用LogData.step進行內容步驟記錄
method String 請求的本地java方法
args Object 方法請求參數
respBody Object 方法響應參數
costTime long 整個方法耗時
logDate Date Log產生時間,LogData對象初始化的時間
threadName String 執行緒名稱
threadId long 執行緒Id
success boolean 執行狀態,成功(true)/異常(false)

AopLog 註解選項說明

選項 類型 說明 默認
logOnErr boolean 僅當發生異常時才記錄收集 false
type String 操作類型 默認值”undefined”
headers String[] 記錄的header資訊 ,選擇要記錄哪些header資訊 默認”User-Agent”,”content-type”
args boolean 是否記錄請求參數 true
respBody boolean 是否記錄響應參數 true
stackTraceOnErr boolean 當目標方法發生異常時,是否追加異常堆棧資訊到LogData的content中 false
asyncMode boolean 非同步方式收集 true
collector Class<? extends LogCollector> 指定日誌收集器 默認不調整收集器,使用全局的日誌收集器

LogData的step方法。

記錄步驟。(如果某些重要步驟希望被記錄下來)
例如 :

import com.github.AopLog;
import com.github.LogData;
import name.ealen.infra.base.resp.RespBody;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;


/**
 * @author EalenXie create on 2020/6/22 14:28
 */
@AopLog(type = "測試",stackTraceOnErr = true)
@RestController
public class AppController {


    @GetMapping("/app/sayHello")
    public RespBody<String> sayHello() {
        LogData.step("1. 第一步執行完成");
        //......
        LogData.step("2. 第二步執行完成");
        //.....
        LogData.step("3. service的方法執行完成");
        //.....
        return RespBody.ok("hello EalenXie");
    }

}

注意: 此方法如果不在被@AopLog註解的方法的整體調用鏈路中使用,則當前執行緒中的ThreadLocal中的LogData不會釋放,需要手動調用LogData.removeCurrent();

此時再次介面調用 /say/hello 測試即可看看到控制台列印出結果,重點觀察content欄位 :

2020-09-16 17:26:20.285  INFO 3284 --- [AsyncExecutor-2] name.ealen.infra.advice.AopLogCollector  : {"appName":"app-template","host":"127.0.0.1","port":8080,"clientIp":"192.168.110.1","reqUrl":"//localhost:8080/app/sayHello","httpMethod":"GET","headers":{"User-Agent":"Apache-HttpClient/4.5.10 (Java/11.0.5)"},"type":"測試","content":"1. 第一步執行完成\n2. 第二步執行完成\n3. service的方法執行完成\n","method":"name.ealen.api.facade.AppController#sayHello","args":null,"respBody":{"code":"200","desc":"OK","message":"請求成功","dateTime":"2020-09-16 17:26:20","body":"hello EalenXie"},"logDate":1600248380283,"costTime":1,"threadName":"http-nio-8080-exec-2","threadId":32,"success":true}

關於

開源Github地址 : //github.com/EalenXie/aop-log

感謝各位提出意見和支援。

Tags: