1. 程式人生 > 實用技巧 >【日誌】新版日誌技術

【日誌】新版日誌技術

0. 學習目標

  1. 日誌門面和日誌體系
  2. SLF4J
  3. logback的使用
  4. log4j2的使用
  5. SpringBoot中的日誌使用

1. 日誌門面

當我們的系統變的更加複雜的時候,我們的日誌就容易發生混亂。隨著系統開發的進行,可能會更新不同的日誌框架,造成當前系統中存在不同的日誌依賴,讓我們難以統一的管理和控制。就算我們強制要求所有的模組使用相同的日誌框架,系統中也難以避免使用其他類似spring,mybatis等其他的第三方框架,它們依賴於我們規定不同的日誌框架,而且他們自身的日誌系統就有著不一致性,依然會出來日誌體系的混亂。

所以我們需要借鑑JDBC的思想,為日誌系統也提供一套門面,那麼我們就可以面向這些介面規範來開發,避免了直接依賴具體的日誌框架。這樣我們的系統在日誌中,就存在了日誌的門面和日誌的實現。

常見的日誌門面:
JCL、slf4j
常見的日誌實現:
JUL、log4j、logback、log4j2

日誌門面和日誌實現的關係:

日誌框架出現的歷史順序:
log4j -->JUL-->JCL--> slf4j --> logback --> log4j2

2. SLF4J的使用

簡單日誌門面(Simple Logging Facade For Java) SLF4J主要是為了給Java日誌訪問提供一套標準、規範的API框架,其主要意義在於提供介面,具體的實現可以交由其他日誌框架,例如log4j和logback等。當然slf4j自己也提供了功能較為簡單的實現,但是一般很少用到。對於一般的Java專案而言,日誌框架會選擇slf4j-api作為門面,配上具體的實現框架(log4j、logback等),中間使用橋接器完成橋接。

官方網站: https://www.slf4j.org/

SLF4J是目前市面上最流行的日誌門面。現在的專案中,基本上都是使用SLF4J作為我們的日誌系統。
SLF4J日誌門面主要提供兩大功能:

  1. 日誌框架的繫結
  2. 日誌框架的橋接

2.1 SLF4J入門

  1. 新增依賴

    <!--slf4j core 使用slf4j必須新增-->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.27</version>
    </dependency>
    <!--slf4j 自帶的簡單日誌實現 -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>
        <version>1.7.27</version>
    </dependency>
  2. 編寫程式碼

    public class Slf4jTest {
        // 宣告日誌物件
        public final static Logger LOGGER =
        LoggerFactory.getLogger(Slf4jTest.class);
        @Test
        public void testQuick() throws Exception {
            //列印日誌資訊
            LOGGER.error("error");
            LOGGER.warn("warn");
            LOGGER.info("info");
            LOGGER.debug("debug");
            LOGGER.trace("trace");
            // 使用佔位符輸出日誌資訊
            String name = "jack";
            Integer age = 18;
            LOGGER.info("使用者:{},{}", name, age);
            // 將系統異常資訊寫入日誌
            try {
                int i = 1 / 0;
            } catch (Exception e) {
                // e.printStackTrace();
                LOGGER.info("出現異常:", e);
            }
        }
    }

為什麼要使用SLF4J作為日誌門面?

  1. 使用SLF4J框架,可以在部署時遷移到所需的日誌記錄框架。
  2. SLF4J提供了對所有流行的日誌框架的繫結,例如log4j,JUL,Simple logging和NOP。因此可以在部署時切換到任何這些流行的框架。
  3. 無論使用哪種繫結,SLF4J都支援引數化日誌記錄訊息。由於SLF4J將應用程式和日誌記錄框架分離,因此可以輕鬆編寫獨立於日誌記錄框架的應用程式。而無需擔心用於編寫應用程式的日誌記錄框架。
  4. SLF4J提供了一個簡單的Java工具,稱為遷移器。使用此工具,可以遷移現有專案,這些專案使用日誌框架(如Jakarta Commons Logging(JCL)或log4j或Java.util.logging(JUL))到SLF4J。

2.2 繫結日誌的實現(Binding)

如前所述,SLF4J支援各種日誌框架。SLF4J發行版附帶了幾個稱為“SLF4J繫結”的jar檔案,每個繫結對應一個受支援的框架。

