(四)SpringBoot2.0基礎之日誌

在這裏插入圖片描述
在這裏插入圖片描述

知其然 知其所以然

創作不易 求點贊👍 求關注❤️ 求分享👥

絮叨

上一篇我們講了SpringBoot的配置文件,瞭解了配置文件的加載順序和配置的原理,這章呢我們呢來看下日誌的相關內容,對於一個應用程序來說日誌記錄是必不可少的一部分。
開發和生產環境中的問題追蹤,基於日誌的業務邏輯統計分析等都離不日誌。目前常用的日誌框架包括Log4j 1,Log4j 2,Commons logging,Logback,Jul,Jcl,Slf4j等等一大堆。

正文

日誌介紹

要介紹日誌啊,我們首先要知道日誌的分類:日誌門面和日誌實現。
日誌門面是爲日誌系統中的一組接口提供一個統一的高層接口,使得子系統更容易使用。簡單來說就是我統一一組API,其他日誌只要實現我的接口就好。因爲各個日誌框架的API不是統一的,如果我們更換了日誌框架,相應的API也要跟着改變。而有了日誌門面好處是API固定,即使換了別的日誌框架,只要這個日誌框架實現了這個接口,我們就不用修改之前的API。
日誌實現是日誌門面接口的實現。

日誌門面 日誌實現
JCL(Jakarta Commons Logging) Jboss-Logging SLF4j(Simple Logging Facade for Java) Log4j JUL(java.util.logging) Log4j2 Logback

所以入門在使用日誌的時候我們最好呢是從左面選一個日誌門面。從右面選一個日誌實現。比如:SLF4j+log4j,JCL+Logback。
接下來呢,我們介紹下日誌的選擇。

  • JCL:全稱爲"Jakarta Commons Logging",也可稱爲"Apache Commons Logging"。最後一次更新是2014年。Spring框架默認使用此日誌門面。
  • Slf4j:slf4j只是一個日誌標準,並不是日誌系統的具體實現。slf4j只做兩件事情:提供日誌接口和提供獲取具體日誌對象的方法。
  • Jboss-Logging:jboss-logging是一款類似於slf4j的日誌門面,他的使用有點複雜,不是給我們普通人用的。

綜上原因,日誌門面我們選擇slf4j。

  • Log4j:Log4j是Apache的一個開源項目。log4j寫出來的時間比較早,所以當遇到高併發或者日誌輸出過多情況,可能導致線程阻塞,消耗時間過大。且官方停止更新,並建議從log4j升級到log4j2。
  • JUL:JUL是JDK自帶的一個日誌實現,怕市場被log4j獨佔,也開發一個日誌框架,由於在使用便利性和性能上都欠佳,所以存在感一直不高。
  • Logback:和log4j、slf4j是同一個人寫的(Ceki Gülcü,俄羅斯程序員。真乃大神啊),是log4j的升級版。LogBack改進了很多Log4J的缺點,在性能上有了很大的提升,同時使用方式幾乎和Log4J一樣。因爲日誌門面我們選了slf4j,而這兩個框架是同一個人寫的,適配應該會更好,建議選擇Logback。SpringBoot默認選用slf4j和logback的組合,可以導入starter-log4j的場景啓動器去使用log4j2+slf4j的組合。
  • Log4j 2:apache公司借Log4j之名重新寫的日誌框架。改進了很多Log4J的缺點,同時也借鑑了LogBack,號稱在性能上也是完勝LogBack。,但是因爲太好了,有很多框架還不支持。

綜上原因,日誌實現我們選擇Logback。(Log4j2也不錯,這裏只是拿logback舉例)

