自研日志打印器工具類


作者:劉仁鵬
GitHub地址:https://github.com/LingPaicoder/agile-base-4j

轉載請注明出處


1. 前言

  • 今天為大家介紹的是一款自研的日志打印器。相信大家在工作中,少不了要和日志打交道。正所謂“日志打得好,bug沒地兒跑”,清晰、簡潔、全面的日志有助于快速定位問題,而混亂、冗余、丟三落四的日志則會浪費大量時間。
  • 那么一款好用的日志打印器應該具有哪些特性呢?筆者認為主要有以下幾點:
  1. 支持多維分析
    比如,有時我們想從用戶(userId)的維度來查看某一個用戶對系統進行過哪些操作;有時我們想從請求的維度來查看一次請求的具體細節;有時我們又想從統計的維度來查看某些頁面/接口的PV、UV。
  2. 支持上下文收集
    上下文是指在一次請求過程中,與之相關的信息,例如:START日志、事務Id、接口名、接口描述、接口實參、運行時參數、業務進度、耗時時長、END日志等等。有些信息在前置攔截器中就可獲得,有些必須在業務代碼中獲得,而像耗時時長這樣的信息,則適合放到后置攔截器中計算。
    日志打印,主要是為了記系統在當前上下文里的運行情況。如果系統出現問題,我們通過日志最想要得到的信息就是清晰的上下文。如果沒辦法做到這一點,日志只會越多越亂。
  3. 日志要易讀、要格式化
    日志主要是面向人的,給人來看的。格式混亂、不易斷句的日志,容易讓人丟失掉排查問題的關鍵線索。
  4. 要對AOP友好
    AOP對Access日志、End日志、請求參數、處理時間等信息的打印更靈活。一個對AOP友好的日志打印器,可以事半功倍。

2. 簡介

  • 今天要說的這款日志打印器是怎樣做到上面那些特性的呢?先來看一下它都提供了哪些接口吧:
public interface LoggerDecorator extends Logger {
    /**
     * 設置logId
     */
    LoggerDecorator setLogId(String logId);

    /**
     * 設置前綴.可通過攔截器由{@link LogDesc#value()}提供
     */
    LoggerDecorator setBusinessPrefix(String prefix);

    /**
     * 注冊參數
     */
    LoggerDecorator register(String key, Object val);

    /**
     * 清除
     */
    LoggerDecorator clear();
}
  1. setLogId
    setLogId接口通常在一個事務開始時調用。例如一次請求,可以在前置攔截器中通過該接口傳入一個唯一性較強的logId(一般不需要絕對唯一,因為日志文件一般都會以天為單位截斷,只要保持一天內不會重復即可)。然后在該請求內的任何日志打印,都會自動帶上此logId。當需要查看某次請求的具體過程時,利用該功能可以很方便的實現。該接口是為事務/請求維度服務的。
  2. setBusinessPrefix
    setBusinessPrefix接口與setLogId類似,也是在事務開始時調用,事務內的任何日志都會自動帶上接口傳入的參數。不同的是,它是為功能/接口維度服務的。例如對于“用戶注冊”功能,可以將“用戶注冊”作為參數傳入,或者直接傳入接口名(eg:registUser)也可以。
    一般無論是web框架還是服務容器,都會提供在攔截器中獲取Function對象的接口,因此能夠獲得方法名。或者自定義一個注解,加在方法上,就可以自定義功能描述信息了。像下面這樣:
@LogDesc("查詢商家信息")
public Response<Custom> getCustomById(long customId)
  1. register
    regist接口用來收集關鍵的運行時參數,例如會決定代碼跳轉的boolean類型變量(eg:isCustomExist)、用戶主鍵等等。所有被注冊了的K-V,都會在后續的任何打印(info/error/warn)時自動追加在日志內容之后。

  2. clear
    clear通常在事務結束時使用,例如后置攔截器。這個接口的功能是,將本次事務中的所有收集的內容(即通過之前三個接口收集的內容)清空。以免影響下次事務的日志打印。

3. 演示

  • 來看一下示例代碼:
    下面的代碼,模擬了1000次事務處理,日志打印分三個部分:前置攔截器日志打印、業務代碼日志打印、后置攔截器日志打印。
public class LOGTest {

    private static final LoggerDecorator LOG = BaseLoggerDecorator.getLogger(LOGTest.class);

    public static void main(String[] args) throws Exception {
        for (int i = 1; i <= 1000; i++) {
            // 模仿前置攔截器
            LOG.setLogId(RandomUtil.shortUUID())
                    .setBusinessPrefix("具體業務描述")
                    .register("currNum", i)
                    .info("START");
            long startTime = System.currentTimeMillis();

            // 模仿業務代碼
            LOG.register("userId", RandomUtil.nextInt(1000, 10000));
            Thread.sleep(RandomUtil.nextInt(5, 10));
            if (i % 2 == 0) {
                LOG.info("currNum是偶數");
            }
            if (i % 3 == 0) {
                LOG.info("currNum是3的倍數");
            }

            // 模仿后置攔截器
            long endTime = System.currentTimeMillis();
            LOG.register("usedTime", endTime - startTime).info("END");
            LOG.clear();
        }
    }

}
  • 部分日志截圖:
