天天看點

Spring Boot 應用系列 4 -- Spring Boot 2 整合log4j2

一、背景

1. log4j2傳承于log4j和logback,它是目前性能最好的日志處理工具,有關它們的性能對比請看:

Spring Boot 應用系列 4 -- Spring Boot 2 整合log4j2

2. 除了性能好之外,log4j2有這麼幾個重要的新features:

(1) 自動熱重載配置檔案,而且重新加載期間不會丢失日志請求。logback也可以熱重載配置檔案,但是它在重新加載期間會丢失請求;

(2) 用插件代替code style的自定義appender;

(3) 支援異步日志,至于異步日志的性能,請參考官方評測:

Spring Boot 應用系列 4 -- Spring Boot 2 整合log4j2

由此可見,log4j2的性能優勢就展現在異步日志上,如果使用log4j2而不用其異步日志,那麼它的性能跟logback相差不大。

3. 日志級别

Spring Boot 應用系列 4 -- Spring Boot 2 整合log4j2

我們使用log4j2或者logback時一般會用通用接口slf4j來進行橋接,但是slf4j僅支援trace->error區間的事件。

二、配置

1. pom.xml

由于Spring Boot預設的日志實作是logback、log4j和slf4j,是以需要在引入log4j2的同時排除掉預設的日志實作。

Spring Boot 應用系列 4 -- Spring Boot 2 整合log4j2
pom.xml的dependencies節點是這樣的:

<dependencies>
        <!-- Spring Boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- Spring Boot end -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>
    </dependencies>      

2. 本文我們希望通過配置達到以下效果:

(1) 所有級别的日志均可以通過控制台列印;

(2) 日志的存儲目錄格式為“/yyyy-MM/dd/”(“年-月/日/”),日志檔案名稱包含小時;

(2) error級别的日志存儲在“/yyyy-MM/dd/app-error-{HH}.log”中,其中HH是日志發生的小時;

(3) warn級别的日志存儲在“/yyyy-MM/dd/app-warn-{HH}.log”中;

(4) 其他級别的日志存儲在“/yyyy-MM/dd/app-other-{HH}.log”中;

(5) 所有日志檔案按照小時歸檔,一個小時一套檔案(三個具體檔案error, warn, other);

(6) 設定日志檔案的size上限,如果某一小時出現的日志特别多,超過size limit之後自動生成帶數字字尾的檔案。

3. log4j2.xml

log4j2的配置均在log4j2.xml中:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
    <Properties>
        <Property name="baseDir">logs</Property>
        <Property name="message-pattern">[%d{HH:mm:ss:SSS}] [%t] %-5level %logger{36} - %msg%n</Property>
    </Properties>
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout>
                <Pattern>${message-pattern}</Pattern>
            </PatternLayout>
        </Console>
        <RollingRandomAccessFile name="RollingRandomAccessFile_Other" fileName="${baseDir}/app-other.log" filePattern="${baseDir}/$${date:yyyy-MM}/$${date:dd}/app-other-%d{HH-mm}-%i.log" immediateFlush="false">
            <PatternLayout>
                <Pattern>${message-pattern}</Pattern>
            </PatternLayout>
            <Filters>
                <ThresholdFilter level="FATAL" onMatch="ACCEPT" onMismatch="NEUTRAL" />
                <ThresholdFilter level="ERROR" onMatch="DENY" onMismatch="NEUTRAL" />
                <ThresholdFilter level="WARN" onMatch="DENY" onMismatch="NEUTRAL" />
                <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="NEUTRAL" />
                <ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="NEUTRAL" />
                <ThresholdFilter level="TRACE" onMatch="ACCEPT" onMismatch="NEUTRAL" />
                <ThresholdFilter level="ALL" onMatch="ACCEPT" onMismatch="NEUTRAL" />
            </Filters>
            <Policies>
                <TimeBasedTriggeringPolicy />
                <SizeBasedTriggeringPolicy size="10 MB" />
            </Policies>
        </RollingRandomAccessFile>
        <RollingRandomAccessFile name="RollingRandomAccessFile_Warn" fileName="${baseDir}/app-warn.log" filePattern="${baseDir}/$${date:yyyy-MM}/$${date:dd}/app-warn-%d{HH-mm}-%i.log" immediateFlush="false">
            <PatternLayout>
                <Pattern>${message-pattern}</Pattern>
            </PatternLayout>
            <Filters>
                <ThresholdFilter level="ERROR" onMatch="DENY" onMismatch="NEUTRAL" />
                <ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY" />
            </Filters>
            <Policies>
                <TimeBasedTriggeringPolicy />
                <SizeBasedTriggeringPolicy size="10 MB" />
            </Policies>
        </RollingRandomAccessFile>
        <RollingRandomAccessFile name="RollingRandomAccessFile_Error" fileName="${baseDir}/app-error.log" filePattern="${baseDir}/$${date:yyyy-MM}/$${date:dd}/app-error-%d{HH}-%i.log" immediateFlush="false">
            <PatternLayout>
                <Pattern>${message-pattern}</Pattern>
            </PatternLayout>
            <Filters>
                <ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY" />
            </Filters>
            <Policies>
                <TimeBasedTriggeringPolicy />
                <SizeBasedTriggeringPolicy size="10 MB" />
            </Policies>
        </RollingRandomAccessFile>
        <Async name="Async">
            <AppenderRef ref="RollingRandomAccessFile_Warn" />
            <AppenderRef ref="RollingRandomAccessFile_Error" />
            <AppenderRef ref="RollingRandomAccessFile_Other" />
        </Async>
    </Appenders>
    <Loggers>
        <Root level="INFO">
            <AppenderRef ref="Console" />
            <AppenderRef ref="Async" />
        </Root>
        <Logger name="devutility.test.log.log4j2.controller.TestController" level="TRACE" additivity="false">
            <AppenderRef ref="Console" />
        </Logger>
    </Loggers>
