Java的標準日誌

Howlet 發表於 2021-07-24
Java

雖然開源社群有很多優秀的日誌框架,但我們學習標準的java日誌框架是為了更好的理解其他框架啊(近期專案要用ELK)

看自己以前寫的Log4J簡直不忍直視啊啊啊啊,那時還感覺自我良好



1. 為什麼要使用日誌

我們都試過在程式碼中插入System.out.println方法來進行除錯吧,當找出問題根源後就把插入的print語句刪除,若又出現問題則需再次插入這些語句,如此反覆。那麼日誌API就是為了解決這個問題而設計的,使用日誌的優勢:

  • 可隨時開閉日誌記錄,還能分級別篩選日誌,並且保留日誌程式碼開銷很小
  • 日誌可簡單地被定向到控制檯顯示,檔案儲存,或者網路傳輸
  • 日誌可格式化其記錄的格式
  • 日誌可由配置檔案控制
  • 日誌利於日後錯誤的定位






2. Logger

java有標準的日誌系統,在java.util.logging包下。因為它不太好用,就出現了各種補充的日誌框架,其實我看著也還行,能夠應付我的日常使用了


2.1 示例

看不懂沒關係,碼入下面的程式就可以看到日誌記錄的情況了

public class loggerTest {
    public static void main(String[] args) {
        
        // 1. 獲得一個全域性的日誌記錄器
        Logger global = Logger.getGlobal();
        
        // 2. 日誌有七個級別,從高到低分別是:Sever、Warning、Info、Config、Fine、Finer、Finest
        // 	  預設級別為INFO,意思只輸出前三個級別的記錄
        global.info("INFO MSG");
        global.warning("WARNING MSG");
        global.severe("SERVE MSG");
        
         // 3. 可通過setLevel來設定日誌級別,來限制其他級別的記錄
        global.setLevel(Level.WARNING);
    }
}

// 控制檯輸出
// 七月 23, 2021 8:57:17 下午 logging.loggerTest main
// 資訊: INFO MSG
// 七月 23, 2021 8:57:17 下午 logging.loggerTest main
// 警告: WARNING MSG
// 七月 23, 2021 8:57:17 下午 logging.loggerTest main
// 嚴重: SERVE MSG


2.2 日誌的記錄器

記錄器是用來 "記錄"、定位日誌記錄的,一般我們不想把所有的日誌都記錄到一個全域性記錄器上,那麼我們就可以自定義一個記錄器


public class loggerTest {

    // 未被任何變數引用的日誌記錄器可能被垃圾回收掉,所以採用了靜態變數的方式
    private static  final Logger myLogger = Logger.getLogger("com.howl.logger.loggerTest");;

    public static void main(String[] args) {
         myLogger.info("this is my logger msg");
    }
}

日誌的記錄器有類似於包名繼承的層次結構,父記錄器設定了日誌級別,那麼子記錄器就會繼承這個級別,所以日誌框架的記錄器命名都以類名限定



2.3 日誌配置

java有個叫日誌管理器的東西專門來管配置的,java9的配置檔案是在 jre/conf/logging.properties。日誌管理器在虛擬機器啟動時就初始化,就是在main方法執行之前


我們可以在啟動專案時就指定日誌的配置檔案:java -Djava.util.logging.config.file=新檔名

也可在專案執行時用System.setProperty("java.util.logging.config.file", file)指定配置檔案,並LogManager.getLogManager().readConfiguration()重新初始化日誌管理器生效配置(食用配置檔案形式不好,其他日誌框架的配置在專案根目錄,會自動讀取的)



2.4 日誌的處理器

處理器是用於處理記錄的(也有日誌級別),記錄器有ConsoleHandler、FileHandler、SocketHandler。預設情況下記錄器將記錄發到ConsoleHandler然後輸出,如想輸出到其他地方就新增其他的處理器。具體流程的話,就是記錄器將記錄發給自己的處理器和父記錄器的處理器,全部記錄器的最終祖先是名為 "" 的一個記錄器,它有一個ConsoleHandler,所以預設的日誌記錄都輸出到控制檯


public class loggerTest {

