Spring基礎知識(25)- Spring Boot (六)
統一日誌框架、日誌配置及輸出
1. 統一日誌框架
在專案開發中,日誌十分的重要,不管是記錄執行情況還是定位線上問題,都離不開對日誌的分析。在 Java 領域裡存在著多種日誌框架,如 JCL、SLF4J、Jboss-logging、jUL、log4j、log4j2、logback 等等。
1) 日誌框架的選擇
日誌框架可以被分為兩類:日誌門面(日誌抽象層)和日誌實現,如下表。
分類 描述
日誌門面(日誌抽象層) 為 Java 日誌訪問提供一套標準和規範的 API 框架,其主要意義在於提供介面。比如:JCL(Jakarta Commons Logging)、SLF4j(Simple Logging Facade for Java)、jboss-logging
日誌實現 日誌門面的具體的實現,比如 Log4j、JUL(java.util.logging)、Log4j2、Logback
通常情況下,日誌由一個日誌門面與一個日誌實現組合搭建而成,Spring Boot 選用 SLF4J + Logback 的組合來搭建日誌系統。
SLF4J 是目前最流行的日誌門面,使用 Slf4j 可以很靈活的使用佔位符進行引數佔位,簡化程式碼,擁有更好的可讀性。
Logback 是 Slf4j 的原生實現框架,它與 Log4j 出自一個人之手,但擁有比 log4j 更多的優點、特性和更做強的效能,現在基本都用來代替 log4j 成為主流。
2) SLF4J 的使用
在專案開發中,記錄日誌時不應該直接呼叫日誌實現層的方法,而應該呼叫日誌門面(日誌抽象層)的方法。
在使用 SLF4J 記錄日誌時,我們需要在應用中匯入 SLF4J 及日誌實現,並在記錄日誌時呼叫 SLF4J 的方法,例如:
1 import org.slf4j.Logger; 2 import org.slf4j.LoggerFactory; 3 4 public class App { 5 public staticvoid main(String[] args) { 6 Logger logger = LoggerFactory.getLogger(HelloWorld.class); 7 // 呼叫 sl4j 的 info() 方法,而非呼叫 logback 的方法 8 logger.info("Hello World"); 9 } 10 }
SLF4J 作為一款優秀的日誌門面或者日誌抽象層,它可以與各種日誌實現框架組合使用,以達到記錄日誌的目的。
從 SLF4J 官方給出的方案可以看出:
(1) Logback 作為 Slf4j 的原生實現框架,當應用使用 SLF4J+Logback 的組合記錄日誌時,只需要引入 SLF4J 和 Logback 的 Jar 包即可;
(2) Log4j 雖然與 Logback 出自同一個人之手,但是 Log4j 出現要早於 SLF4J,因而 Log4j 沒有直接實現 SLF4J,當應用使用 SLF4J+Log4j 的組合記錄日誌時,不但需要引入 SLF4J 和 Log4j 的 Jar 包,還必須引入它們之間的適配層(Adaptation layer)slf4j-log4j12.jar,該適配層可謂“上有老下有小”,它既要實現 SLF4J 的方法,還有呼叫 Log4j 的方法,以達到承上啟下的作用;
(3) 當應用使用 SLF4J+JUL 記錄日誌時,與 SLF4J+Log4j 一樣,不但需要引入 SLF4J 和 JUL 的對應的 Jar 包,還要引入適配層 slf4j-jdk14.jar。
這裡我們需要注意一點,每一個日誌的實現框架都有自己的配置檔案。使用 slf4j 記錄日誌時,配置檔案應該使用日誌實現框架(例如 logback、log4j 和 JUL 等等)自己本身的配置檔案。
3) 統一日誌框架(通用)
通常一個完整的應用下會依賴於多種不同的框架,而且它們記錄日誌使用的日誌框架也不盡相同,例如,Spring Boot(slf4j+logback),Spring(commons-logging)、Hibernate(jboss-logging)等等。那麼如何統一日誌框架的使用呢?
對此,SLF4J 官方也給出了相應的解決方案。
統一日誌框架一共需要以下 3 步 :
(1) 排除應用中的原來的日誌框架;
(2) 引入替換包替換被排除的日誌框架;
(3) 匯入 SLF4J 實現。
SLF4J 官方給出的統一日誌框架的方案是使用一個替換包來替換原來的日誌框架,例如 log4j-over-slf4j 替換 Log4j(Commons Logging API)、jul-to-slf4j.jar 替換 JUL(java.util.logging API)等等。
替換包內包含被替換的日誌框架中的所有類,這樣就可以保證應用不會報錯,但替換包內部實際使用的是 SLF4J API,以達到統一日主框架的目的。
4) 統一日誌框架(Spring Boot)
在使用 Spring Boot 時,同樣可能用到其他的框架,例如 Mybatis、Spring MVC、 Hibernate 等等,這些框架的底層都有自己的日誌框架,此時我們也需要對日誌框架進行統一。
統一日誌框架的使用一共分為 3 步,Soring Boot 作為一款優秀的開箱即用的框架,已經為使用者完成了其中 2 步:引入替換包和匯入 SLF4J 實現。
SpringBoot 底層使用 slf4j+logback 的方式記錄日誌,當我們引入了依賴了其他日誌框架的第三方框架(例如 Hibernate)時,只需要把這個框架所依賴的日誌框架排除,即可實現日誌框架的統一,示例程式碼如下。
1 <dependency> 2 <groupId>org.apache.activemq</groupId> 3 <artifactId>activemq-console</artifactId> 4 <version>${activemq.version}</version> 5 <exclusions> 6 <exclusion> 7 <groupId>commons-logging</groupId> 8 <artifactId>commons-logging</artifactId> 9 </exclusion> 10 </exclusions> 11 </dependency>
2. 日誌配置及輸出
1) 預設配置
Spring Boot 預設使用 SLF4J+Logback 記錄日誌,並提供了預設配置,即使我們不進行任何額外配,也可以使用 SLF4J+Logback 進行日誌輸出。
常見的日誌配置包括日誌級別、日誌的輸入出格式等內容。
(1) 日誌級別
日誌的輸出都是分級別的,當一條日誌資訊的級別大於或等於配置檔案的級別時,就對這條日誌進行記錄。
常見的日誌級別如下(優先順序依次升高)。
日誌級別 | 說明 |
trace | 追蹤,指明程式執行軌跡。 |
debug | 除錯,實際應用中一般將其作為最低級別,而 trace 則很少使用。 |
info | 輸出重要的資訊,使用較多。 |
warn | 警告,使用較多。 |
error | 錯誤資訊,使用較多。 |
(2) 輸出格式
可以通過以下常用日誌引數對日誌的輸出格式進行修改,如下表。
輸出格式 | 說明 |
%d{yyyy-MM-dd HH:mm:ss, SSS} | 日誌生產時間,輸出到毫秒的時間 |
%-5level | 輸出日誌級別,-5 表示左對齊並且固定輸出 5 個字元,如果不足在右邊補 0 |
%logger 或 %c | logger 的名稱 |
%thread 或 %t | 輸出當前執行緒名稱 |
%p | 日誌輸出格式 |
%message 或 %msg 或 %m | 日誌內容,即 logger.info("message") |
%n | 換行符 |
%class 或 %C | 輸出 Java 類名 |
%file 或 %F | 輸出檔名 |
%L | 輸出錯誤行號 |
%method 或 %M | 輸出方法名 |
%l | 輸出語句所在的行數, 包括類名、方法名、檔名、行數 |
hostName | 本地機器名 |
hostAddress | 本地 ip 地址 |
示例
1 package com.example; 2 3 import org.junit.jupiter.api.Test; 4 import org.slf4j.Logger; 5 import org.slf4j.LoggerFactory; 6 import org.springframework.boot.test.context.SpringBootTest; 7 8 @SpringBootTest 9 class AppTest { 10 Logger logger = LoggerFactory.getLogger(getClass()); 11 /** 12 * 測試日誌輸出 13 * SLF4J 日誌級別從小到大 trace > debug > info > warn > error 14 */ 15 @Test 16 void logTest() { 17 // 日誌級別 由低到高 18 logger.trace("trace 級別日誌"); 19 logger.debug("debug 級別日誌"); 20 logger.info("info 級別日誌"); 21 logger.warn("warn 級別日誌"); 22 logger.error("error 級別日誌"); 23 } 24 }
控制檯輸出:
2022-04-11 18:11:18.557 INFO 62045 --- [ main] com.example.AppTest : info 級別日誌
2022-04-11 18:11:18.559 WARN 62045 --- [ main] com.example.AppTest : warn 級別日誌
2022-04-11 18:11:18.560 ERROR 62045 --- [ main] com.example.AppTest : error 級別日誌
2) 修改預設日誌配置
可以根據自身的需求,通過全域性配置檔案(application.properties 或 application.yml)修改 Spring Boot 日誌級別和顯示格式等預設配置。
修改 application.properties,程式碼如下。
1 # 日誌級別 2 logging.level.com.example=trace 3 # 使用相對路徑的方式設定日誌輸出的位置(專案根目錄目錄\springboot-log\spring.log) 4 logging.file.path=springboot-log 5 # 絕對路徑方式將日誌檔案輸出到 【專案所在磁碟根目錄\springboot-log\spring.log】 6 # logging.file.path=D:\\temp\\springboot-log 7 # 控制檯日誌輸出格式 8 logging.pattern.console=%d{yyyy-MM-dd hh:mm:ss} [%thread] %-5level %logger{50} - %msg%n 9 # 日誌檔案輸出格式 10 logging.pattern.file=%d{yyyy-MM-dd} === [%thread] === %-5level === %logger{50} === - %msg%n
執行 AppTest,控制檯輸出:
2022-04-11 06:13:52 [main] TRACE com.example.AppTest - trace 級別日誌
2022-04-11 06:13:52 [main] DEBUG com.example.AppTest - debug 級別日誌
2022-04-11 06:13:52 [main] INFO com.example.AppTest - info 級別日誌
2022-04-11 06:13:52 [main] WARN com.example.AppTest - warn 級別日誌
2022-04-11 06:13:52 [main] ERROR com.example.AppTest - error 級別日誌
3) 自定義日誌配置
在 Spring Boot 的配置檔案 application.porperties/yml 中,可以對日誌的一些預設配置進行修改,但這種方式只能修改個別的日誌配置,想要修改更多的配置或者使用更高階的功能,則需要通過日誌實現框架自己的配置檔案進行配置。
Spring 官方提供了各個日誌實現框架所需的配置檔案,使用者只要將指定的配置檔案放置到專案的類路徑下即可。
日誌框架 | 配置檔案 |
Logback | logback-spring.xml、logback-spring.groovy、logback.xml、logback.groovy |
Log4j2 | log4j2-spring.xml、log4j2.xml |
JUL (Java Util Logging) | logging.properties |
從上表可以看出,日誌框架的配置檔案基本上被分為 2 類:
(1) 普通日誌配置檔案,即不帶 srping 標識的配置檔案,例如 logback.xml;
(2) 帶有 spring 表示的日誌配置檔案,例如 logback-spring.xml。
這兩種日誌配置檔案在使用時大不相同,下面我們就對它們分別進行介紹。
(1) 普通日誌配置檔案
將 logback.xml、log4j2.xml 等不帶 spring 標識的普通日誌配置檔案,放在專案的類路徑下後,這些配置檔案會跳過 Spring Boot,直接被日誌框架載入。通過這些配置檔案,我們就可以達到自定義日誌配置的目的。
示例
建立 src/main/resources/logback.xml 檔案,內容如下。
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!-- 3 scan:當此屬性設定為true時,配置檔案如果發生改變,將會被重新載入,預設值為true。 4 scanPeriod:設定監測配置檔案是否有修改的時間間隔,如果沒有給出時間單位, 5 預設單位是毫秒當scan為true時,此屬性生效。預設的時間間隔為1分鐘。 6 debug:當此屬性設定為true時,將打印出logback內部日誌資訊,實時檢視logback執行狀態。 7 預設值為false。 8 --> 9 <configuration scan="false" scanPeriod="60 seconds" debug="false"> 10 <!-- 定義日誌的根目錄 --> 11 <property name="LOG_HOME" value="D:\\temp"/> 12 <!-- 定義日誌檔名稱 --> 13 <property name="appName" value="springboot-basic-logback"></property> 14 15 <!-- ch.qos.logback.core.ConsoleAppender 表示控制檯輸出 --> 16 <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender"> 17 <!-- 18 日誌輸出格式: 19 %d表示日期時間, 20 %thread表示執行緒名, 21 %-5level:級別從左顯示5個字元寬度 22 %logger{50} 表示logger名字最長50個字元,否則按照句點分割。 23 %msg:日誌訊息, 24 %n是換行符 25 --> 26 <layout class="ch.qos.logback.classic.PatternLayout"> 27 <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] ***** %-5level %logger{50} - %msg%n</pattern> 28 </layout> 29 </appender> 30 31 <!-- 滾動記錄檔案,先將日誌記錄到指定檔案,當符合某個條件時,將日誌記錄到其他檔案 --> 32 <appender name="appLogAppender" class="ch.qos.logback.core.rolling.RollingFileAppender"> 33 <!-- 指定日誌檔案的名稱 --> 34 <file>${LOG_HOME}/${appName}.log</file> 35 <!-- 36 當發生滾動時,決定 RollingFileAppender 的行為,涉及檔案移動和重新命名 37 TimeBasedRollingPolicy: 最常用的滾動策略,它根據時間來制定滾動策略, 38 既負責滾動也負責出發滾動。 39 --> 40 <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> 41 <!-- 42 滾動時產生的檔案的存放位置及檔名稱 %d{yyyy-MM-dd}:按天進行日誌滾動 43 %i:當檔案大小超過maxFileSize時,按照i進行檔案滾動 44 --> 45 <fileNamePattern>${LOG_HOME}/${appName}-%d{yyyy-MM-dd}-%i.log</fileNamePattern> 46 <!-- 47 可選節點,控制保留的歸檔檔案的最大數量,超出數量就刪除舊檔案。假設設定每天滾動, 48 且maxHistory是365,則只儲存最近365天的檔案,刪除之前的舊檔案。 49 注意,刪除舊檔案是,那些為了歸檔而建立的目錄也會被刪除。 50 --> 51 <MaxHistory>365</MaxHistory> 52 <!-- 53 當日志文件超過maxFileSize指定的大小是,根據上面提到的%i進行日誌檔案滾動 54 注意此處配置SizeBasedTriggeringPolicy是無法實現按檔案大小進行滾動的, 55 必須配置timeBasedFileNamingAndTriggeringPolicy 56 --> 57 <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> 58 <maxFileSize>100MB</maxFileSize> 59 </timeBasedFileNamingAndTriggeringPolicy> 60 </rollingPolicy> 61 <!-- 日誌輸出格式: --> 62 <layout class="ch.qos.logback.classic.PatternLayout"> 63 <pattern>%d{yyyy-MM-dd HH:mm:ss} [ %thread ] ---- [ %-5level ] [ %logger{50} : %line ] - %msg%n 64 </pattern> 65 </layout> 66 </appender> 67 68 <!-- 69 logger主要用於存放日誌物件,也可以定義日誌型別、級別 70 name:表示匹配的logger型別字首,也就是包的前半部分 71 level:要記錄的日誌級別,包括 TRACE < DEBUG < INFO < WARN < ERROR 72 additivity:作用在於children-logger是否使用 rootLogger配置的appender進行輸出, 73 false:表示只用當前logger的appender-ref,true: 74 表示當前logger的appender-ref和rootLogger的appender-ref都有效 75 --> 76 <!-- hibernate logger --> 77 <logger name="net.biancheng.www" level="debug"/> 78 <!-- Spring framework logger --> 79 <logger name="org.springframework" level="debug" additivity="false"></logger> 80 <!-- 81 root與logger是父子關係,沒有特別定義則預設為root,任何一個類只會和一個logger對應, 82 要麼是定義的logger,要麼是root,判斷的關鍵在於找到這個logger, 83 然後判斷這個logger的appender和level。 84 --> 85 <root level="info"> 86 <appender-ref ref="stdout"/> 87 <appender-ref ref="appLogAppender"/> 88 </root> 89 </configuration>
(2) 帶有 spring 標識的日誌配置檔案
Spring Boot 推薦使用者使用 logback-spring.xml、log4j2-spring.xml 等這種帶有 spring 標識的配置檔案。這種配置檔案被放在專案類路徑後,不會直接被日誌框架載入,而是由 Spring Boot 對它們進行解析,這樣就可以使用 Spring Boot 的高階功能 Profile,實現在不同的環境中使用不同的日誌配置。
示例,建立 src/main/resources/logback-spring.xml 檔案,內容如下。
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!-- 3 scan:當此屬性設定為true時,配置檔案如果發生改變,將會被重新載入,預設值為true。 4 scanPeriod:設定監測配置檔案是否有修改的時間間隔,如果沒有給出時間單位, 5 預設單位是毫秒當scan為true時,此屬性生效。預設的時間間隔為1分鐘。 6 debug:當此屬性設定為true時,將打印出logback內部日誌資訊,實時檢視logback執行狀態。 7 預設值為false。 8 --> 9 <configuration scan="false" scanPeriod="60 seconds" debug="false"> 10 <!-- 定義日誌的根目錄 --> 11 <property name="LOG_HOME" value="D:\\temp"/> 12 <!-- 定義日誌檔名稱 --> 13 <property name="appName" value="springboot-basic-logback-spring"></property> 14 15 <!-- ch.qos.logback.core.ConsoleAppender 表示控制檯輸出 --> 16 <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender"> 17 <layout class="ch.qos.logback.classic.PatternLayout"> 18 <!--開發環境 日誌輸出格式--> 19 <springProfile name="dev"> 20 <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} ---> [%thread] --> %-5level %logger{50} - %msg%n</pattern> 21 </springProfile> 22 <!--非開發環境 日誌輸出格式--> 23 <springProfile name="!dev"> 24 <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} === [%thread] === %-5level %logger{50} - %msg%n</pattern> 25 </springProfile> 26 </layout> 27 </appender> 28 29 <!-- 滾動記錄檔案,先將日誌記錄到指定檔案,當符合某個條件時,將日誌記錄到其他檔案 --> 30 <appender name="appLogAppender" class="ch.qos.logback.core.rolling.RollingFileAppender"> 31 <!-- 指定日誌檔案的名稱 --> 32 <file>${LOG_HOME}/${appName}.log</file> 33 <!-- 34 當發生滾動時,決定 RollingFileAppender 的行為,涉及檔案移動和重新命名 35 TimeBasedRollingPolicy: 最常用的滾動策略,它根據時間來制定滾動策略, 36 既負責滾動也負責出發滾動。 37 --> 38 <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> 39 <!-- 40 滾動時產生的檔案的存放位置及檔名稱 %d{yyyy-MM-dd}:按天進行日誌滾動 41 %i:當檔案大小超過maxFileSize時,按照i進行檔案滾動 42 --> 43 <fileNamePattern>${LOG_HOME}/${appName}-%d{yyyy-MM-dd}-%i.log</fileNamePattern> 44 <!-- 45 可選節點,控制保留的歸檔檔案的最大數量,超出數量就刪除舊檔案。假設設定每天滾動, 46 且maxHistory是365,則只儲存最近365天的檔案,刪除之前的舊檔案。 47 注意,刪除舊檔案是,那些為了歸檔而建立的目錄也會被刪除。 48 --> 49 <MaxHistory>365</MaxHistory> 50 <!-- 51 當日志文件超過maxFileSize指定的大小是,根據上面提到的%i進行日誌檔案滾動 52 注意此處配置SizeBasedTriggeringPolicy是無法實現按檔案大小進行滾動的, 53 必須配置timeBasedFileNamingAndTriggeringPolicy 54 --> 55 <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> 56 <maxFileSize>100MB</maxFileSize> 57 </timeBasedFileNamingAndTriggeringPolicy> 58 </rollingPolicy> 59 <!-- 日誌輸出格式: --> 60 <layout class="ch.qos.logback.classic.PatternLayout"> 61 <pattern>%d{yyyy-MM-dd HH:mm:ss} [ %thread ] ---- [ %-5level ] [ %logger{50} : %line ] - %msg%n 62 </pattern> 63 </layout> 64 </appender> 65 66 <!-- 67 logger主要用於存放日誌物件,也可以定義日誌型別、級別 68 name:表示匹配的logger型別字首,也就是包的前半部分 69 level:要記錄的日誌級別,包括 TRACE < DEBUG < INFO < WARN < ERROR 70 additivity:作用在於children-logger是否使用 rootLogger配置的appender進行輸出, 71 false:表示只用當前logger的appender-ref,true: 72 表示當前logger的appender-ref和rootLogger的appender-ref都有效 73 --> 74 <!-- hibernate logger --> 75 <logger name="net.biancheng.www" level="debug"/> 76 <!-- Spring framework logger --> 77 <logger name="org.springframework" level="debug" additivity="false"></logger> 78 79 <!-- 80 root與logger是父子關係,沒有特別定義則預設為root,任何一個類只會和一個logger對應, 81 要麼是定義的logger,要麼是root,判斷的關鍵在於找到這個logger, 82 然後判斷這個logger的appender和level。 83 --> 84 <root level="info"> 85 <appender-ref ref="stdout"/> 86 <appender-ref ref="appLogAppender"/> 87 </root> 88 </configuration>