demo演示如何寫一個無配置格式統一的日誌

宜信技術學院發表於2019-09-23


一、背景

大量專案在使用logback記日誌,有部分專案使用日誌混亂,格式不統一,多數人搞不懂配置檔案,導致配置錯誤,現在需要開發一套統一的、少配置的日誌元件,方便使用。

二、設計思路

儘量採用0配置,無logback.xml

日誌格式統一,方便後續日誌分析系統

只有兩個日誌級別,一個是正常日誌,一個是異常日誌

提供log4j、jcl、logback、commons-log等橋接方案及版本相容方案

提子執行緒、json格式化輸出、map格式化、陣列格式化、請求響應引數(供耗時)等便捷日誌輸出方法

支援redis、db、http自動開關配置****

新增日誌型別(logger)

api採用流式結構,類似StringBuffer

三、概要設計

1、零配置

調研程式碼  

java
static LoggerContext lc;
    static {
        lc = (LoggerContext) LoggerFactory.getILoggerFactory();
        // 對應配置中的appender
        ConsoleAppender ca = new ConsoleAppender();
        ca.setContext(lc);
        ca.setName("console");
        // 格式
        PatternLayoutEncoder pl = new PatternLayoutEncoder();
        pl.setContext(lc);
        pl.setPattern("%d{MMddHHmmss.SSS} [%thread] %-5level %logger{36} - %msg%n");
        pl.start();
        ca.setEncoder(pl);
        ca.start();
        // 對應配置中的logger
        ch.qos.logback.classic.Logger rootLogger = lc.getLogger("com.test");
        rootLogger.addAppender(ca);}

上面程式碼等價於下面的xml

                    %d{MMddHHmmss.SSS} [%thread] %-5level %logger{36} - %msg%n

由此可以隨意把配置檔案中的內容以程式碼形式編寫,理論已經可以實現0配置。

2、輸出路徑

約定固定將日誌輸出到,相對路徑log/xxx.yyyy-MM-dd-HH.log,其中xxx為logger的name

3、日誌格式

格式固定:
MMddHHmmss.SSS||id||【交易名★子步驟】||context ||[level][執行緒號]
例:
150000.311||N-XrUTQzIc1531897200311||【CiTeeFilter★ci攔截器】||ci攔截器 請求的完整引數為:{"merchantId":["0012444"],"userId":["13112341232"]} ||[INFO][http-8091-7]

固定格式的核心程式碼,攔截到日誌請求,按照格式拼裝,主要方法為繼承ThrowableProxyConverter和MessageConverter來實現對日誌的攔截,並修改為想要的格式,其中使用的例如id等放到本地變數內,核心是對MDC的使用

4、基礎logger

所有日誌都預設輸出到這裡 logger name:service 系統初始化時,定義這個Logger和appender,即這個Logger為root log

5、自定義的logger

提供addLogger方法,引數 packageName 包名,例如:com.test 必輸引數 如果name未設定時,name預設為包名最後一個.後面的字元 name 名字,決定日誌檔案的名字 非必輸 path 日誌路徑 非必輸 additivity 是否輸出到root log內

6、特殊的log

提供特殊元件的log配置,例如:redis 預設ERROR http 預設ERROR db連線池 預設ERROR kafka 預設ERROR schedul 預設ERROR spring 預設ERROR

7、異常、換行日誌處理

提供exception異常棧格式列印 提供帶換行的格式化列印 程式碼思路:繼承ThrowableProxyConverter,獲取異常棧,在每行的前面插入固定格式文字

8、普通日誌api(VirgoLog)

方法 方法描述
setUniqKey(id) 設定當前執行緒id,執行緒開始時設定即可,後面無需設定
updateStep(trade, step) 更新當前id的步驟資訊
log(msg, param) 記錄普通日誌,msg替換規則,普通替換為{},如果想替換為業務日誌api中的格式,使用``替換
logErr(msg, e) 記錄異常日誌
log( trade, step, msg, param) 記錄普通日誌,此方法會自動更新id、trade、step,不建議使用
logErr(trade, step, msg, e) 記錄異常日誌
log(cid, trade, step, msg, param) 記錄普通日誌,此方法會自動更新id、trade、step,不建議使用
logErr(cid, trade, step, msg, e) 記錄異常日誌
debug(msg, param) 記錄debug級別日誌,不建議使用

業務日誌api(VirgoLog)

