帶你掌握Java各種日誌框架

螞蟻小哥發表於2022-02-21

一:日誌基本概念及框架

1:什麼是日誌

  Java程式設計師在開發專案時都是依賴Eclipse/IDEA等整合開發工具的Debug除錯功能來跟蹤解決Bug,但專案打包部署釋出到了測試環境和生產環境怎麼辦?難道連線到生產伺服器裝個IDEA做遠端除錯,實際並不能允許讓你這麼做。所以,日誌的作用就是在測試環境和生產環境沒有Debug除錯工具時為開發人員和測試人員定位問題的手段日誌打得好,就能根據日誌的軌跡快速定位並解決線上問題,反之,日誌輸出不好,不僅無法輔助定位問題反而可能會影響到程式的執行效能和穩定性。

  很多介紹 AOP 的地方都採用日誌來作為介紹,實際上日誌要採用切面的話是極其不科學的!對於日誌來說,只是在方法開始、結束、異常時輸出一些日誌資訊,那是絕對不夠的,這樣的日誌對於日誌分析沒有任何意義。如果在方法的開始和結束記錄個日誌,那方法中呢?如果方法中沒有日誌的話,那就完全失去了日誌的意義!如果應用出現問題要查詢由什麼原因造成的,也沒有什麼作用。這樣的日誌還不如不用!

2:日誌的作用

不管是使用何種程式語言,日誌輸出幾乎無處不再。總結起來,日誌大致有以下幾種用途:
「問題追蹤」:輔助排查和定位線上問題,優化程式執行效能。
「狀態監控」:通過日誌分析,可以監控系統的執行狀態。
「安全審計」:審計主要體現在安全上,可以發現非授權的操作。
日誌在應用程式中是非常重要的,好的日誌資訊能有助於我們在程式出現BUG時能快速進行定位,並能找出其中的原因。

3:常用日誌框架

  按照出來的時間順序排列:Log4j(reload4j) --> JUL --> JCL --> SLF4J --> Logback --> Log4j2

二:JUL日誌框架

  JUL全稱Java Util Logging,是Java原生的日誌框架,使用時不需要另外引入第三方類庫,相對於其他日誌框架來說其特點是使用方便,能夠在小型應用中靈活應用。但JUL日誌框架使用的頻率並不高,但一旦需要解除此類的程式碼,仍然要求開發人員能夠迅速看懂程式碼,並理解。

1:框架結構圖

Loggers:
    被稱為記錄器,應用程式通過獲取Logger物件,呼叫其API來發布日誌資訊,Logger通常是應用程式訪問日誌系統的入口程式;
Appenders:
    也被稱為Handlers,每個Logger都會關聯一組Handlers,Logger會將日誌交給關聯的Handlers處理,由Handlers負責將日誌記錄;
    Handlers在此是一個抽象類,由其具體的實現決定日誌記錄的位置是控制檯、檔案、網路上的其他日誌服務異或是作業系統日誌;
Layouts:
    也被稱為Formatters,它負責對日誌事件中的資料進行轉換和格式化,Layouts決定了記錄的資料在一條日誌記錄中的最終顯示形式;
Level:
    每條日誌訊息都有一個關聯的日誌級別。該級別粗略指導了日誌訊息的重要性和緊迫,可以將Level和Loggers或Appenders做關聯以便於我們
  過濾訊息; Filters: 過濾器,根據需要定製哪些資訊會被記錄,哪些資訊會被放過。 一個完整的日誌記錄過程如下: 使用者使用Logger來進行日誌記錄的行為,Logger可以同時持有若干個Handler,日誌輸出操作是由Handler完成的;在Handler輸出日誌前,
  會經過Filter的過濾,判斷哪些日誌級別放行、哪些攔截,Handler會將日誌內容輸出到指定位置(日誌檔案、控制檯等);Handler在輸出日
  志時會使用Layout對日誌內容進行排版,之後再輸出到指定的位置。

2:入門案例

    @Test
    public void demoA() {
        // 1.建立JUL Logger物件無法傳入class物件,只能傳如:"cn.xw.testDemo"
        Logger logger = Logger.getLogger(TestDemo.class.getName());
        // 2.輸出日誌的兩種方式,其中日誌級別共有7種,還有2種特殊級別
        logger.severe("[SEVERE 1000] 表明程式嚴重的失敗或錯誤");
        logger.warning("[WARNING 900] 表明潛在的問題");
        // 帶有佔位符的日誌列印
        String name = "螞蟻小哥", age = "24";
        logger.log(Level.INFO, "姓名:{0} 年齡:{1}", new Object[]{name, age});
    }

3:日誌級別

JUL內建工七種日誌級別,另外有兩種特殊級別(共9個)
java.util.logging.Level中定義了七種基本的日誌級別:
    SEVERE【致命級別 級別碼:1000】(最高階別):表明程式嚴重的失敗或錯誤
    WARNING【警告級別 級別碼:900】:表明潛在的問題
    INFO【資訊級別 級別碼:800】(預設):通過訊息會被輸入到控制檯,所以資訊級別一般記錄對終端使用者或系統管理員有用的資訊
    CONFIG【配置級別 級別碼:700】:用於表明系統靜態配置的資訊
    FINE【除錯級別 級別碼:500】:輸出開發人員關注的資訊
    FINER【除錯級別 級別碼:400】:輸出進入方法或者出入方法的一些資訊和異常捕捉
    FINEST【除錯級別 級別碼:300】(最低等級):輸出最細的資訊
java.util.logging.Level中定義了兩種基本的日誌級別:
    OFF:可用於關閉日誌記錄
    ALL:啟用所有訊息的日誌記錄

注:預設的日誌日誌級別由RootLogger決定的所有的Logger物件預設都會繼承並使用RootLogger所提供的控制檯輸出處理器
    物件ConsoleHandler同時,RootLogger的預設日誌輸出等級為INFO,則所有未經配置的Logger預設也是使用該日誌級別。
跟蹤程式碼:
    Logger logger = Logger.getLogger(TestDemo.class.getName());
    ==>進入getLogger
    return demandLogger(name, null, Reflection.getCallerClass());
    ==>進入demandLogger
    LogManager manager = LogManager.getLogManager();
    ==>進入getLogManager
    manager.ensureLogManagerInitialized();
    ==>進入ensureLogManagerInitialized
    owner.readPrimordialConfiguration();    //讀取配置檔案 logging.properties
    owner.rootLogger = owner.new RootLogger();
    owner.addLogger(owner.rootLogger);
    //判斷日誌級別不存在的話則預設defaultLevel
    // private final static Level defaultLevel = Level.INFO; //預設就是INFO
    if (!owner.rootLogger.isLevelInitialized()) {
        owner.rootLogger.setLevel(defaultLevel);
    }

4:自定義日誌(控制檯記錄和日誌檔案記錄)

  自定義日誌通常會在單獨的配置檔案中去配置Logger的日誌等級和處理器型別等,但作為入門,需要了解如何在Java程式碼中,通過更改Logger日誌級別和配置自定義ConsoleHandler的方式,去影響日誌輸出。如果不希望Logger物件使用RootLogger中的日誌級別進行輸出,則需要對Logger進行以下配置:重新設定Logger的日誌輸出等級;重新配置Logger的處理器Handler型別,並不再使用RootLogger中提供的預設處理器。

    @Test
    public void demoA() throws IOException {
        //獲取日誌記錄器
        Logger logger = Logger.getLogger(TestDemo.class.getName());
        //設定false;指定此記錄器是否應將其輸出傳送到其父記錄器(是否繼承父記錄器配置)(必須設定)
        logger.setUseParentHandlers(false);
        //建立日誌格式化元件物件
        //若想自定義格式化則通過simpleFormatter.format(java.util.logging.LogRecord record)
        SimpleFormatter simpleFormatter = new SimpleFormatter();
        //建立Handler(ConsoleHandler為控制檯輸出處理器)
        ConsoleHandler consoleHandler = new ConsoleHandler();
        //建立檔案處理器Handler  fileLog.log檔名稱
        FileHandler fileHandler = new FileHandler("./fileLog.log");
        //設定處理器中的內容輸出格式及輸出級別
        consoleHandler.setFormatter(simpleFormatter);
        consoleHandler.setLevel(Level.CONFIG);
        fileHandler.setFormatter(simpleFormatter);
        fileHandler.setLevel(Level.CONFIG);
        //設定記錄器中的處理器(控制檯記錄器、檔案記錄器)及記錄器的輸出級別
        logger.addHandler(consoleHandler);
        logger.addHandler(fileHandler);
        logger.setLevel(Level.CONFIG);
        //輸出列印
        logger.severe("[SEVERE 1000] 表明程式嚴重的失敗或錯誤");
        logger.warning("[WARNING 900] 表明潛在的問題");
        logger.info("[INFO 800] 資訊級別,通過訊息會被輸入到控制檯,所以資訊級別一般記錄對終端使用者或系統管理員有用的資訊");
        logger.config("[CONFIG 700] 配置級別,用於表明系統靜態配置的資訊"); //這時預設級別為 CONFIG
        logger.fine("[FINE 500] 輸出開發人員關注的資訊");
        logger.finer("[FINER 400] 輸出進入方法或者出入方法的一些資訊和異常捕捉");
        logger.finest("[FINEST 300] 輸出最細的資訊");
    }