使用slf4j的日誌繫結流程:

  1. 新增slf4j-api的依賴
  2. 使用slf4j的API在專案中進行統一的日誌記錄
  3. 繫結具體的日誌實現框架
    1. 繫結已經實現了slf4j的日誌框架,直接新增對應依賴
    2. 繫結沒有實現slf4j的日誌框架,先新增日誌的介面卡,再新增實現類的依賴
  4. slf4j有且僅有一個日誌實現框架的繫結(如果出現多個預設使用第一個依賴日誌實現)

通過maven引入常見的日誌實現框架:

<!--slf4j core 使用slf4j必須新增-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.27</version>
</dependency>

<!-- log4j-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.27</version>
</dependency>
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

<!-- jul -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-jdk14</artifactId>
    <version>1.7.27</version>
</dependency>

<!--jcl -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-jcl</artifactId>
    <version>1.7.27</version>
</dependency>

<!-- nop -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-nop</artifactId>
    <version>1.7.27</version>
</dependency>

要切換日誌框架,只需替換類路徑上的slf4j繫結。例如,要從java.util.logging切換到log4j,只需將slf4j-jdk14-1.7.27.jar替換為slf4j-log4j12-1.7.27.jar即可。

SLF4J不依賴於任何特殊的類裝載。實際上,每個SLF4J繫結在編譯時都是硬連線的, 以使用一個且只有一個特定的日誌記錄框架。例如,slf4j-log4j12-1.7.27.jar繫結在編譯時繫結以使用log4j。在您的程式碼中,除了slf4j-api-1.7.27.jar之外,您只需將您選擇的一個且只有一個繫結放到相應的類路徑位置。不要在類路徑上放置多個繫結。以下是一般概念的圖解說明。

2.3 橋接舊的日誌框架(Bridging)

通常,您依賴的某些元件依賴於SLF4J以外的日誌記錄API。您也可以假設這些元件在不久的將來不會切換到SLF4J。為了解決這種情況,SLF4J附帶了幾個橋接模組,這些模組將對log4j,JCL和java.util.logging API的呼叫重定向,就好像它們是對SLF4J API一樣。

橋接解決的是專案中日誌的遺留問題,當系統中存在之前的日誌API,可以通過橋接轉換到slf4j的實現

  1. 先去除之前老的日誌框架的依賴
  2. 新增SLF4J提供的橋接元件
  3. 為專案新增SLF4J的具體實現

遷移的方式:
如果我們要使用SLF4J的橋接器,替換原有的日誌框架,那麼我們需要做的第一件事情,就是刪除掉原有專案中的日誌框架的依賴。然後替換成SLF4J提供的橋接器。

<!-- log4j-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>log4j-over-slf4j</artifactId>
    <version>1.7.27</version>
</dependency>

<!-- jul -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jul-to-slf4j</artifactId>
    <version>1.7.27</version>
</dependency>

<!--jcl -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    <version>1.7.27</version>
</dependency>

注意問題:

  1. jcl-over-slf4j.jar和 slf4j-jcl.jar不能同時部署。前一個jar檔案將導致JCL將日誌系統的選擇委託給SLF4J,後一個jar檔案將導致SLF4J將日誌系統的選擇委託給JCL,從而導致無限迴圈。
  2. log4j-over-slf4j.jar和slf4j-log4j12.jar不能同時出現
  3. jul-to-slf4j.jar和slf4j-jdk14.jar不能同時出現
  4. 所有的橋接都只對Logger日誌記錄器物件有效,如果程式中呼叫了內部的配置類或者是Appender,Filter等物件,將無法產生效果。

2.4 SLF4J原理解析

  1. SLF4J通過LoggerFactory載入日誌具體的實現物件。
  2. LoggerFactory在初始化的過程中,會通過performInitialization()方法繫結具體的日誌實現。
  3. 在繫結具體實現的時候,通過類載入器,載入org/slf4j/impl/StaticLoggerBinder.class
  4. 所以,只要是一個日誌實現框架,在org.slf4j.impl包中提供一個自己的StaticLoggerBinder類,在其中提供具體日誌實現的LoggerFactory就可以被SLF4J所載入

3. Logback的使用

Logback是由log4j創始人設計的另一個開源日誌元件,效能比log4j要好。
官方網站:https://logback.qos.ch/index.html

