1. 程式人生 > 實用技巧 >Log4j日誌框架

Log4j日誌框架

簡介

  • 本篇是關於Log4j日誌框架的介紹。
  • 閱讀本文請至少了解JUL日誌框架,因為大部分的知識點是類似的。

Log4j日誌框架

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

入門案例

  • 所使用的專案使用Maven構建,需要引入Log4j依賴:
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
  • Log4j預設情況下是需要提供配置檔案的,如果resource目錄下不存在log4j.properties配置檔案,則控制檯中會輸出相應的警告資訊。
  • 考慮以下程式碼:
@Test
public void test() {
    // 1.在沒有log4j.properties的情況下,獲取日誌記錄器物件Logger
    Logger logger = Logger.getLogger(Log4jTest.class);

    // 2.嘗試輸出日誌記錄
    logger.info("Hey, log4j.");
}
  • 此時控制檯輸出為:

  • 接觸過JUL的都知道,當普通的Logger
    沒有進行額外的配置時,其預設會繼承並使用RootLogger的配置。
  • 同樣地,Log4j中也存在RootLogger,但由於預設情況下RootLogger不具有任何的Appender(即Handler)。
  • 如果程式碼僅為了測試某項功能,並不想編寫複雜的log4j.properties,可以使用Log4j提供的預設配置,在獲取Logger前使用以下程式碼載入預設配置:BasicConfigurator.configure();
  • 檢視configure()方法的原始碼:
/**
   Add a {@link ConsoleAppender} that uses {@link PatternLayout}
   using the {@link PatternLayout#TTCC_CONVERSION_PATTERN} and
   prints to <code>System.out</code> to the root category.  */
static
public
void configure() {
  Logger root = Logger.getRootLogger();
  root.addAppender(new ConsoleAppender(
         new PatternLayout(PatternLayout.TTCC_CONVERSION_PATTERN)));
}
  • 古早時期的原始碼格式有點不太現代,但意義明確:為RootLogger物件新增一個Appender,其中Appender的型別為控制器輸出的ConsoleAppender,輸出的格式使用PatternLayout.TTCC_CONVERSION_PATTERN
  • 以下為PatternLayout.TTCC_CONVERSION_PATTERN原始碼:
/** A conversion pattern equivalent to the TTCCCLayout.
    Current value is <b>%r [%t] %p %c %x - %m%n</b>. */
public final static String TTCC_CONVERSION_PATTERN
                                           = "%r [%t] %p %c %x - %m%n";
  • 關於PatternLayout的相關格式化規則,後續會列舉出來。
  • 於是,使用以下程式碼載入預設配置並輸出日誌:
@Test
public void testQuick() {
    // 1.初始化配置資訊,使用預設的配置,如果不載入預設配置同時不具有log4j.properties配置檔案,程式將發出警告
    BasicConfigurator.configure();

    // 2.獲取日誌記錄器物件Logger
    Logger logger = Logger.getLogger(Log4jTest.class);

    // 3.日誌記錄輸出
    logger.info("Hey, log4j.");
}
  • 此時控制檯輸出為:

日誌級別

  • Log4j中的日誌級別與JUL的不同,一共提供了6中日誌級別:
    1. FATAL:嚴重錯誤,一般會造成系統崩潰並終止執行;
    2. ERROR:錯誤資訊,不會影響系統執行;
    3. WARN:警告資訊,可能會發生問題;
    4. INFO:執行資訊,資料連線、網路連線、I/O操作等等;
    5. DEBUG:除錯資訊,一般在開發中使用,記錄程式變數引數傳遞資訊等等。預設級別;
    6. TRACE:追蹤資訊,記錄程式所有的流程資訊。
  • 參考以下示例程式碼:
@Test
public void testQuick() {
    // 1.初始化配置資訊,使用預設的配置,如果不載入預設配置同時不具有log4j.properties配置檔案,程式將發出警告
    BasicConfigurator.configure();

    // 2.獲取日誌記錄器物件Logger
    Logger logger = Logger.getLogger(Log4jTest.class);

    // 3.日誌級別測試
    logger.fatal("[FATAL] 嚴重錯誤,一般會造成系統崩潰並終止執行。");
    logger.error("[ERROR] 錯誤資訊,不會影響系統執行。");
    logger.warn("[WARN] 警告資訊,可能會發生問題。");
    logger.info("[INFO] 執行資訊,資料連線、網路連線、I/O操作等等。");
    logger.debug("[DEBUG] 除錯資訊,一般在開發中使用,記錄程式變數引數傳遞資訊等等。預設級別。");
    logger.trace("[TRACE] 追蹤資訊,記錄程式所有的流程資訊。");
}
  • 執行輸出,得到:

  • 輸出的日誌仍然由預設的日誌級別所決定,其中預設級別為DEBUG
  • 為了測試預設日誌級別,可以使用以下程式碼測試RootLogger
    1. 使用getRootLogger()獲取RootLogger物件;
    2. 使用RootLogger中的相關方法獲取日誌物件的日誌等級及其關聯的Appender詳情。