5:JUL(Logging)父子關係

  從上面我們可以看出,預設日誌級別為INFO,但是更改日誌級別為CONFIG時輸出還是INFO,未起效果;這是因為我們建立Logger物件時繼承並使用了RootLogger中的日誌等級和處理器物件(子類無法覆蓋父類設定的預設配置)

  我們可以通過如下程式碼來看出Logger日誌的繼承關係,繼承關係就由獲取getLogger("xxx.xxx.xxx")中的點來區分繼承關係

    @Test
    public void demoA(){
        //建立3個日誌記錄器分別是 RootManger -> logger1 -> logger2
        //子logger也間接繼承RootManger
        Logger logger1 = Logger.getLogger("cn.xw");
        Logger logger2 = Logger.getLogger("cn.xw.TestDemo");
        //對比繼承關係
        System.out.println(logger2.getParent() == logger1); // true

        //這裡我針對 logger1 進行了自定義日誌記錄編寫(所以logger2預設繼承 logger1才是正確的)
        // logger1 不在繼承RootLogger 所以繼承關係就變為 logger1 -> logger2
        logger1.setUseParentHandlers(false);
        SimpleFormatter simpleFormatter = new SimpleFormatter();
        ConsoleHandler consoleHandler = new ConsoleHandler();
        consoleHandler.setFormatter(simpleFormatter);
        consoleHandler.setLevel(Level.CONFIG);
        logger1.addHandler(consoleHandler);
        logger1.setLevel(Level.CONFIG);
        //logger2記錄器記錄日誌
        logger2.severe("[SEVERE 1000] 表明程式嚴重的失敗或錯誤");
        logger2.warning("[WARNING 900] 表明潛在的問題");
        logger2.info("[INFO 800] 資訊級別,通過訊息會被輸入到控制檯,所以資訊級別一般記錄對終端使用者或系統管理員有用的資訊");
        logger2.config("[CONFIG 700] 配置級別,用於表明系統靜態配置的資訊"); //這是要設定的級別為 CONFIG
        logger2.fine("[FINE 500] 輸出開發人員關注的資訊");
        logger2.finer("[FINER 400] 輸出進入方法或者出入方法的一些資訊和異常捕捉");
        logger2.finest("[FINEST 300] 輸出最細的資訊");
    }

6:JUL使用自定義配置檔案

  開發中較為經常的使用的自定義日誌方式,是通過logging.properties檔案的方式進行配置(這個配置檔案預設在jre/lib目錄下);我們程式中需要編寫
程式碼去顯示載入logging.properties檔案以啟用自定義的配置。
  Handler是單獨進行配置的,開發人員可以單獨定義控制檯輸出日誌的處理器物件ConsoleHandler或檔案輸出日誌的處理器物件FileHandler等;具備相關自定義Handler後,需要將Logger與Handler進行關聯,配置檔案支援RootLogger或指定名稱的Logger與自定義Handler進行關聯;無論任何時候,都需要明確日誌最終的輸出等級,是同時由Logger與其相關聯的Handler所決定的。 
帶你掌握Java各種日誌框架
# RootLogger的日誌級別(預設INFO),所有的Handler都受限於此日誌級別,Handler的日誌級別可以比RootLogger的日誌級別高
.level=ALL
# RootLogger預設的處理器,可以配置多個,所有非手動解除父日誌的子日誌都將使用這些處理器
handlers=java.util.logging.ConsoleHandler, java.util.logging.FileHandler

# ConsoleHandler控制檯輸出處理器配置
# 指定ConsoleHandler預設日誌級別
java.util.logging.ConsoleHandler.level=ALL
java.util.logging.ConsoleHandler.encoding=UTF-8

# FileHandler檔案輸出處理器配置
# 指定FileHandler預設日誌級別
java.util.logging.FileHandler.level=ALL
# 日誌檔案輸出路徑(當前執行程式的路徑)
java.util.logging.FileHandler.pattern=./appLog.log
# 單個日誌檔案大小,單位是bit,1024bit即為1kb 1024*1024*100 = 100MB
java.util.logging.FileHandler.limit=1024*1024*100
# 日誌檔案數量,如果數量為2,則會生成appLog.log.0檔案和appLog.log.1檔案,總容量為: (limit * count)bit
java.util.logging.FileHandler.count=2
# FileHandler持有的最大併發鎖數
java.util.logging.FileHandler.maxLocks=100
# 指定要使用的Formatter類的名稱,FileHandler預設使用的是XMLFormatter
java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter
# 涉及中文日誌就最好加上編碼集
java.util.logging.FileHandler.encoding=UTF-8
# 是否以追加方式新增日誌內容
java.util.logging.FileHandler.append=true
# SimpleFormatter的輸出格式配置
java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n

# 自定義日誌級別,其中”cn.xw“指的是Logger.getLogger(String name)中的入參name!!!
com.anhui.handlers=java.util.logging.ConsoleHandler
com.anhui.level=CONFIG
# 如果此時不關閉名為cn.hanna的Logger的父日誌處理器,則在控制檯會同時出現父日誌處理器和自定義的處理器,訊息將重複輸出
com.anhui.useParentHandlers=false
logging.properties配置檔案
    @Test
    public void demoA() throws IOException {
        //獲取LogManager,LogManager是單例物件
        LogManager logManager = LogManager.getLogManager();
        //讀取配置檔案,並載入應用配置檔案logging.properties
        InputStream resourceAsStream = TestDemo.class.getClassLoader().getResourceAsStream("logging.properties");
        logManager.readConfiguration(resourceAsStream);
        //獲取日誌記錄器
        Logger logger = Logger.getLogger("cn.xw.TestDemo");
        //輸出列印
        logger.severe("[SEVERE 1000] 表明程式嚴重的失敗或錯誤");
        logger.warning("[WARNING 900] 表明潛在的問題");
        logger.info("[INFO 800] 資訊級別,通過訊息會被輸入到控制檯,所以資訊級別一般記錄對終端使用者或系統管理員有用的資訊");
        logger.config("[CONFIG 700] 配置級別,用於表明系統靜態配置的資訊");
        logger.fine("[FINE 500] 輸出開發人員關注的資訊");
        logger.finer("[FINER 400] 輸出進入方法或者出入方法的一些資訊和異常捕捉");
        logger.finest("[FINEST 300] 輸出最細的資訊"); //這時預設級別為 ALL
        System.out.println("=============================================");
        //指定自定義日誌物件的名稱,配置檔案中對com.anhui名稱的Logger進行了特殊配置 自定義
        Logger loggerCustomize = Logger.getLogger("com.anhui");
        loggerCustomize.severe("[SEVERE 1000] 表明程式嚴重的失敗或錯誤");
        loggerCustomize.warning("[WARNING 900] 表明潛在的問題");
        loggerCustomize.info("[INFO 800] 資訊級別,通過訊息會被輸入到控制檯,所以資訊級別一般記錄對終端使用者或系統管理員有用的資訊");
        loggerCustomize.config("[CONFIG 700] 配置級別,用於表明系統靜態配置的資訊"); //這時預設級別為 CONFIG
        loggerCustomize.fine("[FINE 500] 輸出開發人員關注的資訊");
        loggerCustomize.finer("[FINER 400] 輸出進入方法或者出入方法的一些資訊和異常捕捉");
        loggerCustomize.finest("[FINEST 300] 輸出最細的資訊");
    }

三:Log4j日誌框架

  Log4j全稱是Log for Java,它是Apache的一個開源專案,通過使用Log4j,我們可以控制日誌資訊輸出的位置是控制檯、檔案還是GUI元件,輸出位置甚至可以是套介面伺服器、NT的事件記錄器、UNIX Syslog守護程式等;Log4j也可以控制每一條日誌的輸出格式;通過定義每一條日誌資訊的級別,我們能夠更加細緻地控制日誌的生成過程。學習Log4j最好有JUL日誌框架基礎,因為大部分都類似。

1:入門案例

<!--Log4j日誌座標匯入-->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