Logback主要分為三個模組:

  • logback-core:其它兩個模組的基礎模組
  • logback-classic:它是log4j的一個改良版本,同時它完整實現了slf4j API
  • logback-access:訪問模組與Servlet容器整合提供通過Http來訪問日誌的功能

後續的日誌程式碼都是通過SLF4J日誌門面搭建日誌系統,所以在程式碼是沒有區別,主要是通過修改配置檔案和pom.xml依賴

3.1 logback入門

  1. 新增依賴

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.25</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.3</version>
    </dependency>
  2. java程式碼

    //定義日誌物件
    public final static Logger LOGGER = LoggerFactory.getLogger(LogBackTest.class);
    
    @Test
    public void testSlf4j(){
        //列印日誌資訊
        LOGGER.error("error");
        LOGGER.warn("warn");
        LOGGER.info("info");
        LOGGER.debug("debug");
        LOGGER.trace("trace");
    }

3.2 logback配置

logback會依次讀取以下型別配置檔案:

  • logback.groovy
  • logback-test.xml
  • logback.xml 如果均不存在會採用預設配置
  1. logback元件之間的關係
    1. Logger:日誌的記錄器,把它關聯到應用的對應的context上後,主要用於存放日誌物件,也可以定義日誌型別、級別。
    2. Appender:用於指定日誌輸出的目的地,目的地可以是控制檯、檔案、資料庫等等。
    3. Layout:負責把事件轉換成字串,格式化的日誌資訊的輸出。在logback中Layout物件被封裝在encoder中。
  2. 基本配置資訊

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
        <!--
            日誌輸出格式:
            %-5level
            %d{yyyy-MM-dd HH:mm:ss.SSS}日期
            %c類的完整名稱
            %M為method
            %L為行號
            %thread執行緒名稱
            %m或者%msg為資訊
            %n換行
        -->
        <!--格式化輸出:%d表示日期,%thread表示執行緒名,%-5level:級別從左顯示5個字元寬度,%msg:日誌訊息,%n是換行符-->
        <property name="pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %c [%thread] %-5level %msg%n"/>
        <!--
            Appender: 設定日誌資訊的去向,常用的有以下幾個
            ch.qos.logback.core.ConsoleAppender (控制檯)
            ch.qos.logback.core.rolling.RollingFileAppender (檔案大小到達指定尺寸的時候產生一個新檔案)
            ch.qos.logback.core.FileAppender (檔案)
        -->
        <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
            <!--輸出流物件 預設 System.out 改為 System.err-->
            <target>System.err</target>
            <!--日誌格式配置-->
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <pattern>${pattern}</pattern>
            </encoder>
        </appender>
        <!--
            用來設定某一個包或者具體的某一個類的日誌列印級別、以及指定<appender>。
            <loger>僅有一個name屬性,一個可選的level和一個可選的addtivity屬性
            name:
                用來指定受此logger約束的某一個包或者具體的某一個類。
            level:
                用來設定列印級別,大小寫無關:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
                如果未設定此屬性,那麼當前logger將會繼承上級的級別。
            additivity:
                是否向上級loger傳遞列印資訊。預設是true。
            <logger>可以包含零個或多個<appender-ref>元素,標識這個appender將會新增到這個logger
        -->
        <!--
            也是<logger>元素,但是它是根logger。預設debug
            level:用來設定列印級別,大小寫無關:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
            <root>可以包含零個或多個<appender-ref>元素,標識這個appender將會新增到這個logger。
        -->
        <root level="ALL">
            <appender-ref ref="console"/>
        </root>
    </configuration>
  3. FileAppender配置

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
        <!-- 自定義屬性 可以通過${name}進行引用-->
        <property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss} %c %M %L [%thread] %m %n"/>
        <!--
        日誌輸出格式:
            %d{pattern}日期
            %m或者%msg為資訊
            %M為method
            %L為行號
            %c類的完整名稱
            %thread執行緒名稱
            %n換行
            %-5level
        -->
        <!-- 日誌檔案存放目錄 -->
        <property name="log_dir" value="d:/logs"></property>
    
        <!--控制檯輸出appender物件-->
        <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
            <!--輸出流物件 預設 System.out 改為 System.err-->
            <target>System.err</target>
            <!--日誌格式配置-->
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <pattern>${pattern}</pattern>
            </encoder>
        </appender>
    
        <!--日誌檔案輸出appender物件-->
        <appender name="file" class="ch.qos.logback.core.FileAppender">
            <!--日誌格式配置-->
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <pattern>${pattern}</pattern>
            </encoder>
            <!--日誌輸出路徑-->
            <file>${log_dir}/logback.log</file>
        </appender>
    
        <!-- 生成html格式appender物件 -->
        <appender name="htmlFile" class="ch.qos.logback.core.FileAppender">
            <!--日誌格式配置-->
            <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>
            <!--日誌輸出路徑-->
            <file>${log_dir}/logback.html</file>
        </appender>
    
        <!--RootLogger物件-->
        <root level="all">
            <appender-ref ref="console"/>
            <appender-ref ref="file"/>
            <appender-ref ref="htmlFile"/>
        </root>
    </configuration>
  4. RollingFileAppender配置

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
        <!-- 自定義屬性 可以通過${name}進行引用-->
        <property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss} %c %M %L [%thread] %m %n"/>
        <!--
        日誌輸出格式:
            %d{pattern}日期
            %m或者%msg為資訊
            %M為method
            %L為行號
            %c類的完整名稱
            %thread執行緒名稱
            %n換行
            %-5level
        -->
        <!-- 日誌檔案存放目錄 -->
        <property name="log_dir" value="d:/logs"></property>
    
        <!--控制檯輸出appender物件-->
        <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
            <!--輸出流物件 預設 System.out 改為 System.err-->
            <target>System.err</target>
            <!--日誌格式配置-->
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
            </encoder>
        </appender>
    
        <!-- 日誌檔案拆分和歸檔的appender物件-->
        <appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <!--日誌格式配置-->
            <encoder
            class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <pattern>${pattern}</pattern>
            </encoder>
            <!--日誌輸出路徑-->
            <file>${log_dir}/roll_logback.log</file>
            <!--指定日誌檔案拆分和壓縮規則-->
            <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
                <!--通過指定壓縮檔名稱,來確定分割檔案方式-->
                <fileNamePattern>${log_dir}/rolling.%d{yyyy-MMdd}.log%i.gz</fileNamePattern>
            <!--檔案拆分大小-->
                <maxFileSize>1MB</maxFileSize>
            </rollingPolicy>
        </appender>
    
        <!--RootLogger物件-->
        <root level="all">
            <appender-ref ref="console"/>
            <appender-ref ref="rollFile"/>
        </root>
    </configuration>
  5. Filter和非同步日誌配置

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
        <!-- 自定義屬性 可以通過${name}進行引用-->
        <property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss} %c %M %L [%thread] %m %n"/>
        <!--
        日誌輸出格式:
            %d{pattern}日期
            %m或者%msg為資訊
            %M為method
            %L為行號
            %c類的完整名稱
            %thread執行緒名稱
            %n換行
            %-5level
        -->
        <!-- 日誌檔案存放目錄 -->
        <property name="log_dir" value="d:/logs/"></property>
    
        <!--控制檯輸出appender物件-->
        <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
            <!--輸出流物件 預設 System.out 改為 System.err-->
            <target>System.err</target>
            <!--日誌格式配置-->
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <pattern>${pattern}</pattern>
            </encoder>
        </appender>
    
        <!-- 日誌檔案拆分和歸檔的appender物件-->
        <appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <!--日誌格式配置-->
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <pattern>${pattern}</pattern>
            </encoder>
            <!--日誌輸出路徑-->
            <file>${log_dir}roll_logback.log</file>
            <!--指定日誌檔案拆分和壓縮規則-->
            <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
                <!--通過指定壓縮檔名稱,來確定分割檔案方式-->
                <fileNamePattern>${log_dir}rolling.%d{yyyy-MM-dd}.log%i.gz</fileNamePattern>
                <!--檔案拆分大小-->
                <maxFileSize>1MB</maxFileSize>
            </rollingPolicy>
            <!--filter配置-->
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <!--設定攔截日誌級別-->
                <level>error</level>
                <onMatch>ACCEPT</onMatch>
                <onMismatch>DENY</onMismatch>
            </filter>
        </appender>
    
        <!--非同步日誌-->
        <appender name="async" class="ch.qos.logback.classic.AsyncAppender">
            <appender-ref ref="rollFile"/>
        </appender>
    
        <!--RootLogger物件-->
        <root level="all">
            <appender-ref ref="console"/>
            <appender-ref ref="async"/>
        </root>
    
        <!--自定義logger additivity表示是否從 rootLogger繼承配置-->
        <logger name="com.itheima" level="debug" additivity="false">
            <appender-ref ref="console"/>
        </logger>
    </configuration>
  6. 官方提供的log4j.properties轉換成logback.xml
    https://logback.qos.ch/translator/