    // 靜態變數放垃圾回收
    private static  final Logger myLogger = Logger.getLogger("com.howl.logger.loggerTest");;

    public static void main(String[] args) throws IOException {

        // 檔案、控制檯處理器
        FileHandler fileHandler = new FileHandler();
        ConsoleHandler consoleHandler = new ConsoleHandler();
        
        myLogger.addHandler(consoleHandler);	// 這條語句在控制檯輸出了兩次
        myLogger.addHandler(fileHandler);

        myLogger.info("add two handler");
    }
}

// 控制檯輸出
// 七月 23, 2021 9:31:26 下午 logging.loggerTest main
// 資訊: add two handler
// 七月 23, 2021 9:31:26 下午 logging.loggerTest main
// 資訊: add two handler

怎麼會有兩條記錄?

  • fileHander是輸出檔案的(不在控制檯輸出),日誌檔案預設儲存在使用者目錄下的javaN.log中,其中N是唯一編號,預設格式為XML

  • 上面說的myLogger發給自己處理器consoleHandler輸出,也會發給父處理器輸出,所以有兩條,可配置userParentHandlers = false,取消使用父處理器



2.5 日誌的過濾器

記錄器,處理器只能根據日誌級別來過濾,而過濾器則更加自由多樣化。我們需要實現Filter介面(注意是Logger下的介面)然後將其交給記錄器(是記錄器啊,下面標題2.6的才是交給處理器)


public class loggerTest {

    private static  final Logger myLogger = Logger.getLogger("com.howl.logger.loggerTest");;

    public static void main(String[] args) throws IOException {

        // 過濾器
        Filter filter = new Filter() {
            @Override
            public boolean isLoggable(LogRecord record) {
                return record.getMessage().contains("HAHA");    // 記錄包含了HAHA就不過濾
            }
        };

        myLogger.setFilter(filter);
        myLogger.info("add two handler");
        myLogger.info("i am HAHA");
    }
}

// 控制檯輸出
// 七月 23, 2021 9:43:27 下午 logging.loggerTest main
// 資訊: i am HAHA


2.6 日誌的格式化器

格式化器顧名思義是用來格式化記錄的,看需要生成什麼樣格式的記錄,我的話就在日誌前加點東西就好了。也是需要實現format介面的,當然記錄的格式化操作是交給處理器的


public class loggerTest {

    private static  final Logger myLogger = Logger.getLogger("com.howl.logger.loggerTest");;

    public static void main(String[] args) throws IOException {

        // 格式化器
        Formatter formatter = new Formatter() {
            @Override
            public String format(LogRecord record) {
                return "這裡是格式化器: "+ record.getMessage() + "\n\n";
            }
        };

        ConsoleHandler consoleHandler = new ConsoleHandler();
        consoleHandler.setFormatter(formatter);

        myLogger.addHandler(consoleHandler);
        myLogger.info("i am HAHA");
    }
}

// 控制檯輸出
// 這裡是格式化器: i am HAHA
// 
// 七月 23, 2021 9:52:58 下午 logging.loggerTest main
// 資訊: i am HAHA






3. java日誌的發展史

  • Apache 的 log4j 日誌框架最早出現(可用配置檔案管理日誌,並動態載入)
  • java1.4 後面才新增的標準日誌庫 java.util.logging(JUL)
  • Apache 推出日誌門面Apache Commons Logging(JCL,提供了一套日誌介面,相容上面二者)
  • 再然後 JCL 的作者弄了個新的日誌門面 slf4j,並提供了其元件實現 logback
  • 最後 Apache 重寫log4j,推出 log4j2
  • 因為 slf4j 門面後面才出現,所以推出了各種補丁使其相容 JCL 的介面,看著好複雜

日誌門面 元件實現
JCL、slf4j log4j、log4j、logback、JUL

使用框架需選一個日誌門面,然後再選擇個門面的實現,不選擇實現的話預設使用 java 的標準庫







4. 專案中為什麼不使用JUL

筆者還沒在專案中實際用過日誌框架,體會到的不多,目前只知道 JUL 的配置管理器實屬敗筆~ 。等筆者搭完這次專案用到的ELK之後再慢慢體會把