平時記日誌時,如果某個類沒有時間toString方法,會無法正確列印出資料,此時提供替換方法,直接將object替換為json列印,核心程式碼思路為

MessageFormatter是處理{}替換的類,重新寫個類,稍加改動即支援{}也支援`` ,並判斷替換為json還是toString

api如下

方法 方法描述
begin(msg) 記錄開始
end(msg) 記錄完成,會列印本執行緒內上一個begin到現在的耗時
logJson(json, format) 記錄json格式化日誌,format表示是否換行
logMap(map, format) 記錄map格式化日誌
logCollection(list, format) 記錄集合格式化日誌
logArray(array, format) 記錄陣列格式化日誌
logObjct(obj, format) 記錄Object格式化日誌

系統api(LoggerHelper)

方法 方法描述
getLogger() 獲取logger,用於記日誌
getLogger(name) 透過name獲取logger
addLogger() 參考自定義Logger,如果logger已經建立,則不再建立,一般不使用,除非想自定義日誌名等
consoleOpen() 開啟控制檯日誌,系統啟動時預設配置控制檯日誌
commonOpen(name, level) 預設的元件都是error級別,這個方法可以變更日誌級別,例如redis http等

9、特殊的格式化

map:即轉化為json,然後再格式化  

collection:同上  

array:也同上  

object:同上

10、問題

  • 密碼脫敏、加解密有必要單獨提取方法嗎

  • 提供父執行緒列印開關

11、maven依賴

                    com.cdc.ecliptic            virgo            1.5_1.6-SNAPSHOT

12、demo

public static void main(String[] args) throws InterruptedException {
        // 啟動
        VirgoLancher.start("hahaha", "com.cdc.virgo", "D:/test/hahah.log");
        LoggerHelper.commonOpen("hahaha", LogLevel.DEBUG);
        Logger logger1 = LoggerFactory.getLogger("druid");
//        VirgoLancher.commonStart("abc", "com.cdc.virgo");
        // 開啟控制檯
        LoggerHelper.consoleOpen();
        // 設定cid
        VirgoLog.setUniqKey(null);
        // 設定步驟名和交易名
        VirgoLog.updateStep("adfa", "saf");
        // 獲取Logger
        VirgoLog logger = VirgoLog.getLogger();
        // 開啟debug級別(只有在開發階段可以開啟)
//        logger.changeLevel(LogLevel.DEBUG);
        // 記錄換行
        logger.log("a");
        logger1.info("dddddddddd");
        logger1.error("dddddddddd");
//        logger1.info("sfdasfaf" +
//                "\nafafdasfd" +
//                "\nasfdasf");
        logger.log("sfdasfaf" +
                "\nafafdasfd" +
                "\nasfdasf");
//        logger1.info("b");
        // 正常日誌
//        logger.log("我只有一行");
        Map map = new HashMap();
        map.put("asdf", "1");
        map.put("asdf2", "2");
        map.put("asdf3", "13");
        map.put("asdf4", "14");
        map.put("asdf5", "15");
        map.put("asdf6", "16");
//        // 異常日誌也支援格式化
//        logger.logErr("我錯了:{},你沒錯:~~", new Exception("asdfsaflk"), "啊", map);
//        logger.log("----------------------------------------------");
//        // {}替換普通物件,呼叫toString()  ~~把物件轉換為json並且格式化輸出 ``把物件轉換為json不格式化輸出
        logger.log("你好{},你是誰~~``,sd~xx {}", map, map, map, "tttt");
        VirgoLog.updateStep("saf2");
//        // 把物件轉換為json輸出
//        logger.logJson(map, false);
//        // 更新步驟名和交易名
//        VirgoLog.updateStep("bbbbb", "ccccc");
//        // 耗時日誌列印
        logger.begin("處理內容");
        logger.begin("處理第二個");
        logger.begin("處理第三個");
        Thread.sleep(3000L);
        logger.end();
        Thread.sleep(1000L);
        logger.end();
        VirgoLog.updateStep("saf3");
        logger.end();
//        // 記錄debug日誌,一般除錯用
//        logger.logDebug("jajajajaja");
//        List l = new ArrayList();
//        B b = new B();
//        try {
//            b.b();
//        } catch (Exception e) {
//            logger.logErr("woqu", e);
//        }
    }

作者:劉鵬飛

來源:宜信技術學院


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69918724/viewspace-2657884/,如需轉載,請註明出處,否則將追究法律責任。

相關文章