@Test
public void testDetails() {
    // 1.初始化配置資訊,使用預設的配置,如果不載入預設配置,將無法正常執行
    BasicConfigurator.configure();

    // 2.獲取日誌記錄器物件RootLogger
    final Logger rootLogger = Logger.getRootLogger();

    // 3.輸出配置詳情
    System.out.println("Logger level: " + rootLogger.getLevel());
    final Enumeration allAppenders = rootLogger.getAllAppenders();
    while (allAppenders != null && allAppenders.hasMoreElements()) {
        final Appender appender = (Appender) allAppenders.nextElement();
        System.out.println("Appender is: " +appender.getClass().getSimpleName());
    }
}
  • 執行輸出:

  • 注意,對於Log4j中的Appender來說,它們不具有日誌等級,只有Logger物件具有日誌等級。

Log4j相關元件

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

a. Logger

  • 日誌記錄器,負責收集處理日誌記錄,Logger的例項命名通常是類的全限定類名。
  • Logger的名字大小寫敏感,其命名有繼承機制。
    • 例如:nameorg.apache.commonslogger會繼承nameorg.apachelogger
  • log4j 1.2版以來, Logger類已經取代了Category類。對於熟悉早期版本的log4j的人來說,Logger類可以被視為Category類的別名。

b. Appenders

  • Appender用來指定日誌輸出到哪個地方,可以同時指定日誌的輸出目的地。
  • Log4j常用的輸出目的地有以下幾種:
輸出端型別 作用
ConsoleAppender 將日誌輸出到控制檯
FileAppender 將日誌輸出到檔案中
DailyRollingFileAppender 將日誌輸出到一個日誌檔案,週期為天,即每天輸出
RollingFileAppender 將日誌資訊輸出到一個日誌檔案,並且指定檔案的大小,當超過指定大小,會自動將檔案重新命名,同時產生一個新的檔案
JDBCAppender 將日誌資訊儲存到資料庫中

c. 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字元,就從左邊交遠銷出的字元截掉

自定義配置

  • 使用Log4j不需要顯式地載入配置檔案,對於Maven專案來說,程式會自動掃描resources目錄下的log4j.properties配置檔案。

  • 自定義配置如下:

    1. 日誌輸出等級為INFO,此時RootLogger擁有三個Appender
      1. ConsoleAppender:配置中的命名為Console
      2. DailyRollingFileAppender:配置中的命名為UserDefinedName
      3. JDBCAppender:配置中的命名為logDB
    2. RootLogger的預設輸出級別配置在log4j.rootLogger的首位,隨後緊跟的是關聯的Appender的名稱,表示其預設支援輸出的方式有哪些。
# 指定日誌的輸出級別與輸出端
log4j.rootLogger=INFO, Console, UserDefinedName, logDB

# 控制檯輸出配置
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

# 檔案輸出配置
log4j.appender.UserDefinedName=org.apache.log4j.DailyRollingFileAppender
# 指定日誌的輸出路徑
log4j.appender.UserDefinedName.File=log4j.log
# 是否以追加日誌的形式新增
log4j.appender.UserDefinedName.Append=true
# 使用自定義日誌格式化器
log4j.appender.UserDefinedName.layout=org.apache.log4j.PatternLayout
# 指定日誌的輸出格式
log4j.appender.UserDefinedName.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [%t:%r] - [%p] %m%n
# 指定日誌的檔案編碼
log4j.appender.UserDefinedName.encoding=UTF-8