consoleLog.png-403.6kB
consoleLog.png-403.6kB
  • 我們截取部分(前6次)日志來進行說明:
logId:6056ff3f52e6 【 具體業務描述 - START 】 - logArgs:{"currNum":1}
logId:6056ff3f52e6 【 具體業務描述 - END 】 - logArgs:{"usedTime":5,"userId":6404,"currNum":1}
logId:62971514bf59 【 具體業務描述 - START 】 - logArgs:{"currNum":2}
logId:62971514bf59 【 具體業務描述 - currNum是偶數 】 - logArgs:{"userId":3433,"currNum":2}
logId:62971514bf59 【 具體業務描述 - END 】 - logArgs:{"usedTime":5,"userId":3433,"currNum":2}
logId:59fd7fdc357f 【 具體業務描述 - START 】 - logArgs:{"currNum":3}
logId:59fd7fdc357f 【 具體業務描述 - currNum是3的倍數 】 - logArgs:{"userId":1736,"currNum":3}
logId:59fd7fdc357f 【 具體業務描述 - END 】 - logArgs:{"usedTime":6,"userId":1736,"currNum":3}
logId:adf9a8e07d1a 【 具體業務描述 - START 】 - logArgs:{"currNum":4}
logId:adf9a8e07d1a 【 具體業務描述 - currNum是偶數 】 - logArgs:{"userId":1805,"currNum":4}
logId:adf9a8e07d1a 【 具體業務描述 - END 】 - logArgs:{"usedTime":10,"userId":1805,"currNum":4}
logId:83f8366c488f 【 具體業務描述 - START 】 - logArgs:{"currNum":5}
logId:83f8366c488f 【 具體業務描述 - END 】 - logArgs:{"usedTime":6,"userId":7144,"currNum":5}
logId:c649de0705f2 【 具體業務描述 - START 】 - logArgs:{"currNum":6}
logId:c649de0705f2 【 具體業務描述 - currNum是偶數 】 - logArgs:{"userId":4828,"currNum":6}
logId:c649de0705f2 【 具體業務描述 - currNum是3的倍數 】 - logArgs:{"userId":4828,"currNum":6}
logId:c649de0705f2 【 具體業務描述 - END 】 - logArgs:{"usedTime":8,"userId":4828,"currNum":6}

1.多維分析:

  • 查看某一個用戶(例如6404)對系統進行過哪些操作:
more log.txt | grep 'END' | grep '6404'

輸出:
logId:6056ff3f52e6 【 具體業務描述 - END 】 - logArgs:{"usedTime":5,"userId":6404,"currNum":1}
  • 查看某次請求(例如第6次請求)的具體細節:
more log.txt | grep 'c649de0705f2'

輸出:
logId:c649de0705f2 【 具體業務描述 - START 】 - logArgs:{"currNum":6}
logId:c649de0705f2 【 具體業務描述 - currNum是偶數 】 - logArgs:{"userId":4828,"currNum":6}
logId:c649de0705f2 【 具體業務描述 - currNum是3的倍數 】 - logArgs:{"userId":4828,"currNum":6}
logId:c649de0705f2 【 具體業務描述 - END 】 - logArgs:{"usedTime":8,"userId":4828,"currNum":6}
  • 查看請求的PV:
more log.txt | grep 'START' | grep '具體業務描述' | wc -l

輸出:
6
  • ......

2.上下文收集:

  • setLogId接口可用來收集事務Id
  • setBusinessPrefix接口用來收集接口描述
  • register接口用來收集關鍵運行時參數
  • clear接口用來清空本次事務的信息

3.日志格式化:

  • 這里說的格式化,是指對業務代碼的格式化。至于時間、日志級別、線程信息、類信息等,可以通過底層日志框架的配置文件來進行配置。例如:

    <Pattern>[%d{MM-dd HH:mm:ss SSS} %-5level] [%t] %c{3} - %m%n%ex</Pattern>
    
  • 日志打印器會按照下面的格式進行打印:

    logId:具體事務Id 【 具體業務描述 - 具體業務進度 】 - logArgs:{具體參數}
    //例如:
    logId:adf9a8e07d1a 【 用戶注冊 - 校驗失敗:姓名為空 】 - logArgs:{"userName":"","phone":"13200000000"}
    

4.AOP支持

  • 日志打印器內置三個ThreadLocal,分別存儲logId、businessPrefix、關鍵運行時參數:
private static final ThreadLocal<String> LOG_ID_THREAD_LOCAL = new ThreadLocal<>();
private static final ThreadLocal<String> BUSINESS_PREFIX_THREAD_LOCAL = new ThreadLocal<>();
private static final ThreadLocal<Map<String, Object>> LOG_ARGS_THREAD_LOCAL = new ThreadLocal<>();
  • 具體使用方式可隨業務場景自定義。

4. GitHub

  • 日志打印器的GitHub地址是https://github.com/LingPaicoder/agile-base-4j
  • 所有的校驗器相關代碼都放在 com.lpcoder.agile.base.forj.log 包下。
  • 使用示例代碼,放在test的 com.lpcoder.agile.base.forj.log 包下。

end
歡迎留言反饋對日志打印器的改進意見。

掃碼可關注微信公眾號:


wx.jpg
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。