</Configuration>      

(1) Properties節點的功能類似于pom.xml裡的properties節點,相當于一個xml級别的全局變量,可用來統一配置,節省代碼;

(2) baseDir這個Property是日志檔案的根目錄,如果以“/”開頭,運作時會在磁盤根目錄下建立指定的日志目錄;如果開頭沒有“/”,則會在項目根目錄下建立日志目錄;将Spring Boot打成jar包後,如果baseDir以“/”開頭,運作時會在磁盤根目錄下建立指定的日志目錄;如果開頭沒有“/”,則會在jar包相同的目錄下建立日志目錄;

(3) RollingRandomAccessFile是log4j2提供的一種appender,它用使用java.io.RandomAccessFile類來記錄檔檔案,詳見log4j2官網。

(4) RollingRandomAccessFile的filePattern屬性可以了解為“歸檔”,是日志的最終歸屬地,必填字段;

(5) RollingRandomAccessFile的fileName屬性可選,如果fileName為空或沒有該屬性則日志直接寫到filePattern指定的檔案中,如果fileName不為空,則fileName指定的日志檔案相當于工作台,日志首先被寫到fileName中,等觸發條件滿足再寫到filePattern中歸檔;

(6) Filters中的ThresholdFilter需要特别注意順序,如果第一個ThresholdFilter中的onMatch="ACCEPT",則不管後面的ThresholdFilter怎麼配置該ThresholdFilter所配級别之上的級别全部ACCEPT;如果36和37兩行對調,則WARN和WARN級别之上的日志全部寫到warn日志中,這顯然不是我們想要的;

(7) TimeBasedTriggeringPolicy指明寫新日志檔案的觸發機制是根據時間,它取filePattern中配置的最後一個時間機關,本文中RollingRandomAccessFile_Other和RollingRandomAccessFile_Warn最後的時間機關是mm,也就是每分鐘寫一個日志檔案,RollingRandomAccessFile_Error最後的時間機關是HH,也就是每小時寫一個日志檔案。

(8) immediateFlush這個屬性代表是否立即将日志寫入檔案,預設值是true,常跟異步日志配合使用。但是在實際使用中發現,對于error日志來講,即便你設定為true,log4j2也會立即将日志立即寫入檔案,因為error級别的日志等級較高,需要實時檢視;

(9) <Loggers>節點下可以有<loger>節點,用來設定某一個包或者具體的某一個類的日志列印級别,它自動繼承root節點的level。

<loger>節點有三個屬性,一個子節點:

a.

name

屬性,必填,指定受此loger限制的包或者類;

b. level屬性,選填,指定loger的日志級别,如果不指定則繼承父級的level;

c. 

addtivity

屬性,預設為true,指定Logger 是否繼承父Logger輸出源(appender)。

d. 如果<logger>節點包含appender,則該logger使用其所包含的appender列印日志;如果不包含任何appender,且addtivity=true,則該logger使用父級appender輸出,否則該logger沒有任何輸出。

本文中TestController下的trace級别以上的日志均通過控制台輸出,其他類中INFO級别以上的日志通過控制台+檔案的方式輸出。

(10) Async節點中包含了所有需要異步寫日志的appender,此外還可以使用asyncRoot來代替Root節點,讓所有日志均已異步方式實作。

三、應用

package devutility.test.log.log4j2.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/test")
public class TestController {
    Logger logger = LoggerFactory.getLogger(TestController.class);

    @RequestMapping("/all")
    public String all(String message) {
        logger.trace(message);
        logger.debug(message);
        logger.info(message);
        logger.warn(message);
        logger.error(message);
        return message;
    }
}      

LoggerFactory是slf4j提供的一個工廠類,slf4j定義了通路日志的一種規範,包括trace->error等五種日志級别,使用它你就可以無需關心日志的底層實作,無論你是使用logback還是log4j或者是log4j2,它都可以支援。

Demo代碼