# MySQL輸出配置
log4j.appender.logDB=org.apache.log4j.jdbc.JDBCAppender
log4j.appender.logDB.layout=org.apache.log4j.PatternLayout
log4j.appender.logDB.Driver=com.mysql.cj.jdbc.Driver
log4j.appender.logDB.URL=jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8&useAffectedRows=true
log4j.appender.logDB.User=root
log4j.appender.logDB.Password=root
log4j.appender.logDB.Sql=INSERT INTO log(project_name, create_date, level, category, file_name, thread_name, line, all_category, message) \
  values('log4j', '%d{yyyy-MM-dd HH:mm:ss}', '%p', '%c', '%F', '%t', '%L', '%l', '%m')
  • 配置中包含了sql輸出日誌的方式,log表的建立程式碼為:
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` ) 
);
  • 讓特定名稱的logger使用特定的配置:
    1. 一個log4j.properties中可以配置多個且可重複的Appender,但Appender的命名不可重複;
    2. 通過log4j.logger.{loggerName}的方式,讓指定名為loggerNamelogger使用該配置;
    3. 由於該logger仍然是隸屬於rootLogger,因此輸出是累加的形式:
      • 例:如果RootLogger使用了ConsoleAppender,同時Logger也使用了ConsoleAppender,此時控制檯將輸出兩次日誌記錄,一次為Logger繼承自RootLogger的輸出,另一次則為Logger自身的輸出。
    4. 但日誌等級level則取決於子日誌LoggerRootLogger,以等級高的一方為準。
      • 例:此時RootLoggerLogger同時使用了ConsoleAppender,但輸出等級分別為INFOWARN,此時控制檯輸出的日誌等級僅有高於等於WARN的記錄,即使此時RootLogger的等級為INFO
  • 有如下log4j.properties配置檔案:
    • 其中RootLogger和名為cn.hannaLogger使用同一個ConsoleAppender,但日誌等級不一致。
# 指定日誌的輸出級別與輸出端
log4j.rootLogger=INFO, 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

# 讓名為“cn.hanna”的logger使用名為Another的Appender
# 此時仍會使用rootLogger中的控制檯輸出,而Level則以兩者間較高的為準
log4j.logger.cn.hanna=WARN, Console
  • 測試程式碼如下:
@Test
public void testAnother() {
    // 1.獲取日誌記錄器物件Logger
    Logger logger = Logger.getLogger("cn.hanna");

    // 2.日誌級別測試
    logger.fatal("[FATAL] 嚴重錯誤,一般會造成系統崩潰並終止執行。");
    logger.error("[ERROR] 錯誤資訊,不會影響系統執行。");
    logger.warn("[WARN] 警告資訊,可能會發生問題。");
    logger.info("[INFO] 執行資訊,資料連線、網路連線、I/O操作等等。");
    logger.debug("[DEBUG] 除錯資訊,一般在開發中使用,記錄程式變數引數傳遞資訊等等。預設級別。");
    logger.trace("[TRACE] 追蹤資訊,記錄程式所有的流程資訊。");
}
  • 執行輸出:

  • 輸出印證了之前所陳述的觀點,不再贅述。
  • 此時,如果一定需要使用ConsoleAppender,但不希望控制檯輸出兩次記錄,有兩種方式:
    1. 摒棄RootLogger的輸出,即斷開指定LoggerRootLogger的繼承關係;
    2. 摒棄Logger的輸出,即指定名稱的Logger直接使用RootLogger關聯的Appender,不再額外指定。
  • 如果選擇斷開指定LoggerRootLogger的繼承關係,需修改配置檔案如下:
# 指定日誌的輸出級別與輸出端
log4j.rootLogger=INFO, 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

# 名為“cn.hanna”的Logger不再繼承使用RootLogger中的Appender
log4j.additivity.cn.hanna=false
log4j.logger.cn.hanna=WARN, Console
  • 如果選擇直接使用RootLogger關聯的Appender,需修改配置檔案如下:
# 指定日誌的輸出級別與輸出端
log4j.rootLogger=INFO, 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

# 對於名稱“cn.hanna”的Logger來說,將直接使用RootLogger中關聯的Appender,但日誌等級由自身決定
log4j.logger.cn.hanna=WARN
  • 兩種方式的測試類執行結果均為:

  • 實際應用中,應該靈活選擇適合與程式碼的方式,去編寫配置檔案。

完整的配置檔案示例

  • 同樣,在此給出一個完整的log4j配置檔案示例:
log4j.rootLogger=INFO, 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

log4j.appender.UserDefinedName=org.apache.log4j.DailyRollingFileAppender
log4j.appender.UserDefinedName.File=log4j.log
log4j.appender.UserDefinedName.Append=true
log4j.appender.UserDefinedName.layout=org.apache.log4j.PatternLayout
log4j.appender.UserDefinedName.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [%t:%r] - [%p] %m%n
log4j.appender.UserDefinedName.encoding=UTF-8

log4j.appender.logDB=org.apache.log4j.jdbc.JDBCAppender
log4j.appender.logDB.layout=org.apache.log4j.PatternLayout
log4j.appender.logDB.Driver=com.mysql.cj.jdbc.Driver
log4j.appender.logDB.URL=jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8&useAffectedRows=true
log4j.appender.logDB.User=root
log4j.appender.logDB.Password=root
log4j.appender.logDB.Sql=INSERT INTO log(project_name, create_date, level, category, file_name, thread_name, line, all_category, message) \
  values('log4j', '%d{yyyy-MM-dd HH:mm:ss}', '%p', '%c', '%F', '%t', '%L', '%l', '%m')

log4j.appender.Another=org.apache.log4j.DailyRollingFileAppender
log4j.appender.Another.File=hanna.log
log4j.appender.Another.Append=true
log4j.appender.Another.layout=org.apache.log4j.PatternLayout
log4j.appender.Another.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [%t:%r] - [%p] %m%n
log4j.appender.Another.encoding=UTF-8

log4j.logger.cn.xyz=WARN, Another
log4j.additivity.cn.xyz=false
  • 說明:其中log4j.appeder.APPENDER_NAME中的APPENDER_NAME為相應Appender的名稱,可以自行定義。

總結

  • Log4j日誌框架近似於JUL日誌框架,但功能性上要優於JUL