3.3 logback-access的使用

logback-access模組與Servlet容器(如Tomcat和Jetty)整合,以提供HTTP訪問日誌功能。我們可以使用logback-access模組來替換tomcat的訪問日誌。

  1. 將logback-access.jar與logback-core.jar複製到$TOMCAT_HOME/lib/目錄下
  2. 修改$TOMCAT_HOME/conf/server.xml中的Host元素中新增:

    <Valve className="ch.qos.logback.access.tomcat.LogbackValve" />
  3. logback預設會在$TOMCAT_HOME/conf下查詢檔案 logback-access.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
        <!-- always a good activate OnConsoleStatusListener -->
        <statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener"/>
        <property name="LOG_DIR" value="${catalina.base}/logs"/>
        <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>${LOG_DIR}/access.log</file>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>access.%d{yyyy-MM-dd}.log.zip</fileNamePattern>
            </rollingPolicy>
            <encoder>
            <!-- 訪問日誌的格式 -->
                <pattern>combined</pattern>
            </encoder>
        </appender>
    
        <appender-ref ref="FILE"/>
    </configuration>
  4. 官方配置: https://logback.qos.ch/access.html#configuration

4. log4j2的使用

Apache Log4j 2是對Log4j的升級版,參考了logback的一些優秀的設計,並且修復了一些問題,因此帶來了一些重大的提升,主要有:

  • 異常處理,在logback中,Appender中的異常不會被應用感知到,但是在log4j2中,提供了一些異常處理機制。
  • 效能提升, log4j2相較於log4j 和logback都具有很明顯的效能提升,後面會有官方測試的資料。
  • 自動過載配置,參考了logback的設計,當然會提供自動重新整理引數配置,最實用的就是我們在生產上可以動態的修改日誌的級別而不需要重啟應用。
  • 無垃圾機制,log4j2在大部分情況下,都可以使用其設計的一套無垃圾機制,避免頻繁的日誌收集導致的jvm gc。