//具體測試程式碼Demo @Test
public void demoA() { //建立日誌記錄器物件 注意是 org.apache.log4j.Logger 包下的 Logger logger = Logger.getLogger(ClientDemo.class); //列印日誌 logger.fatal("嚴重錯誤,一般造成系統崩潰並終止執行"); logger.error("錯誤資訊,不會影響系統執行"); logger.warn("警告資訊,可能會發生問題"); logger.info("執行資訊,資料連線,網路連線,IO操作等"); logger.debug("除錯資訊,一般在開發中使用,記錄程式變數傳遞資訊等等"); logger.trace("追蹤資訊,記錄程式所有的流程資訊"); } 控制檯並未列印日誌且列印警告: log4j:WARN No appenders could be found for logger (cn.xw.ClientDemo). log4j:WARN Please initialize the log4j system properly. log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
用過JUL的都知道,當普通的Logger沒有進行額外的配置時,其預設會繼承並使用RootLogger的配置。Log4j中也存在RootLogger,
但由於預設情況下RootLogger不具有任何的Appender(即Handler)。
如果程式碼僅為了測試某項功能,並不想編寫複雜的log4j.properties,可以使用Log4j提供的預設配置,在獲取Logger前新增以下程式碼載入
預設配置:BasicConfigurator.configure();
檢視configure()方法的原始碼: public static void configure() { Logger root = Logger.getRootLogger(); root.addAppender(new ConsoleAppender(new PatternLayout("%r [%t] %p %c %x - %m%n"))); } 早期的原始碼格式化有點不太現代,但意義明確:為RootLogger物件新增一個Appender,其中Appender的型別為控制器輸出的ConsoleAppender 輸出的格式使用new PatternLayout("%r [%t] %p %c %x - %m%n") 所以應急解決方案是在 Logger logger = Logger.getLogger(ClientDemo.class); 上加個 BasicConfigurator.configure();

2:日誌級別

Log4j中的日誌級別與JUL的不同,Log4j一共提供了六種日誌級別:
    FATAL【嚴重錯誤】
        指出每個嚴重的錯誤事件將會導致應用程式的退出。這個級別比較高了。重大錯誤,這種級別你可以直接停止程式了
    ERROR【錯誤資訊】:
        指出雖然發生錯誤事件,但仍然不影響系統的繼續執行。列印錯誤和異常資訊,如果不想輸出太多的日誌,可以使用這個級別
    WARN【警告資訊】:
        表明會出現潛在錯誤的情形,有些資訊不是錯誤資訊,但是也要給程式設計師的一些提示
    INFO【執行資訊】:
        訊息在粗粒度級別上突出強調應用程式的執行過程。列印一些你感興趣的或者重要的資訊,這個可以用於生產環境中輸出程式
        執行的一些重要資訊,但是不能濫用,避免列印過多的日誌。
    DEBUG【除錯資訊】【預設級別】
        指出細粒度資訊事件對除錯應用程式是非常有幫助的,主要用於開發過程中列印一些執行資訊
    TRACE【追蹤資訊】:
        記錄程式所有的流程資訊,很低的日誌級別,一般不會使用
    ALL:最低等級的,用於開啟所有日誌記錄。
    OFF:最高等級的,用於關閉所有日誌記錄。

為了測試預設日誌級別,可以使用以下程式碼測試RootLogger:
@Test
public void demoA () {
    // 初始化系統配置,不需要配置檔案(這裡直接使用預設配置)
    BasicConfigurator.configure();
    // 獲取日誌記錄器物件RootLogger
    final Logger rootLogger = Logger.getRootLogger();
    // 輸出配置詳情
    System.out.println("Logger level: " + rootLogger.getLevel());
    // 獲取全部Appends
    final Enumeration allAppenders = rootLogger.getAllAppenders();
    while (allAppenders != null && allAppenders.hasMoreElements()) {
        final Appender appender = (Appender) allAppenders.nextElement();
        System.out.println("Appender is: " + appender.getClass().getSimpleName());
    }
}
//輸出:
//Logger level: TRACE
//Appender is: ConsoleAppender

3:Log4j相關元件(重點)

Log4j主要由Loggers(日誌記錄器)、Appenders(輸出端)和Layout(日誌格式化器)組成:
    Loggers:控制日誌的輸出級別與日誌是否輸出
    Appenders:指定日誌的輸出方式(輸出到控制檯、檔案、資料庫等)
    Layout:控制日誌資訊的輸出格式

Ⅰ:Logger
    ①:日誌記錄器,負責收集處理日誌記錄,Logger的例項命名通常是類的全限定類名。
    ②:Logger的名字大小寫敏感,其命名有繼承機制。
        例如:name為org.apache.commons的logger會繼承name為org.apache的logger。
    注:自log4j 1.2版以來, Logger類已經取代了Category類。對於熟悉早期版本的log4j的人來說,Logger類可以被視為Category類的別名
Ⅱ:Appenders
    Appender用來指定日誌輸出到哪個地方,可以同時指定日誌的輸出目的地。
    Log4j常用的輸出目的地有以下幾種:
        ConsoleAppender:
            將日誌輸出到控制檯
        FileAppender:
            將日誌輸出到檔案中
        DailyRollingFileAppender:
            將日誌輸出到一個日誌檔案,週期為天,即每天輸出
        RollingFileAppender:
            將日誌資訊輸出到一個日誌檔案,並且指定檔案的大小,當超過指定大小,會自動將檔案重新命名,同時產生一個新的檔案
        JDBCAppender:
            將日誌資訊儲存到資料庫中
Ⅲ:Layouts
    佈局器Layouts用於控制日誌輸出內容的格式,我們可以使用各種自定義格式輸出日誌。
    Log4j常用的Layouts有以下幾種:
        HTMLLayout:
            格式化日誌輸出為HTML表格形式
        SimpleLayout:
            簡單的日誌輸出格式,列印的日誌格式為info-message
        PatternLayout:
            最強大的格式化方式,可以根據自定義格式輸出日誌,如果沒有指定轉換格式,則使用預設的轉換格式

補充:PatternLayout中的格式化規則:
    log4j採用類似C語言的printf函式的列印格式格式化日誌資訊,具體的佔位符及其含義如下:
        %m     輸出程式碼中指定的日誌資訊
        %p     輸出優先順序,及DEBUG、INFO等
        %n     換行符(Windows平臺的換行符為"\n",Unix平臺為"\n")
        %r     輸出自應用啟動到輸出該 log 資訊耗費的毫秒數
        %c     輸出列印語句所屬的類的全名
        %t     輸出產生該日誌的執行緒全名
        %d     輸出伺服器當前時間,預設為ISO8601,也可以指定格式,如:%d{yyyy年MM月dd日 HH:mm:ss}
        %l     輸出日誌時間發生的位置,包括類名、執行緒、及在程式碼中的行數。如:Test.main(Test.java:10)
        %F     輸出日誌訊息產生時所在的檔名稱
        %L     輸出程式碼中的行號
        %%     輸出一個"%"字元
    可以在%與字元之間加上修飾符來控制最小寬度、最大寬度和文字的對其方式。如:
        %5c     輸出category名稱,最小寬度是5,category<5,預設的情況下右對齊
        %-5c     輸出category名稱,最小寬度是5,category<5,"-"號指定左對齊,會有空格
        %.5c     輸出category名稱,最大寬度是5,category>5,就會將左邊多出的字元截掉,<5不會有空格
        %20.30c category名稱<20補空格,並且右對齊,>30字元,就從左邊交遠銷出的字元截掉

4:自定義配置檔案

  使用Log4j不需要特意載入配置檔案,對於Maven專案來說,程式會自動掃描resources目錄下的log4j.properties配置檔案。在下面的案例中我將針對Appenders的五種輸出端進行介紹

帶你掌握Java各種日誌框架
    @Test
    public void demoA() {
        //建立日誌記錄器物件 注意是 org.apache.log4j.Logger 包下的
        Logger logger = Logger.getLogger(ClientDemo.class);
        //列印日誌
        logger.fatal("嚴重錯誤,一般造成系統崩潰並終止執行");
        logger.error("錯誤資訊,不會影響系統執行");
        logger.warn("警告資訊,可能會發生問題");
        logger.info("執行資訊,資料連線,網路連線,IO操作等");
        logger.debug("除錯資訊,一般在開發中使用,記錄程式變數傳遞資訊等等");
        logger.trace("追蹤資訊,記錄程式所有的流程資訊");
    }
基本程式碼(程式碼都是一樣的,配置檔案才是我們重視的)
ConsoleAppender         對應自定義名稱:console
FileAppender           對應自定義名稱:myFile
RollingFileAppender       對應自定義名稱:rollingFile
DailyRollingFileAppender    對應自定義名稱:dailyFile
JDBCAppender           對應自定義名稱:dbFile
### 注:log4j.properties配置檔案要放在resources目錄下
# 指定日誌的輸出級別與輸出端(輸出級別,自定義輸出端名稱,自定義輸出端名稱....)
log4j.rootLogger = trace,console,myFile,rollingFile,dailyFile,dbFile

###### 控制檯輸出配置
log4j.appender.console = org.apache.log4j.ConsoleAppender
# 使用PatternLayout來宣告日誌用自定義格式
log4j.appender.console.layout = org.apache.log4j.PatternLayout
# 針對PatternLayout來用conversionPattern設定格式
log4j.appender.console.layout.conversionPattern = %d [%t] %-5p [%c] - %m%n

###### 檔案輸出配置
log4j.appender.myFile = org.apache.log4j.FileAppender
log4j.appender.myFile.layout = org.apache.log4j.PatternLayout
log4j.appender.myFile.layout.conversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [%t:%r] - [%p] %m%n
# 是否以追加日誌的形式新增(預設就是true)
log4j.appender.myFile.append = true
# 指定日誌的輸出路徑
log4j.appender.myFile.file = ./fileLog.log
# 指定日誌的檔案編碼
log4j.appender.myFile.encoding = utf-8

###### 檔案自動按大小拆分配置
log4j.appender.rollingFile = org.apache.log4j.RollingFileAppender
log4j.appender.rollingFile.layout = org.apache.log4j.PatternLayout
log4j.appender.rollingFile.layout.conversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [%t:%r] - [%p] %m%n
log4j.appender.rollingFile.append = true
log4j.appender.rollingFile.file = ./fileRollingFileLog.log
log4j.appender.rollingFile.encoding = UTF-8
# 檔案內容超過2KB則進行拆分,拆分的最多檔案由maxBackupIndex定義
log4j.appender.rollingFile.maxFileSize = 2KB
# 檔案拆分的數量(這裡是3),當檔案拆分的數量超限時則最新拆分出的檔案覆蓋最老的日誌檔案
log4j.appender.rollingFile.maxBackupIndex = 3

###### 檔案自動按日期拆分配置
log4j.appender.dailyFile = org.apache.log4j.DailyRollingFileAppender
log4j.appender.dailyFile.layout = org.apache.log4j.PatternLayout
log4j.appender.dailyFile.layout.conversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [%t:%r] - [%p] %m%n
log4j.appender.dailyFile.append = true
log4j.appender.dailyFile.file = ./dailyRollingFile.log
log4j.appender.dailyFile.encoding = UTF-8
# 檔案拆分的日期 正常按照 '.'yyyy-MM-dd 以天為拆分,這裡我以每秒拆分一次
log4j.appender.dailyFile.datePattern = '.'yyyy-MM-dd HH-mm-ss

# MySQL輸出配置
log4j.appender.dbFile = org.apache.log4j.jdbc.JDBCAppender
log4j.appender.dbFile.layout = org.apache.log4j.PatternLayout
log4j.appender.dbFile.layout.conversionPattern = %p %r %c %t %d{yyyy/MM/dd HH:mm:ss:SSS} %m %l %F %L %% %n
log4j.appender.dbFile.URL = jdbc:mysql://localhost:3306/log4j?serverTimezone=GMT%2B8&useAffectedRows=true&useSSL=false
log4j.appender.dbFile.User = root
log4j.appender.dbFile.Password = 123
log4j.appender.dbFile.Sql=INSERT INTO log(project_name, create_date, level, category, file_name, thread_name, line, all_category, message) \
  values('log4j_xiaofeng', '%d{yyyy-MM-dd HH:mm:ss}', '%p', '%c', '%F', '%t', '%L', '%l', '%m')
-- 輸出MySQL配置需要在資料庫建立表
CREATE
TABLE `log` ( `log_id` INT ( 11 ) NOT NULL AUTO_INCREMENT, `project_name` VARCHAR ( 255 ) DEFAULT NULL COMMENT '專案名稱', `create_date` VARCHAR ( 255 ) DEFAULT NULL COMMENT '建立時間', `level` VARCHAR ( 255 ) DEFAULT NULL COMMENT '優先順序', `category` VARCHAR ( 255 ) DEFAULT NULL COMMENT '所在類的全名', `file_name` VARCHAR ( 255 ) DEFAULT NULL COMMENT '輸出日誌訊息產生時所在的檔名稱 ', `thread_name` VARCHAR ( 255 ) DEFAULT NULL COMMENT '日誌事件的執行緒名', `line` VARCHAR ( 255 ) DEFAULT NULL COMMENT '行號', `all_category` VARCHAR ( 255 ) DEFAULT NULL COMMENT '日誌事件的發生位置', `message` VARCHAR ( 4000 ) DEFAULT NULL COMMENT '輸出程式碼中指定的訊息', PRIMARY KEY ( `log_id` ) );

5:自定義配置Logger

如下我建立了兩個日誌記錄器物件
    Logger.getLogger("cn.xw.mapper.StudentMapper");
    Logger.getLogger("cn.xw.mapper.TeacherMapper");
現在要讓特定名稱的logger使用特定的配置:
    通過log4j.logger.{loggerName}的配置方式,讓指定名為loggerName的logger使用該配置,由於該logger仍然是隸屬
   於rootLogger因此輸出是累加的形式。如果RootLogger使用了ConsoleAppender,同時自定義的Logger也使用了
   ConsoleAppender,此時控制檯將輸出兩次日誌記錄,一次為自定義Logger繼承自RootLogger的輸出,另一次則為自定義
   Logger自身的輸出。但日誌等級level則取決於子日誌Logger為準。    例:此時RootLogger和Logger同時使用了ConsoleAppender,但輸出等級分別為INFO(父)和WARN(子),此時控制檯輸出WARN等級
要求"cn.xw.mapper.StudentMapper"日誌輸出到控制檯;"cn.xw.mapper.TeacherMapper"日誌輸出到檔案
# 指定rootLogger根日誌的輸出級別與輸出端
log4j.rootLogger = trace,console

#注:下面兩個自定義的Logger都會繼承rootLogger,所以StudentMapper會輸出控制檯2次,級別的話會取子級別”debug“
# 自定義Logger用來匹配 Logger.getLogger("cn.xw.mapper.StudentMapper");
log4j.logger.cn.xw.mapper.StudentMapper = debug,console
# 自定義Logger用來匹配 Logger.getLogger("cn.xw.mapper.TeacherMapper");
log4j.logger.cn.xw.mapper.TeacherMapper = info,myFile

# 控制檯輸出Appender
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.conversionPattern = %d [%t] %-5p [%c] - %m%n

# 檔案輸出Appender
log4j.appender.myFile = org.apache.log4j.FileAppender
log4j.appender.myFile.layout = org.apache.log4j.PatternLayout
log4j.appender.myFile.layout.conversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [%t:%r] - [%p] %m%n
log4j.appender.myFile.append = true
log4j.appender.myFile.file = ./fileLog.log
log4j.appender.myFile.encoding = utf-8
帶你掌握Java各種日誌框架
@Test
public void demoA() {
    //建立日誌記錄器物件 注意是 org.apache.log4j.Logger 包下的 cn.xw.mapper.StudentMapper
    Logger logger = Logger.getLogger("cn.xw.mapper.StudentMapper");
    logger.fatal("嚴重錯誤,一般造成系統崩潰並終止執行");
    logger.error("錯誤資訊,不會影響系統執行");
    logger.warn("警告資訊,可能會發生問題");
    logger.info("執行資訊,資料連線,網路連線,IO操作等");
    logger.debug("除錯資訊,一般在開發中使用,記錄程式變數傳遞資訊等等");
    logger.trace("追蹤資訊,記錄程式所有的流程資訊");
    // cn.xw.mapper.TeacherMapper
    Logger logger1 = Logger.getLogger("cn.xw.mapper.TeacherMapper");
    logger1.fatal("嚴重錯誤,一般造成系統崩潰並終止執行");
    logger1.error("錯誤資訊,不會影響系統執行");
    logger1.warn("警告資訊,可能會發生問題");
    logger1.info("執行資訊,資料連線,網路連線,IO操作等");
    logger1.debug("除錯資訊,一般在開發中使用,記錄程式變數傳遞資訊等等");
    logger1.trace("追蹤資訊,記錄程式所有的流程資訊");
}
/** 列印資訊
2022-02-09 15:45:49,172 [main] FATAL [cn.xw.mapper.StudentMapper] - 嚴重錯誤,一般造成系統崩潰並終止執行
2022-02-09 15:45:49,172 [main] FATAL [cn.xw.mapper.StudentMapper] - 嚴重錯誤,一般造成系統崩潰並終止執行
2022-02-09 15:45:49,172 [main] ERROR [cn.xw.mapper.StudentMapper] - 錯誤資訊,不會影響系統執行
2022-02-09 15:45:49,172 [main] ERROR [cn.xw.mapper.StudentMapper] - 錯誤資訊,不會影響系統執行
2022-02-09 15:45:49,173 [main] WARN  [cn.xw.mapper.StudentMapper] - 警告資訊,可能會發生問題
2022-02-09 15:45:49,173 [main] WARN  [cn.xw.mapper.StudentMapper] - 警告資訊,可能會發生問題
2022-02-09 15:45:49,173 [main] INFO  [cn.xw.mapper.StudentMapper] - 執行資訊,資料連線,網路連線,IO操作等
2022-02-09 15:45:49,173 [main] INFO  [cn.xw.mapper.StudentMapper] - 執行資訊,資料連線,網路連線,IO操作等
2022-02-09 15:45:49,173 [main] DEBUG [cn.xw.mapper.StudentMapper] - 除錯資訊,一般在開發中使用,記錄程式變數傳遞資訊等等
2022-02-09 15:45:49,173 [main] DEBUG [cn.xw.mapper.StudentMapper] - 除錯資訊,一般在開發中使用,記錄程式變數傳遞資訊等等
注:這下面就是繼承了RootLogger輸出的 TeacherMapper資訊 ,它子物件是輸出到檔案裡,
2022-02-09 15:45:49,173 [main] FATAL [cn.xw.mapper.TeacherMapper] - 嚴重錯誤,一般造成系統崩潰並終止執行
2022-02-09 15:45:49,173 [main] ERROR [cn.xw.mapper.TeacherMapper] - 錯誤資訊,不會影響系統執行
2022-02-09 15:45:49,173 [main] WARN  [cn.xw.mapper.TeacherMapper] - 警告資訊,可能會發生問題
2022-02-09 15:45:49,174 [main] INFO  [cn.xw.mapper.TeacherMapper] - 執行資訊,資料連線,網路連線,IO操作等
Process finished with exit code 0
*/
測試程式碼資訊
這時就有人問了,自定義的LoggerName是"cn.xw.mapper.StudentMapper"使用ConsoleAppender輸出了兩次,一次是繼承父Logger,一次是
自己原本的;若想輸出一次則有兩種方式:
①:摒棄RootLogger的輸出,即斷開指定Logger與RootLogger的繼承關係
    # 自定義Logger用來匹配 Logger.getLogger("cn.xw.mapper.StudentMapper");
    log4j.logger.cn.xw.mapper.StudentMapper = debug,console
    # 名為“cn.xw.mapper.StudentMapper”的Logger不再繼承使用RootLogger中的Appender
    log4j.additivity.cn.xw.mapper.StudentMapper = false
②:摒棄Logger的輸出,即指定名稱的Logger直接使用RootLogger關聯的Appender,子Logger不再額外指定和RootLogger相同的Appender
    # 自定義Logger用來匹配 Logger.getLogger("cn.xw.mapper.StudentMapper");
    log4j.logger.cn.xw.mapper.StudentMapper = debug

6:日誌輸出詳細資訊

    @Test
    public void demoA() {
        //設定內部除錯模式,開啟此功能會在控制檯詳細記錄Log4j在記錄日誌時列印的日誌,記錄自己
        LogLog.setInternalDebugging(true);
        //建立日誌記錄器物件
        Logger logger = Logger.getLogger("cn.xw.mapper.StudentMapper");
        logger.fatal("嚴重錯誤,一般造成系統崩潰並終止執行");
    }

四:JCL日誌門面框架

  JCL(Jakarta Commons Logging)是Apache提供的一個通用日誌API(日誌門面)。使用者可以自由選擇第三方的日誌元件作為具體實現,像log4j或者JDK自帶日誌框架(JUL),選擇Log4j或JUL的第三方日誌框架後,common-logging會通過動態查詢的機制,在程式執行時自動找出真正使用的日誌庫。當然common-logging內部有一個Simple logger 的簡單實現,但是功能很弱。所以使用common-logging,通常都是配合著log4j以及其它日誌框架來使用。使用它的好處就是我們程式碼依賴common-logging的API而非log4j的API,避免了和具體的日誌API直接耦合,在有必要時,可以更改日誌實現的第三方庫。

JCL有兩個基本抽象類:Log(基本記錄器)和LogFactory(負責建立Log例項)

1:JCL入門及總結

<!--JCL門面座標依賴-->
    <dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.2</version>
    </dependency>

// 入門程式碼
public class JCLClient {
    @Test
    public void demoA() {
        //建立日誌物件;LogFactory是一個抽象類,LogFactoryImpl才是具體實現
        Log logger = LogFactory.getLog(JCLClient.class);
        //日誌記錄輸出
        logger.fatal("嚴重錯誤,一般造成系統崩潰並終止執行");
        logger.error("錯誤資訊,不會影響系統執行");
        logger.warn("警告資訊,可能會發生問題");
        logger.info("執行資訊,資料連線,網路連線,IO操作等"); // 預設級別INFO(和JUL預設級別一樣)
        logger.debug("除錯資訊,一般在開發中使用,記錄程式變數傳遞資訊等等");
        logger.trace("追蹤資訊,記錄程式所有的流程資訊");
    }
}

  二月 09, 2022 9:49:11 下午 cn.xw.JCLClient demoA
  嚴重: 嚴重錯誤,一般造成系統崩潰並終止執行
  二月 09, 2022 9:49:11 下午 cn.xw.JCLClient demoA
  嚴重: 錯誤資訊,不會影響系統執行
  二月 09, 2022 9:49:11 下午 cn.xw.JCLClient demoA
  警告: 警告資訊,可能會發生問題
  二月 09, 2022 9:49:11 下午 cn.xw.JCLClient demoA
  資訊: 執行資訊,資料連線,網路連線,IO操作等

  通過上面的入門案例我們可以發現,列印出來的日誌和JUL的日誌樣式是一樣的,那麼我猜測它底層的日誌實現使用的是JUL,因為我並未匯入Log4j包,那匯入了Log4j座標是什麼樣的呢?測試一下看看

<!--Log4j日誌框架座標-->
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.17</version>
    </dependency>

//匯入後我們編寫的程式碼不用改變,直接可以執行,日誌列印出log4j:WARN No appenders could be found for logger (cn.xw.JCLClient).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.這就證明了,匯入Log4j日誌框架座標後JCL會動態查詢到Log4j並切換Log4j作為底層日誌列印的第三方框架
這裡在resources目錄下編寫一個log4j.properties配置檔案即可

總結:我們為什麼要使用日誌門面: ①:面向介面開發,不再依賴具體的實現類。減少程式碼的耦合 ②:專案通過匯入不同的日誌實現類,可以靈活的切換日誌框架 ③:統一API,方便開發者學習和使用 ④:統一配置便於專案日誌的管理

2:原始碼分析

  檢視Log介面並游標對著Log介面按下CTRL+H來檢視具體的實現類

AvalonLoggerJdk13LumberjackLoggerJdk14LoggerLog4JLoggerLogKitLoggerNoOpLogSimpleLog

進入JDK14Logger類:

//注意這裡引入的類是JDK自帶的日誌JUL的Logger類
import java.util.logging.Level;
import java.util.logging.Logger;

public class Jdk14Logger implements Log, Serializable {
    protected static final Level dummyLevel = Level.FINE;
    protected transient Logger logger = null;//預設為null
    public Jdk14Logger(String name) {
        this.name = name;
        //呼叫了自身的getLogger()方法
        logger = getLogger();
    }
    //1、獲取Logger例項方法
    public Logger getLogger() {
        if (logger == null) {
            //重要:這裡呼叫的是java.util.logging.Logger(即JUL)的獲取例項方法
            logger = Logger.getLogger(name);
        }
        return logger;
    }
    //info日誌等級呼叫
    public void info(Object message) {
        //實際上就是呼叫的JUL的INFO日誌等級的Log方法
        log(Level.INFO, String.valueOf(message), null);
    }
    //fatal:我們之前在Log4j中看到是最高等級,這裡也用來表示最高的意思
    public void fatal(Object message, Throwable exception) {
        //實際上就是呼叫JUL的SERVER日誌等級(最高)的Log方法
        log(Level.SEVERE, String.valueOf(message), exception);
    }
}

  看到這裡其實心裡就清楚了,原來就是外面套了個方法呀,通過繼承Log介面來讓Log介面統一管理各個日誌框架,從而達到切換日誌框架不改變程式碼的操作。那對於JCL中Log4j的方法我們也差不多知道了是如何實現了的。

帶你掌握Java各種日誌框架
//注意這裡使用的是Log4j的日誌
import org.apache.log4j.Logger;
import org.apache.log4j.Priority;
import org.apache.log4j.Level;

public class Log4JLogger implements Log, Serializable {

        //預設為null
        private transient volatile Logger logger = null;
        private static final String FQCN = Log4JLogger.class.getName();
        
        //類被載入時進行初始化
        static {
            if (!Priority.class.isAssignableFrom(Level.class)) {
                throw new InstantiationError("Log4J 1.2 not available");
            }
            
            Priority _traceLevel;
            try {
            } catch(Exception ex) {
                _traceLevel = Level.DEBUG;
            }
            traceLevel = _traceLevel;
        }
    
        //無參構造器
        public Log4JLogger() {
            name = null;
        }
    
        //有參構造器
        public Log4JLogger(String name) {
            this.name = name;
            //通過呼叫自身類的getLogger()獲取
            this.logger = getLogger();//見1
        }
    
       //1、通過org.apache.log4j.Logger的getLogger()來獲取例項
       public Logger getLogger() {
            Logger result = logger;
            if (result == null) {
                synchronized(this) {
                    result = logger;
                    if (result == null) {
                        logger = result = Logger.getLogger(name);
                    }
                }
            }
            return result;
        }
        
        //fatal等級方法就是呼叫的Log4j的FATAL等級,是一致的
        public void fatal(Object message) {
            getLogger().log(FQCN, Level.FATAL, message, null);
        }
}
這裡是匯入Log4j後會例項Log4jLogger物件

  那為什麼新增一個Log4j座標底層就切換到了Log4j的日誌框架輸出列印,去除Log4j座標就變成JUL日誌框架列印了,別急我們這就要檢視LogFactory.getLog(xxx.class)的getLog方法了,通過它獲取不同的Log物件

// LogFactory抽象物件
public abstract class LogFactory {
    ①:獲取Log物件例項
    public static Log getLog(Class clazz) throws LogConfigurationException {
        //獲取具體例項
        return getFactory().getInstance(clazz);
    }
}

// LogFactoryImpl是LogFactory具體實現類
public class LogFactoryImpl extends LogFactory {
...
    //該陣列中包含了對應的全限定類名
    private static final String[] classesToDiscover = new String[]
    {"org.apache.commons.logging.impl.Log4JLogger",
    "org.apache.commons.logging.impl.Jdk14Logger",
    "org.apache.commons.logging.impl.Jdk13LumberjackLogger",
    "org.apache.commons.logging.impl.SimpleLog"};
...
    ②:獲取Log例項
    public Log getInstance(Class clazz) throws LogConfigurationException {
        //這裡傳入類的名稱
        return this.getInstance(clazz.getName());
    }
...
    ③:其中獲取到Log例項
    public Log getInstance(String name) throws LogConfigurationException {
        Log instance = (Log)this.instances.get(name);
        if (instance == null) {
            //通過類名獲取到Log例項
            instance = this.newInstance(name);
            this.instances.put(name, instance);
        }
        return instance;
    }
...
    ④:獲取新的例項
    protected Log newInstance(String name) throws LogConfigurationException {
        try {
            Log instance;
            Object[] params;
            //預設為null
            if (this.logConstructor == null) {
                //重要:查詢Log實現類,及獲取Log的單個實現類
                instance = this.discoverLogImplementation(name);
            } else {
                params = new Object[]{name};
                instance = (Log)this.logConstructor.newInstance(params);
            }

            if (this.logMethod != null) {
                params = new Object[]{this};
                this.logMethod.invoke(instance, params);
            }
            //返回獲取到的Log例項即可
            return instance;
        } catch (LogConfigurationException var5)
        ...
    }

    //⑤:該方法通過陣列的順序來進行實現類例項
    private Log discoverLogImplementation(String logCategory) throws LogConfigurationException {
        ...
        //核心部分:從陣列中進行遍歷,可見classesToDiscover陣列內容(上面),順序依次為Log4JLogger、Jdk14Logger..
        //其中的result == null則是判斷是否獲取到了例項,若獲取到退出for迴圈
     //迴圈根據多種框架實現類的全限定類目去查詢獲取的Log例項是否存在
for(int i = 0; i < classesToDiscover.length && result == null; ++i) { result = this.createLogFromClass(classesToDiscover[i], logCategory, true); } if (result == null) { throw new LogConfigurationException("No suitable Log implementation"); } else { //返回指定例項 return result; } } }

  我們在呼叫LogFactory.getLog()方法時獲取了JCL中Log介面的實現類例項,在實現類中的構造器裡獲取到了真正對應的日誌框架Logger例項

總結:
    1、JCL日誌門面中Log是通用介面,LogFactory.getLog()用於獲取對應Log的實現類。
    2、Log介面(包含了主要的日誌等級方法)的實現類主要的是兩個,Jdk14Logger(即JUL,不導任何包預設)和Log4JLogger
        (即Log4j,匯入Log4j則切換)。
    3、對於獲取Log例項是在LogFactory.getLog()中執行獲取的,執行順序見如下
        "org.apache.commons.logging.impl.Log4JLogger"
        "org.apache.commons.logging.impl.Jdk14Logger"
        "org.apache.commons.logging.impl.Jdk13LumberjackLogger"
        "org.apache.commons.logging.impl.SimpleLog"
    4、在獲取到指定Log例項時,即使用構造器會進行各個實現類中的Logger例項的賦值獲取(通過對應的日誌框架),
        其中的日誌等級方法呼叫的是對應框架的日誌等級方法。

五:SLF4J日誌門面框架(重點)

  SLF4J即簡單日誌門面(Simple Logging Facade for Java),它主要是為了給Java日誌訪問提供一套標準、規範的API框架,其主要意義在於提供介面,具體的實現可以交由其它日誌框架,例如log4j和logback等;當然slf4j自己也提供了功能較為簡單的實現,但是一般很少用到;對於一般的Java專案而言,日誌框架會選擇slf4j-api作為門面,配上具體的實現框架(log4j、logback等),中間使用橋接器完成橋接。所以我們可以得出SLF4J最重要的兩個功能就是對於日誌框架的繫結以及日誌框架的橋接。SLF4J和JCL這兩個門面框架差不多。

核心功能:日誌框架的繫結、日誌框架的橋接

1:SLF4J入門案例及說明

<!--SLF4J基本的API座標依賴;注單獨匯入這個是沒有日誌效果-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.35</version>
</dependency>
<!--基本的simple,就是剛才說的SLF4J提供的基本日誌實現,-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>1.7.35</version>
</dependency>
    @Test
    public void demoA() {
        //通過Logger工廠物件動態獲取我們具體匯入的日誌實現框架(Log4j、Logback、JUL、slf4j-simple)
        Logger logger = LoggerFactory.getLogger(Slf4jDemo.class);
        //日誌列印方式1
        logger.error("[error  ] 最高階別程式錯誤");
        logger.warn("[warning] 程式警告不會出現錯誤");
        logger.info("[info   ] 程式info資料連線,網路連線,IO操作等"); // 預設級別
        logger.debug("[debug  ] 一般在開發中使用,記錄程式變數傳遞資訊等等");
        logger.trace("[trace  ] 記錄程式所有的流程資訊");
        //日誌列印方式2
        String name = "螞蟻小哥", age = "23";
        logger.info("info日誌記錄:我的姓名是:{} 年齡:{}", name, age);
        //日誌列印方式3
        try {
            int a = 1 / 0;
        } catch (Exception e) {
            logger.error("程式發生嚴重錯誤!", e);
        }
    }

[main] ERROR cn.xw.Slf4jDemo - [error  ] 最高階別程式錯誤
[main] WARN cn.xw.Slf4jDemo - [warning] 程式警告不會出現錯誤
[main] INFO cn.xw.Slf4jDemo - [info   ] 程式info資料連線,網路連線,IO操作等
[main] INFO cn.xw.Slf4jDemo - info日誌記錄:我的姓名是:螞蟻小哥 年齡:23
[main] ERROR cn.xw.Slf4jDemo - 程式發生嚴重錯誤!
java.lang.ArithmeticException: / by zero
    at cn.xw.Slf4jDemo.demoA(Slf4jDemo.java:30)
    ....

2:日誌級別

  這裡我就簡單和大家闡述一下了,因為大同小異,和上面的日誌框架的級別差不多

ERROR【錯誤資訊】:
    用來記錄執行時的錯誤資訊,表示程式執行過程中出現了需要被解決的問題,往往是一些異常。使用error日誌的時候
    一般會將詳細的異常出現的原因記錄。如果不想輸出太多的日誌,可以使用這個級別
WARN【警告資訊】:
    用來記錄一些警告資訊。警告資訊表示,程式進入了一個特殊的狀態,在該狀態下程式可以繼續執行,但是不建議讓程式進入
    該狀態,因為該狀態可能導致結果出現問題;有些資訊不是錯誤資訊,但是也要給程式設計師的一些提示
INFO【執行資訊】:
    訊息在粗粒度級別上突出強調應用程式的執行過程。列印一些你感興趣的或者重要的資訊,這個可以用於生產環境中輸出程式
    執行的一些重要資訊,但是不能濫用,避免列印過多的日誌。
DEBUG【除錯資訊】:
    這類日誌往往用在判斷是否有出現bug的場景,且往往記錄了程式碼執行的詳細資訊,比如方法呼叫傳入的引數資訊,適用開發環境
TRACE【追蹤資訊】:
    最低優先順序的日誌,一般用來追蹤詳細的程式執行流,比如程式的執行過程中,執行到了哪一個方法,進入了哪一條分支。
    通過trace程式的執行流程,可以判斷程式是否按照期望的邏輯在執行,一般不會使用

3:常見日誌框架的繫結整合

  通過前面介紹知道SLF4J它只是個門面框架,只提供API介面而不提供底層實現,所以我們就要繫結具體的日誌實現框架;在入門案例中我們已經繫結了SLF4J官方的自己編寫的入門日誌框架,這裡就不在過多介紹,因為功能單調;

針對上面的編號進行介紹:
①:SLF4J
    單獨匯入slf4j-api是沒有日誌列印的效果,只會列印幾句提示資訊,提示未繫結日誌實現,因為底層沒有繫結具體的日誌框架
②:SLF4J+logback
    底層繫結logback日誌實現框架
③:SLF4J+reload4j(Log4j)
    底層繫結Log4j日誌實現框架(reload4j是Log4j的升級版,因為之前Log4j出現了重大漏洞)
④:SLF4J+JUL
    底層繫結Java自帶的JUL日誌實現框架
⑤:SLF4J+Simple
    底層繫結SLF4J官方推出的基本日誌實現框架
⑥:SLF4J+nop
    關閉一切日誌輸出資訊
下面按照順序依次介紹及使用

 ①:SLF4J

<!--SLF4J基本的API座標依賴;注單獨匯入這個是沒有日誌效果-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.35</version>
</dependency>

//測試程式碼(這個測試程式碼是通用的,因為使用的是門面API,後面的案例可以不要改變測試程式碼)
@Test
public void demoA() {
    //通過Logger工廠物件動態獲取我們具體匯入的日誌實現框架(Log4j、Logback、JUL、slf4j-simple)
    Logger logger = LoggerFactory.getLogger(Slf4jDemo.class);
    //日誌列印方式
    logger.error("[error  ] 最高階別程式錯誤");
    logger.warn("[warning] 程式警告不會出現錯誤");
    logger.info("[info   ] 程式info資料連線,網路連線,IO操作等");
    logger.debug("[debug  ] 一般在開發中使用,記錄程式變數傳遞資訊等等");
    logger.trace("[trace  ] 記錄程式所有的流程資訊");
}

<!--具體列印-->
<!--提示我們說沒有繫結具體的日誌實現-->
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

②:SLF4J+LogBack

<!--整合logback 在maven中只要匯入logback-classic即可,code會依賴傳遞-->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.10</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
    <version>1.2.10</version>
</dependency>

<!--定義一個logback.xml檔案放到resources目錄下,沒有當前檔案則走預設配置,後面章節會詳細講解logback-->
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false" scan="false" scanPeriod="30 seconds">
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    <root level="DEBUG">
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>

<!--列印效果-->
17:35:04.120 [main] ERROR cn.xw.Slf4jDemo - [error  ] 最高階別程式錯誤
17:35:04.124 [main] WARN  cn.xw.Slf4jDemo - [warning] 程式警告不會出現錯誤
17:35:04.125 [main] INFO  cn.xw.Slf4jDemo - [info   ] 程式info資料連線,網路連線,IO操作等
17:35:04.126 [main] DEBUG cn.xw.Slf4jDemo - [debug  ] 一般在開發中使用,記錄程式變數傳遞資訊等等

③:SLF4J+reload4j(Log4j)

<!--reload4j介面卡  替代Log4j-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-reload4j</artifactId>
    <version>1.7.35</version>
</dependency>
<!--reload4j(原名Log4j)-->
<dependency>
    <groupId>ch.qos.reload4j</groupId>
    <artifactId>reload4j</artifactId>
    <version>1.2.18.5</version>
</dependency>

<!--建立log4j.properties放在resources目錄下(必須有這個檔案,否則無法使用)-->
log4j.rootLogger=DEBUG,console
#輸出到控制檯
log4j.appender.console=org.apache.log4j.ConsoleAppender
#設定輸出樣式
log4j.appender.console.layout=org.apache.log4j.PatternLayout
#日誌輸出資訊格式為
log4j.appender.console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n

<!--日誌列印-->
2022-02-12 17:59:30,428 [main] ERROR [cn.xw.Slf4jDemo] - [error  ] 最高階別程式錯誤
2022-02-12 17:59:30,429 [main] WARN  [cn.xw.Slf4jDemo] - [warning] 程式警告不會出現錯誤
2022-02-12 17:59:30,429 [main] INFO  [cn.xw.Slf4jDemo] - [info   ] 程式info資料連線,網路連線,IO操作等
2022-02-12 17:59:30,429 [main] DEBUG [cn.xw.Slf4jDemo] - [debug  ] 一般在開發中使用,記錄程式變數傳遞資訊等等

④:SLF4J+JUL

<!--JUL介面卡-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-jdk14</artifactId>
    <version>1.7.35</version>
</dependency>

// 列印日誌
二月 12, 2022 6:01:45 下午 cn.xw.Slf4jDemo demoA
嚴重: [error  ] 最高階別程式錯誤
二月 12, 2022 6:01:45 下午 cn.xw.Slf4jDemo demoA
警告: [warning] 程式警告不會出現錯誤
二月 12, 2022 6:01:45 下午 cn.xw.Slf4jDemo demoA
資訊: [info   ] 程式info資料連線,網路連線,IO操作等

⑤:SLF4J+Simple

  這個可以參考入門案例

⑥:SLF4J+nop

<!--slf4j-nop禁用日誌,匯入這個座標即可-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-nop</artifactId>
    <version>1.7.35</version>
</dependency>

⑦:注意事項

座標匯入不能出現衝突情況,比如既匯入了Log4j作底層實現也匯入了LogBack作底層實現,這樣就會產生衝突,會選擇其中一個,
選擇規律是先匯入的被選擇
這時日誌列印出多個實現框架,並在後面選擇出當前正在使用的日誌列印框架是logback
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/I:/maven_repository/ch/qos/logback/logback-classic/1.2.10/logback-classic-1.2.10.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/I:/maven_repository/org/slf4j/slf4j-reload4j/1.7.35/slf4j-reload4j-1.7.35.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]

4:SLF4J日誌橋接技術

  假設當初的專案使用的是非SLF4J的日誌技術(這裡我就以當初使用Log4j來說),但是在不久的將來感覺這些日誌框架不是太好,這時我想切換SLF4J門面+LogBack日誌框架來實現具體的日誌記錄,那麼當初在專案中引用的Log4j座標將不能再繼續使用下去了,因為我想把整個專案中的日誌記錄全部交由最終的LogBack輸出,如果Log4j引用的座標不剔除,那麼最終以前的日誌列印還是走的Log4j輸出

<!--當初使用Log4j日誌來列印日誌-->
<!--reload4j(原名Log4j)-->
<dependency>
    <groupId>ch.qos.reload4j</groupId>
    <artifactId>reload4j</artifactId>
    <version>1.2.18.5</version>
</dependency>

<!--後來把整個專案改變為SLF4J門面+LogBack日誌框架-->
<!--SLF4J基本的API座標依賴;注單獨匯入這個是沒有日誌效果-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.35</version>
</dependency>
<!--整合logback 在maven中只要匯入logback-classic即可,code會依賴傳遞-->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.10</version>
</dependency>
<!--reload4j的橋接器,橋接到當前SLF4J門面中,還需把原來的reload4j註釋掉-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>log4j-over-slf4j</artifactId>
    <version>1.7.35</version>
</dependency>

六:Logback日誌框架

Logback 是由log4j創始人設計的另一個開源日誌元件;可分為以下幾個模組:
    logback-core:
        其它兩個模組的基礎模組
    logback-classic:
        它是log4j的一個改良版本,同時它完整實現了slf4j API使你可以很方便地更換成其它日誌系統如log4j或JDK14 Logging
    logback-access:
        訪問模組與Servlet容器整合提供通過Http來訪問日誌的功能
詳細學習Logback請參考 logback系列教程 (51gjie.com)

1:Logback取代Log4j的理由

1:更快的實現:Logback的核心重寫了,在一些關鍵執行路徑上效能提升10倍以上。而且logback不僅效能提升了,初始化記憶體載入也更小了。
2:非常充分的測試:Logback經過了幾年,數不清小時的測試。Logback的測試完全不同級別的。
3:Logback-classic非常自然實現了SLF4j:Logback-classic實現了SLF4j。在使用SLF4j中,你都感覺不到logback-classic。
    而且因為logback-classic非常自然地實現了slf4j,所以切換到log4j或者其他,非常容易,只需要提供成另一個jar包就OK,
    根本不需要去動那些通過SLF4J API實現的程式碼。
4:非常充分的文件 官方網站有兩百多頁的文件。
5:自動重新載入配置檔案,當配置檔案修改了,Logback-classic能自動重新載入配置檔案。掃描過程快且安全,它並不需要另外建立一個
  掃描執行緒。這個技術充分保證了應用程式能跑得很歡在JAVA EE環境裡面。 6:謹慎的模式和非常友好的恢復,在謹慎模式下,多個FileAppender例項跑在多個JVM下,能夠安全地寫到同一個日誌檔案。
  RollingFileAppender會有些限制。Logback的FileAppender和它的子類包括RollingFileAppender能夠非常友好地從I/O異常
  中恢復。 7:配置檔案可以處理不同的情況,開發人員經常需要判斷不同的Logback配置檔案在不同的環境下(開發,測試,生產)。而這些配置檔案僅僅
  只有一些很小的不同,可以通過,和來實現,這樣一個配置檔案就可以適應多個環境。 8:Filters(過濾器)有些時候,需要診斷一個問題,需要打出日誌。在log4j,只有降低日誌級別,不過這樣會打出大量的日誌,會影響應
  用效能。在Logback,你可以繼續 保持那個日誌級別而除掉某種特殊情況,如alice這個使用者登入,她的日誌將打在DEBUG級別而其他   使用者可以繼續打在WARN級別。要實現這個功能只需加4行XML配置。可以參考MDCFIlter 。 9:SiftingAppender(一個非常多功能的Appender):它可以用來分割日誌檔案根據任何一個給定的執行引數。如,SiftingAppender能夠   區別日誌事件跟進使用者的Session,然後每個使用者會有一個日誌檔案。 10:自動壓縮已經打出來的log:RollingFileAppender在產生新檔案的時候,會自動壓縮已經打出來的日誌檔案。壓縮是個非同步過程,   所以甚至對於大的日誌檔案,在壓縮過程中應用不會受任何影響。 11:堆疊樹帶有包版本:Logback在打出堆疊樹日誌時,會帶上包的資料。 12:自動去除舊的日誌檔案:通過設定TimeBasedRollingPolicy或者SizeAndTimeBasedFNATP的maxHistory屬性,你可以控制已經產生日   志檔案的最大數量。如果設定maxHistory 12,那那些log檔案超過12個月的都會被自動移除。

2:Logback入門案例

        <!--Logback基礎模組-->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.2.10</version>
        </dependency>
        <!--logback-classic是log4j改良版本,它完整實現SLF4J API,這樣只要匯入此座標就可以和SLF4J API契合-->
        <!--匯入此座標會自帶依賴匯入一個core基本包-->
        <!--這個依賴直接包含了 logback-core 以及 slf4j-api的依賴-->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.10</version>
        </dependency>
@Test
public void demoA() {
    //通過Logger工廠物件動態獲取我們具體匯入的日誌實現框架Logback
    //因為匯入logback-classic座標後會自動依賴傳遞SLF4J-api門面
    Logger logger = LoggerFactory.getLogger(LogbackDemo.class);
    //日誌列印方式
    logger.error("[error  ] 最高階別程式錯誤");
    logger.warn(" [warning] 程式警告不會出現錯誤");
    logger.info(" [info   ] 程式info資料連線,網路連線,IO操作等");
    logger.debug("[debug  ] 一般在開發中使用,記錄程式變數傳遞資訊等等"); // 預設級別
    logger.trace("[trace  ] 記錄程式所有的流程資訊");
}
注:Logback因為和SLF4J API緊密結合,所以日誌級別Level和SLF4J一樣;
不指定配置檔案則有預設配置檔案
日誌級別:error > warning > info > debug > trace

3:Logback日誌檔案載入順序

logback在啟動的時候,會按照下面的順序載入配置文
①:如果java程式啟動時指定了logback.configurationFile屬性,就用該屬性指定的配置檔案。
    如java -Dlogback.configurationFile=/path/to/mylogback.xml Test ,
    這樣執行Test類的時候就會載入/path/to/mylogback.xml配置
②:在classpath中查詢 logback.groovy 檔案
③:在classpath中查詢 logback-test.xml 檔案
④:在classpath中查詢 logback.xml 檔案
⑤:如果是jdk6+,那麼會呼叫ServiceLoader 查詢com.qos.logback.classic.spi.Configurator介面的第一個實現類
⑥:自動使用ch.qos.logback.classic.BasicConfigurator,在控制檯輸出日誌
注:上面的順序表示優先順序,使用java -D配置的優先順序最高,只要獲取到配置後就不會再執行下面的流程。
    相關程式碼可以看ContextInitializer#autoConfig()方法。

4:Logback.xml控制檯輸出配置

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <!--定義日誌格式引數,後面可以通過${myPattern}使用-->
    <property name="myPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %c [%thread] %-5level %msg%n"/>

    <!--定義ConsoleAppender 用於在螢幕上輸出日誌-->
    <appender name="consoleAppend" class="ch.qos.logback.core.ConsoleAppender">
        <!--顯示控制檯日誌顏色 System.err【紅色】 System.out【預設白色】-->
        <target>System.err</target>
        <!--配置日誌輸出格式,並引用 myPattern 自定的日誌格式-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${myPattern}</pattern>
        </encoder>
    </appender>

    <!--配置日誌記錄器並設定日誌記錄器的列印級別-->
    <root level="ALL">
        <!--引入appender....-->
        <appender-ref ref="consoleAppend"/>
    </root>
</configuration>

//日誌列印
2022-02-15 16:10:34.466 cn.xw.LogbackDemo [main] ERROR [error  ] 最高階別程式錯誤
2022-02-15 16:10:34.470 cn.xw.LogbackDemo [main] WARN   [warning] 程式警告不會出現錯誤
2022-02-15 16:10:34.471 cn.xw.LogbackDemo [main] INFO   [info   ] 程式info資料連線,網路連線,IO操作等
2022-02-15 16:10:34.472 cn.xw.LogbackDemo [main] DEBUG [debug  ] 一般在開發中使用,記錄程式變數傳遞資訊等等
2022-02-15 16:10:34.472 cn.xw.LogbackDemo [main] TRACE [trace  ] 記錄程式所有的流程資訊

5:Logback.xml檔案和HTML格式輸出

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <!--定義日誌格式引數,後面可以通過${myPattern}使用-->
    <property name="myPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %c [%thread] %-5level %msg%n"/>
    <!--定義日誌路徑引數,後面可以通過${myPattern}使用-->
    <property name="file_dir" value="D:/logs"/>

    <!--定義FileAppender 用於在檔案上輸出日誌-->
    <appender name="fileAppend" class="ch.qos.logback.core.FileAppender">
        <!--日誌檔名稱-->
        <file>${file_dir}/fileLogback.log</file>
        <!--配置日誌輸出格式,並引用 myPattern 自定的日誌格式-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${myPattern}</pattern>
        </encoder>
    </appender>

    <!--定義FileAppender 用於在檔案上輸出日誌-->
    <appender name="fileHtmlAppend" class="ch.qos.logback.core.FileAppender">
        <file>${file_dir}/fileLogback.html</file>
        <!--因為是要輸出HTML格式,所以需要佈局包裝器一下-->
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <!--設定最終的輸出樣式-->
            <layout class="ch.qos.logback.classic.html.HTMLLayout">
                <pattern>%level%d{yyyy-MM-dd HH:mm:ss}%c%M%L%thread%m</pattern>
            </layout>
        </encoder>
    </appender>
    
    <!--配置日誌記錄器並設定日誌記錄器的列印級別-->
    <root level="ALL">
        <!--引入appender....-->
        <appender-ref ref="fileAppend"/>
        <appender-ref ref="fileHtmlAppend"/>
    </root>
</configuration>

6:Logback.xml檔案輸出之拆分歸檔

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <!--定義日誌格式引數,後面可以通過${myPattern}使用-->
    <property name="myPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %c [%thread] %-5level %msg%n"/>
    <!--定義日誌路徑引數,後面可以通過${myPattern}使用-->
    <property name="file_dir" value="D:/logs"/>

    <!--定義RollingFileAppender 用於在檔案上輸出日誌並拆分歸檔-->
    <appender name="rollFileAppend" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--定義日誌輸出的路徑-->
        <!--這裡的scheduler.manager.server.home 沒有在上面的配置中設定,所以會使用java啟動時配置的值-->
        <!--比如通過 java -Dscheduler.manager.server.home=/path/to XXXX 配置該屬性-->
        <file>${scheduler.manager.server.home}/fileRollLogback.log</file>
        <!--配置日誌輸出格式,並引用 myPattern 自定的日誌格式-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${myPattern}</pattern>
        </encoder>
        <!--基於大小和時間滾動策略-->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--按照時間和壓縮格式宣告檔名 壓縮檔案為.gz-->
            <fileNamePattern>${scheduler.manager.server.home}/roll.%d{yyyy-MM-dd}.log%i.gz</fileNamePattern>
            <!--按照檔案大小拆分 每個檔案達到500MB會自動壓縮歸檔-->
            <maxFileSize>500MB</maxFileSize>
        </rollingPolicy>
    </appender>

    <!--配置日誌記錄器並設定日誌記錄器的列印級別-->
    <root level="ALL">
        <!--引入appender....-->
        <appender-ref ref="rollFileAppend"/>
    </root>
</configuration>

7:Logback過濾器及非同步列印

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <!--定義日誌格式引數,後面可以通過${myPattern}使用-->
    <property name="myPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %c [%thread] %-5level %msg%n"/>

    <!--定義ConsoleAppender 用於在螢幕上輸出日誌-->
    <appender name="consoleAppend" class="ch.qos.logback.core.ConsoleAppender">
        <!--顯示控制檯日誌顏色 System.err【紅色】 System.out【預設白色】-->
        <target>System.err</target>
        <!--配置日誌輸出格式,並引用 myPattern 自定的日誌格式-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${myPattern}</pattern>
        </encoder>

        <!--LevelFilter: 級別過濾器,根據日誌級別進行過濾。如果日誌級別等於配置級別,
        過濾器會根據onMath 和 onMismatch接收或拒絕日誌。-->
        <!--例如:將過濾器的日誌級別配置為INFO,所有INFO級別的日誌交給appender處理,非INFO級別的日誌,被過濾掉。-->
<!--        <filter class="ch.qos.logback.classic.filter.LevelFilter">-->
<!--        <level>INFO</level>-->
<!--        <onMatch>ACCEPT</onMatch>-->
<!--        <onMismatch>DENY</onMismatch>-->
<!--        </filter>-->
        <!--ThresholdFilter: 臨界值過濾器,過濾掉低於指定臨界值的日誌。當日志級別等於或高於臨界值時,
        過濾器返回NEUTRAL;當日志級別低於臨界值時,日誌會被拒絕。-->
        <!-- 過濾掉所有低於 DEBUG 級別的日誌,留下DEBUG及以上級別的日誌 -->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>DEBUG</level>
        </filter>
    </appender>
    
    <!--配置非同步日誌-->
    <appender name="asyncAppend" class="ch.qos.logback.classic.AsyncAppender">
        <!--要使用到非同步的Appender-->
        <appender-ref ref="consoleAppend"/>
    </appender>
    
    <!--配置日誌記錄器並設定日誌記錄器的列印級別-->
    <root level="ALL">
        <!--引入appender....-->
        <appender-ref ref="asyncAppend"/>
    </root>
</configuration>

8:自定義Logger

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <!--定義日誌格式引數,後面可以通過${myPattern}使用-->
    <property name="myPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %c [%thread] %-5level %msg%n"/>
    <!--定義ConsoleAppender 用於在螢幕上輸出日誌-->
    <appender name="consoleAppend" class="ch.qos.logback.core.ConsoleAppender">
        <!--顯示控制檯日誌顏色 System.err【紅色】 System.out【預設白色】-->
        <target>System.err</target>
        <!--配置日誌輸出格式,並引用 myPattern 自定的日誌格式-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${myPattern}</pattern>
        </encoder>
    </appender>

    <!--additvity="false" 不繼承父元素-->
    <logger name="cn.xw" level="INFO" additvity="false">
        <!--自定義logger中配置appender-->
        <appender-ref ref="consoleAppend"/>
    </logger>
</configuration>

七:Log4j2日誌框架

   具體參考Log4j2

相關文章