SLF4J+LogBack使用

  1. 首先我們要導入slf4j和logback的jar包。logback-classic會幫我們導入logback-core的jar,所以知道入職一個就可以了。scope標籤要寫compile,如果是test只有在測試環境下才可以使用,而我們是main方法啓動不屬於測試環境,所以這個包相當於就沒有加入依賴。
       <dependency>
           <groupId>ch.qos.logback</groupId>
           <artifactId>logback-classic</artifactId>
           <version>1.2.3</version>
           <scope>compile</scope>
       </dependency>
       <dependency>
           <groupId>org.slf4j</groupId>
           <artifactId>slf4j-api</artifactId>
           <version>1.7.30</version>
       </dependency>
       ```
    
  2. 編寫測試類。
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    public class LogbackTest {
        private static final Logger logger = LoggerFactory.getLogger(LogbackTest.class);
        public static void main(String[] args) {
            logger.info("hello world");
        }
    }
    
  3. 運行查看測試結果。
    在這裏插入圖片描述
    注意:在我們實際開發使用的時候,調用日誌方法的時候,不要使用日誌實現去調用,這樣就失去了日誌門面的意義。而是使用日誌門面裏面的方法去調用。
    正例:
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    public class LogbackTest {
        private static final Logger logger = LoggerFactory.getLogger(LogbackTest.class);
    }
    

SLF4J搭配其他日誌實現

從上面我們知道slf4j是日誌門面,可以和日誌實現搭配使用。那麼它是怎麼去適配其他的日誌實現的呢。跟着我,我們一探究竟。
在這裏插入圖片描述
我們根據上圖去講解slf4j和各個日誌實現的使用。豎着看,每一紅框是一類,並且標上了數字。

  • ①:如果在我們的程序中我們只導入了slf4j的jar,它會輸出到一個空的路徑,因爲我們沒有日誌實現。(只有slf4j的jar)
  • ②:如果在我們的程序中導入了slf4j的jar,並且導入了logback的日誌實現。雖然我們在調用的時候使用的是slf4j,但是因爲我們有logback作日誌實現。所以日誌會記錄到控制檯或者文件中。(有slf4j和logback的jar)
  • ③:如果在我們的程序中導入了slf4j的jar,並且想使用log4j作日誌實現,我們要導入一個適配層,也就是slf4j-log4j12.jar。這個jar相當於承上啓下,上要去實現slf4j的接口,下日誌實現要調用log4j的方法。(有slf4j和log4j和slf4j-log4j12的jar)
  • ④:如果在我們的程序中導入了slf4j的jar,並且想使用HUL作日誌實現,同log4j一樣,我們要導入一個適配層,slf4j-jdk14.jar。作用同③。(有slf4j和log4j和slf4j-jdk14的jar)
  • ⑤:如果在我們的程序中導入了slf4j的jar,我們也可以導入slf4j-simple的jar,它是slf4j的簡單實現。(有slf4j和slf4j-simple的jar)

總結:如果我們要使用slf4j做日誌門面,那麼選擇不同的日誌實現就按照上圖所說的去導入相應的jar去實現就好。每一個日誌的實現框架都有自己的配置文件。使用slf4j以後,配置文件還是做成日誌實現自己本身的配置文件。

統一日誌框架

在我們的程序中可能會存在多個日誌門面和日誌實現,比如,如果我們依賴spring,spring用的日誌框架是JUL,如果我們依賴springboot,springboot使用slf4j+logback的組合,如果我們依賴Hibernate,它使用的是jboss-logging等等。
這樣我們系統中的日誌框架就很雜,不易於管理和日誌分析。所以我們可以統一日誌框架,其他依賴中的框架也和我們一起統一使用同一個日誌框架。那麼我們要怎麼統一日誌框架呢?
在這裏插入圖片描述

  • ①:我們首先豎着看,我們的應用程序使用slf4j+logback作爲日誌的統一框架。之後橫着看,我們的應用程序會有很多依賴,而這些依賴可能是用的是commons-logging,log4j,JUL等等。那麼我們要怎麼統一呢?答案是我們使用替換包,比如commons-logging,我們導入jcl-over-slf4j.jar去替換原來的jar,log4j我們使用log4j-over-slf4j.jar去替換原來的jar等等。(首先要在原依賴中排除掉日誌依賴)
    這個替換包爲什麼能做到呢?因爲它包裝了原來的commons-logging包,原來的包中有什麼類,這個替換包就有相同的類,這樣再調用這個包的時候就不會報錯(API是一樣的)。但是方法中真正的實現是去調用slf4j的API,而slf4j又去調用我們真正的日誌框架,這就達到了統一日誌框架的目的。
  • ②:和①一樣,首先豎着看,這次我們使用slf4j作爲日誌門面,但是日誌實現使用的是log4j,但是因爲slf4j和log4j不適配,我們還要導入一個適配jar,也就是slf4j-log4j12.jar。之後再橫着看,如果我們程序中的依賴使用了commons-logging,我們也是要導入jcl-over-slf4j.jar去替換原來的jar,log4j我們使用log4j-over-slf4j.jar去替換原來的jar等等。(首先要在原依賴中排除掉日誌依賴)
  • ③:和②一樣,不做過多解釋了。可以結合之前的SLF4J搭配其他日誌實現和②去看。

總結一下想要統一日誌框架的步驟:

  1. 再pom中排除其他日誌依賴
  2. 使用替換包去替換原來依賴的日誌包
  3. 導入我們想要統一後的日誌依賴(slf4j+logback)

SpringBoot日誌

上面我們介紹了日誌的基本概念和基本使用以及日誌之間的搭配。接下來我們迴歸正傳,說一下SpringBoot中的日誌。
我們首先使用springboot initializr新建一個springboot項目,這裏我們只選擇一個web模塊就好。(如果不會的話,請看我的第三篇文章)
在這裏插入圖片描述
創建好項目後,我們打開pom文件,右鍵->diagrams->Show dependencies…打開我們的maven依賴。
在這裏插入圖片描述
在這裏我們可以看到spring-boot-starter-web依賴了spring-boot-starter,而spring-boot-starter依賴了spring-boot-starter-logging。這個spring-boot-starter-logging就是springboot的日誌功能的模塊。有了以上的基礎再看SpringBoot的日誌依賴就很明白了。
在這裏插入圖片描述
在這裏插入圖片描述
根據上圖可以知道①SpringBoot默認使用slf4j+logback或log4j2實現日誌功能。②因爲有轉換包,所以SpringBoot把其他的日誌都替換成了slf4j的實現。③想統一日誌框架的話,就要把其他框架依賴的日誌框架排除掉。

日誌級別

我們在上面的項目中寫以下代碼,運行測試類。

package com.springbootlog.springbootlog;

@SpringBootTest
@RunWith(SpringRunner.class)
class SpringbootLog02ApplicationTests {
    Logger logger = LoggerFactory.getLogger(getClass());
    @Test
    void contextLoads() {
        logger.trace("trace");
        logger.debug("debug");
        logger.info("info");
        logger.warn("warn");
        logger.error("error");
    }
}

在這裏插入圖片描述
由上圖我們可以知道,當我們不指定日誌級別時,SpringBoot默認給我們使用的是info級別。日誌的級別由低到高:trace<debug<info<warn<error。
我們可以在application.properties文件中指定日誌級別。也可以指定某個包的日誌級別。

logging.level.root=trace // 指定日誌級別
logging.level.com.springbootlog.springbootlog=error // 指定指定包的日誌級別

在這裏插入圖片描述

日誌輸出格式
# 在控制檯輸出的日誌的格式
logging.pattern.console=%d{yyyy/MM/dd} [%thread] %-5level %logger{50} - %msg%n
# 指定文件中日誌輸出的格式
logging.pattern.file=%d{yyyy-MM-dd} === [%thread] === %-5level === %logger{50} ==== %msg%n

日誌輸出格式:
%d表示日期時間,
%thread表示線程名,
%-5level:級別從左顯示5個字符寬度
%logger{50} 表示logger名字最長50個字符,否則按照句點分割。
%msg:日誌消息,
%n是換行符

%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n

在這裏插入圖片描述

日誌輸出路徑
  • logging.path:在當前磁盤的根路徑下創建相應的文件夾;使用 spring.log 作爲默認文件。
    # 在當前磁盤的根路徑下創建spring文件夾和裏面的log文件夾;使用 spring.log 作爲默認文件
    logging.path=/spring/log
    
  • logging.file:不指定路徑在當前項目下生成springboot.log日誌,可以指定完整的路徑。
    # 不指定路徑在當前項目下生成springboot.log日誌
    #logging.path=
    # 可以指定完整的路徑;
    logging.file=D:/springboot.log
    
  • 區別:
    如果我們既不指定logging.file也不指定logging.path,則日誌只在控制檯輸出。
    如果我們指定logging.file它會把日誌輸出到指定的路徑名和文件名中。
    如果我們指定logging.path它會把日誌輸出到指定的路徑的spring.log文件中。
    logging.file logging.path Example Description
    NA NA 只在控制檯輸出
    指定文件名 NA spring.log 輸出日誌到spring.log文件
    NA 指定目錄 /var/log 輸出到指定目錄的 spring.log 文件中
日誌配置

如果我們在類路徑下放每個日誌框架的自己的配置文件,SpringBoot就不使用默認配置。

Logging System Customization
Logback logback-spring.xml, logback-spring.groovy, logback.xml or logback.groovy
Log4j2 log4j2-spring.xml or log4j2.xml
JDK (Java Util Logging) logging.properties

SpringBoot啓動的時候會在類路徑下掃描這些文件,如果沒有找到就使用SpringBoot自己默認的日誌配置,如果存在,就是用指定的日誌配置。如果文件名字不帶-spring的,會被日誌框架識別。如果文件名帶-spring的,日誌框架就不直接加載日誌的配置項,而是由SpringBoot解析日誌配置,可以使用SpringBoot的高級Profile功能。

<springProfile name="dev">
    <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} ----> [%thread] ---> %-5level %logger{50} - %msg%n</pattern>
</springProfile>
<springProfile name="!dev">
    <pattern>%d{yyyy/MM/dd HH:mm:ss.SSS} ====[%thread] ===%-5level %logger{50} - %msg%n</pattern>
</springProfile>

下面是logback的一些常用配置和解釋。

<?xml version="1.0" encoding="UTF-8"?>
<!--
scan:當此屬性設置爲true時,配置文件如果發生改變,將會被重新加載,默認值爲true。
scanPeriod:設置監測配置文件是否有修改的時間間隔,如果沒有給出時間單位,默認單位是毫秒當scan爲true時,此屬性生效。默認的時間間隔爲1分鐘。
debug:當此屬性設置爲true時,將打印出logback內部日誌信息,實時查看logback運行狀態。默認值爲false-->
<configuration scan="false" scanPeriod="60 seconds" debug="false">
    <!-- 定義日誌的根目錄 -->
    <property name="LOG_HOME" value="/app/log" />
    <!-- 定義日誌文件名稱 -->
    <property name="appName" value="atguigu-springboot"></property>
    <!-- ch.qos.logback.core.ConsoleAppender 表示控制檯輸出 -->
    <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
        <!--
        日誌輸出格式:
			%d表示日期時間,
			%thread表示線程名,
			%-5level:級別從左顯示5個字符寬度
			%logger{50} 表示logger名字最長50個字符,否則按照句點分割。 
			%msg:日誌消息,
			%n是換行符
        -->
        <layout class="ch.qos.logback.classic.PatternLayout">
            <springProfile name="dev">
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} ----> [%thread] ---> %-5level %logger{50} - %msg%n</pattern>
            </springProfile>
            <springProfile name="!dev">
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} ==== [%thread] ==== %-5level %logger{50} - %msg%n</pattern>
            </springProfile>
        </layout>
    </appender>

    <!-- 滾動記錄文件,先將日誌記錄到指定文件,當符合某個條件時,將日誌記錄到其他文件 -->  
    <appender name="appLogAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 指定日誌文件的名稱 -->
        <file>${LOG_HOME}/${appName}.log</file>
        <!--
        當發生滾動時,決定 RollingFileAppender 的行爲,涉及文件移動和重命名
        TimeBasedRollingPolicy: 最常用的滾動策略,它根據時間來制定滾動策略,既負責滾動也負責出發滾動。
        -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--
            滾動時產生的文件的存放位置及文件名稱 %d{yyyy-MM-dd}:按天進行日誌滾動 
            %i:當文件大小超過maxFileSize時,按照i進行文件滾動
            -->
            <fileNamePattern>${LOG_HOME}/${appName}-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
            <!-- 
            可選節點,控制保留的歸檔文件的最大數量,超出數量就刪除舊文件。假設設置每天滾動,
            且maxHistory是365,則只保存最近365天的文件,刪除之前的舊文件。注意,刪除舊文件是,
            那些爲了歸檔而創建的目錄也會被刪除。
            -->
            <MaxHistory>365</MaxHistory>
            <!-- 
            當日志文件超過maxFileSize指定的大小是,根據上面提到的%i進行日誌文件滾動 注意此處配置SizeBasedTriggeringPolicy是無法實現按文件大小進行滾動的,必須配置timeBasedFileNamingAndTriggeringPolicy
            -->
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <!-- 日誌輸出格式: -->     
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [ %logger{50} : %line ] - %msg%n</pattern>
        </layout>
    </appender>

    <!-- 
		logger主要用於存放日誌對象,也可以定義日誌類型、級別
		name:表示匹配的logger類型前綴,也就是包的前半部分
		level:要記錄的日誌級別,包括 TRACE < DEBUG < INFO < WARN < ERROR
		additivity:作用在於children-logger是否使用 rootLogger配置的appender進行輸出,
		false:表示只用當前logger的appender-ref,true:
		表示當前logger的appender-ref和rootLogger的appender-ref都有效
    -->
    <!-- hibernate logger -->
    <logger name="com.atguigu" level="debug" />
    <!-- Spring framework logger -->
    <logger name="org.springframework" level="debug" additivity="false"></logger>
    
    <!-- 
    root與logger是父子關係,沒有特別定義則默認爲root,任何一個類只會和一個logger對應,
    要麼是定義的logger,要麼是root,判斷的關鍵在於找到這個logger,然後判斷這個logger的appender和level。 
    -->
    <root level="info">
        <appender-ref ref="stdout" />
        <appender-ref ref="appLogAppender" />
    </root>
</configuration> 

總結

本文介紹了各個日誌框架以及分類,以及日誌的選擇,在此基礎上介紹了怎麼統一日誌框架。最後我們有了這些基礎,又介紹了SpringBoot中對日誌框架的支持。看完這篇,希望能讓大家對日誌框架有一個大概的理解,以及SpringBoot中的日誌支持。

如果本篇博客有任何錯誤,請批評指教,不勝感激 !

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章