官網: https://logging.apache.org/log4j/2.x/

4.1 Log4j2入門

目前市面上最主流的日誌門面就是SLF4J,雖然Log4j2也是日誌門面,因為它的日誌實現功能非常強大,效能優越。所以大家一般還是將Log4j2看作是日誌的實現,Slf4j + Log4j2應該是未來的大勢所趨。

  1. 新增依賴

    <!-- Log4j2 門面API-->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-api</artifactId>
        <version>2.11.1</version>
    </dependency>
    
    <!-- Log4j2 日誌實現 -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.11.1</version>
    </dependency>
  2. JAVA程式碼

    public class Log4j2Test {
        // 定義日誌記錄器物件
        public static final Logger LOGGER = LogManager.getLogger(Log4j2Test.class);
        @Test
        public void testQuick() throws Exception {
            LOGGER.fatal("fatal");
            LOGGER.error("error");
            LOGGER.warn("warn");
            LOGGER.info("info");
            LOGGER.debug("debug");
            LOGGER.trace("trace");
        }
    }
  3. 使用slf4j作為日誌的門面,使用log4j2作為日誌的實現

    <!-- Log4j2 門面API-->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-api</artifactId>
        <version>2.11.1</version>
    </dependency>
    
    <!-- Log4j2 日誌實現 -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.11.1</version>
    </dependency>
    
    <!--使用slf4j作為日誌的門面,使用log4j2來記錄日誌 -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.25</version>
    </dependency>
    
    <!--為slf4j繫結日誌實現 log4j2的介面卡 -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-slf4j-impl</artifactId>
        <version>2.10.0</version>
    </dependency>

4.2 Log4j2配置

log4j2預設載入classpath下的 log4j2.xml 檔案中的配置。

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" monitorInterval="5">
    <properties>
        <property name="LOG_HOME">D:/logs</property>
    </properties>
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] [%-5level] %c{36}:%L --- %m%n" />
        </Console>
        
        <File name="file" fileName="${LOG_HOME}/myfile.log">
            <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %m%n" />
        </File>
        
        <RandomAccessFile name="accessFile" fileName="${LOG_HOME}/myAcclog.log">
            <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %m%n" />
        </RandomAccessFile>
        
        <RollingFile name="rollingFile" fileName="${LOG_HOME}/myrollog.log" filePattern="D:/logs/$${date:yyyy-MM-dd}/myrollog-%d{yyyyMM-dd-HH-mm}-%i.log">
            <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY" />
            <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %msg%n" />
            <Policies>
                <OnStartupTriggeringPolicy />
                <SizeBasedTriggeringPolicy size="10 MB" />
                <TimeBasedTriggeringPolicy />
            </Policies>
            <DefaultRolloverStrategy max="30" />
        </RollingFile>
    </Appenders>
    <Loggers>
        <Root level="trace">
            <AppenderRef ref="Console" />
        </Root>
    </Loggers>
</Configuration>

4.3 Log4j2非同步日誌

非同步日誌
log4j2最大的特點就是非同步日誌,其效能的提升主要也是從非同步日誌中受益,我們來看看如何使用log4j2的非同步日誌。

  • 同步日誌
  • 非同步日誌

Log4j2提供了兩種實現日誌的方式,一個是通過AsyncAppender,一個是通過AsyncLogger,分別對應前面我們說的Appender元件和Logger元件。

注意:配置非同步日誌需要新增依賴

<!--非同步日誌依賴-->
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.3.4</version>
</dependency>
  1. AsyncAppender方式

    <?xml version="1.0" encoding="UTF-8"?>
    <Configuration status="warn">
        <properties>
            <property name="LOG_HOME">D:/logs</property>
        </properties>
        <Appenders>
            <File name="file" fileName="${LOG_HOME}/myfile.log">
                <PatternLayout>
                    <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
                </PatternLayout>
            </File>
            <Async name="Async">
                <AppenderRef ref="file"/>
            </Async>
        </Appenders>
        <Loggers>
            <Root level="error">
                <AppenderRef ref="Async"/>
            </Root>
        </Loggers>
    </Configuration>
  2. AsyncLogger方式
    AsyncLogger才是log4j2 的重頭戲,也是官方推薦的非同步方式。它可以使得呼叫Logger.log返回的更快。你可以有兩種選擇:全域性非同步和混合非同步。
    • 全域性非同步就是,所有的日誌都非同步的記錄,在配置檔案上不用做任何改動,只需要新增一個log4j2.component.properties 配置;

      Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
    • 混合非同步就是,你可以在應用中同時使用同步日誌和非同步日誌,這使得日誌的配置方式更加靈活。

      <?xml version="1.0" encoding="UTF-8"?>
      <Configuration status="WARN">
          <properties>
              <property name="LOG_HOME">D:/logs</property>
          </properties>
          <Appenders>
              <File name="file" fileName="${LOG_HOME}/myfile.log">
                  <PatternLayout>
                      <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
                  </PatternLayout>
              </File>
              <Async name="Async">
                  <AppenderRef ref="file"/>
              </Async>
          </Appenders>
          <Loggers>
              <AsyncLogger name="com.itheima" level="trace" includeLocation="false" additivity="false">
                  <AppenderRef ref="file"/>
              </AsyncLogger>
              <Root level="info" includeLocation="true">
                  <AppenderRef ref="file"/>
              </Root>
          </Loggers>
      </Configuration>

      如上配置: com.itheima 日誌是非同步的,root日誌是同步的。

使用非同步日誌需要注意的問題:

  1. 如果使用非同步日誌,AsyncAppender、AsyncLogger和全域性日誌,不要同時出現。效能會和AsyncAppender一致,降至最低。
  2. 設定includeLocation=false ,列印位置資訊會急劇降低非同步日誌的效能,比同步日誌還要慢。

4.4 Log4j2的效能

Log4j2最牛的地方在於非同步輸出日誌時的效能表現,Log4j2在多執行緒的環境下吞吐量與Log4j和Logback的比較如下圖。下圖比較中Log4j2有三種模式:
1)全域性使用非同步模式;
2)部分Logger採用非同步模式;
3)非同步Appender。可以看出在前兩種模式下,Log4j2的效能較之Log4j和Logback有很大的優勢。

無垃圾記錄
垃圾收集暫停是延遲峰值的常見原因,並且對於許多系統而言,花費大量精力來控制這些暫停。

許多日誌庫(包括以前版本的Log4j)在穩態日誌記錄期間分配臨時物件,如日誌事件物件,字串,字元陣列,位元組陣列等。這會對垃圾收集器造成壓力並增加GC暫停發生的頻率。

從版本2.6開始,預設情況下Log4j以“無垃圾”模式執行,其中重用物件和緩衝區,並且儘可能不分配臨時物件。還有一個“低垃圾”模式,它不是完全無垃圾,但不使用ThreadLocal欄位。

Log4j 2.6中的無垃圾日誌記錄部分通過重用ThreadLocal欄位中的物件來實現,部分通過在將文字轉換為位元組時重用緩衝區來實現。

使用Log4j 2.5:記憶體分配速率809 MB /秒,141個無效集合。

Log4j 2.6沒有分配臨時物件:0(零)垃圾回收。

有兩個單獨的系統屬性可用於手動控制Log4j用於避免建立臨時物件的機制:

  • log4j2.enableThreadlocals - 如果“true”(非Web應用程式的預設值)物件儲存在ThreadLocal欄位中並重新使用,否則將為每個日誌事件建立新物件。
  • log4j2.enableDirectEncoders - 如果將“true”(預設)日誌事件轉換為文字,則將此文字轉換為位元組而不建立臨時物件。
    注意: 由於共享緩衝區上的同步,在此模式下多執行緒應用程式的同步日誌記錄效能可能更差。如果您的應用程式是多執行緒的並且日誌記錄效能很重要,請考慮使用非同步記錄器。

5. SpringBoot中的日誌使用

springboot框架在企業中的使用越來越普遍,springboot日誌也是開發中常用的日誌系統。springboot預設就是使用SLF4J作為日誌門面,logback作為日誌實現來記錄日誌。

5.1 SpringBoot中的日誌設計

springboot中的日誌

<dependency>
    <artifactId>spring-boot-starter-logging</artifactId>
    <groupId>org.springframework.boot</groupId>
</dependency>

依賴關係圖:

總結:

  1. springboot 底層預設使用logback作為日誌實現。
  2. 使用了SLF4J作為日誌門面
  3. 將JUL也轉換成slf4j
  4. 也可以使用log4j2作為日誌門面,但是最終也是通過slf4j呼叫logback

5.2 SpringBoot日誌使用

  1. 在springboot中測試列印日誌

    @SpringBootTest
    class SpringbootLogApplicationTests {
        //記錄器
        public static final Logger LOGGER = LoggerFactory.getLogger(SpringbootLogApplicationTests.class);
        @Test
        public void contextLoads() {
            // 列印日誌資訊
            LOGGER.error("error");
            LOGGER.warn("warn");
            LOGGER.info("info"); // 預設日誌級別
            LOGGER.debug("debug");
            LOGGER.trace("trace");
        }
    }
  2. 修改預設日誌配置

    logging.level.com.itheima=trace
    # 在控制檯輸出的日誌的格式 同logback
    logging.pattern.console=%d{yyyy-MM-dd} [%thread] [%-5level] %logger{50} - %msg%n
    
    # 指定檔案中日誌輸出的格式
    logging.file=D:/logs/springboot.log
    logging.pattern.file=%d{yyyy-MM-dd} [%thread] %-5level %logger{50} - %msg%n
  3. 指定配置
    給類路徑下放上每個日誌框架自己的配置檔案;pringBoot就不使用預設配置的了

    日誌框架 配置檔案
    Logback logback-spring.xml , logback.xml
    Log4j2 log4j2-spring.xml , log4j2.xml
    JUL logging.properties

    logback.xml:直接就被日誌框架識別了

  4. 使用SpringBoot解析日誌配置
    logback-spring.xml:由SpringBoot解析日誌配置

    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
        <springProfile name="dev">
            <pattern>${pattern}</pattern>
        </springProfile>
        <springProfile name="pro">
            <pattern>%d{yyyyMMdd:HH:mm:ss.SSS} [%thread] %-5level %msg%n</pattern>
        </springProfile>
    </encoder>

    application.properties

    spring.profiles.active=dev
  5. 將日誌切換為log4j2

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
        <!--排除logback-->
            <exclusion>
                <artifactId>spring-boot-starter-logging</artifactId>
                <groupId>org.springframework.boot</groupId>
            </exclusion>
        </exclusions>
    </dependency>
    
    <!-- 新增log4j2 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-log4j2</artifactId>
    </dependency>