作者:小先,一個專注大數據、分佈式技術的非斜槓青年,愛Coding,愛閱讀、愛攝影,更愛生活!
日誌對於程序的重要性不言而喻,當程序運行出現問題的時候,我們可以通過日誌快速的定位、分析問題。
在開發的時候,還可以通過 IDE 的調試功能或者 System.out 、System.err 來簡單打印一下信息,用來定位 bug,但這種方式是在任何時候都不推薦的,在生產環境中,撇開 System.out 會影響系統性能不說,出現問題的時候連錯誤日誌記錄在哪裡都難找。
作為企業應用開發來說,可能有不同的功能模塊,不同的功能模塊之間的相互調用可能存在許多問題,在開發和演示環境中,很多問題可能無法暴露出來。當某個功能發生異常了,然而你無法像開發環境一樣一步一步調試來找出問題,這時候能幫到你的只有日誌記錄。
Java 發展至今,有著非常成熟的生態體系,有著非常多成熟的日誌框架可以選擇, 因此也不推薦你從零搭建日誌框架。
這裡將實戰 Spring Boot 整合 Log4j2 與 Slf4j 實現日誌打印和輸出到文件。
1、Java 日誌框架和日誌門面
在 Java 生態中,日誌方面的技術主要分為日誌框架和日誌門面兩大方面。日誌框架是日誌功能的具體實現,日誌門面在日誌框架的上面再做一層封裝,對應用程序屏蔽底層日誌框架的實現及細節。這裡簡要介紹 Java 常用的日誌框架和日誌門面。
1.1、常用的日誌框架
java.util.logging : 是從 JDK 1.4 開始引入的 Java 原生日誌框架,定義了七個日誌級別,分別是:SEVERE、WARNING、INFO、CONFIG、FINE、FINER、FINEST。
Log4j : 是 Apache 的開源項目,出自 Ceki Gülcü 之手,我們可以靈活地控制日誌的輸出格式、控制日誌輸出到控制檯還文件,而這些無需對代碼進行更改,只需要簡單地更改一下配置文件即可。同樣定義了七個日誌級別:OFF、FATAL、ERROR、WARN、INFO、DEBUG、TRACE。
LogBack : 同樣是出自 Ceki Gülcü 之手的成熟日誌框架,可以看做是 Log4j 的改良加強版本。
Log4j2 : 不僅僅是 Log4j 升級版本,從頭到尾被重寫了,性能有著極大的提升,感興趣的讀者可以自行搜索 Log4j2 和其他日誌框架的性能評析。
1.2、日誌門面
在阿里巴巴《Java開發手冊》中,在日誌規範的第一條中,就是禁止直接使用上面所列的日誌框架的 API,而應該使用像 SLF4J 日誌門面的 API。
上面所列的幾種常用的日誌框架,不同的框架有著不同的 API,這大大增加了代碼與日誌框架的耦合性,當需要更換日誌框架的時候,我們幾乎要把日誌框架相關的代碼重新修改一遍。
為了解決這個問題,可以通過在程序和日誌框架在搭一箇中間層實現,這就是日誌門面,通過日誌門面屏蔽日誌框架的具體實現,即使我們更改了底層日誌框架,也無需對程序進行大修改,頂多就是改改一些配置即可。
日誌門面並不涉及具體的日誌實現,還需依賴 Log4j、Logback 等日誌框架,它僅僅是對程序屏蔽了不同日誌框架的 API 差異,降低程序和日誌框架之間的耦合度。
SLF4J : Java 簡易日誌門面(Simple Logging Facade for Java)的縮寫,也是出自 Log4j 、 LogBack 的作者Ceki Gülcü 之手。支持 Java Logging API、Log4j、logback等日誌框架。根據作者的說法,SLF4J 效率更高,比起 Apache Commons Logging (JCL) 更簡單、更穩定。
Commons Logging : Apache Commons Logging (JCL) 是基於Java的日誌記錄程序。
2、實戰 Log4j2 與 SLF4J
對於 Java 程序來說,Log4j + SLF4J 這樣的組合來進行日誌輸出是個不錯的選擇,通過日誌門面屏蔽底層日誌框架的差異,即使在日後更換日誌框架也無需太多的成本。
前面提到,Log4j2 對比 Log4j 有著巨大的提升,因此這裡選擇 Log4j2 + SLF4J 的組合。實戰如何控制日誌的控制檯輸出和日誌輸出到文件中。
2.1、引用 Log4j2 依賴
Spring Boot 默認使用 Logback 作為日誌框架,默認引入了 spring-boot-starter-logging 這個依賴,修改 build.gradle(pom.xml),添加 Log4j2 依賴和排除 spring-boot-starter-logging 。
項目添加 Log4j2 依賴、排除 spring-boot-starter-logging
build.gradle
// 排除 spring-boot-starter-logging
configurations {
compile.exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'
implementation.exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'
testImplementation.exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'
}
dependencies {
// 其他依賴省略
// 引入 Log4j2
// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-log4j2
compile group: 'org.springframework.boot', name: 'spring-boot-starter-log4j2'
}
pom.xml
<dependencies>
<dependency>
<!-- 排除 spring-boot-starter-logging -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 其他依賴省略 -->
<!-- 引入 Log4j2 https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-log4j2 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
</dependencies>
刷新項目,可以發現 spring-boot-starter-logging 已經被移除和新引入了 spring-boot-starter-log4j2 。
2.2、 Log4j2 的配置
Log4j2 默認配置文件是 resources/log4j2-spring.xml ,新建Log4j2 的配置文件,如果你使用其他文件名稱,可以通過如下方式指定。
application.properties
# 指定 Log4j2 配置文件
logging.config=classpath:other-filename.xml
Log4j2-spring.xml 配置模板
<?xml version="1.0" encoding="UTF-8"?>
<!-- Log4j2 配置文件 參考 https://www.cnblogs.com/keeya/p/10101547.html -->
<!--日誌級別以及優先級排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!-- monitorInterval=“N” 自動間隔 N 秒檢測配置文件是否修改,有修改則自動重新加載配置 可以不設置 -->
<!-- status="warn" Log4j2 本身日誌輸出級別 可以不設置 -->
<configuration monitorInterval="30" status="warn">
<!-- 變量配置 -->
<Properties>
<!-- 日誌輸出格式 -->
<property name="LOG_PATTERN"
value="%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight{%-5level} [%t] %highlight{%c{1.}.%M(%L)}: %msg%n"/>
<!-- 日誌輸出到文件的路徑和文件名 根據項目情況更改 value 值 -->
<property name="LOG_FILE_PATH" value="logger"/>
<property name="LOG_FILE_NAME" value="log4j2"/>
</Properties>
<!-- 定義 appenders -->
<appenders>
<!-- console 設定 控制檯輸出 -->
<console name="Console" target="SYSTEM_OUT">
<!-- 指定 輸出格式 默認 %msg%n -->
<PatternLayout pattern="${LOG_PATTERN}"/>
<!-- onMatch="ACCEPT" 只輸出 level 級別及級別優先級更高的 Log , onMismatch="DENY" 其他拒絕輸出 -->
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
</console>
<!-- 將日誌全部輸出到 test.log,append="true" 表示重新運行時不刪除日誌 -->
<File name="FileLog" fileName="${LOG_FILE_PATH}/test.log" append="true">
<PatternLayout pattern="${LOG_PATTERN}"/>
</File>
<!-- RollingFile 滾動輸出日誌到文件 -->
<!-- 輸出 warn 及更高優先級的 log 到 LOG_FILE_PATH 目錄下的 warn.log 文件 -->
<!-- filePattern 指定 warn.log 文件大於 size 大小時候文件處理規則, %d 日期;%i 編號(最大為下方設置的 max 值) -->
<RollingFile name="RollingFileWarn" fileName="${LOG_FILE_PATH}/warn.log"
filePattern="${LOG_FILE_PATH}/%d{yyyy-MM-dd}/WARN_${LOG_FILE_NAME}_%i.log.gz">
<PatternLayout pattern="${LOG_PATTERN}" />
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
<Policies>
<!-- interval="N" ,N小時滾動一次,默認是1 hour-->
<TimeBasedTriggeringPolicy interval="1"/>
<!-- size="5MB" 指定日誌輸出文件大小,若大小超過size,則日誌會自動存入按 filePattern 規則建立的文件夾下面並進行壓縮 -->
<SizeBasedTriggeringPolicy size="5MB"/>
</Policies>
<!-- DefaultRolloverStrategy 不設置的情況下,默認為最多同一文件夾下7個 filePattern 規矩建立的壓縮文件,多於 max 的值將用新的文件覆蓋就的壓縮文件 -->
<DefaultRolloverStrategy max="10"/>
</RollingFile>
<!-- 輸出 error 及更高優先級的 log 到 LOG_FILE_PATH 目錄下的 error.log 文件 -->
<RollingFile name="RollingFileError" fileName="${LOG_FILE_PATH}/error.log"
filePattern="${LOG_FILE_PATH}/%d{yyyy-MM-dd}/ERROR_${LOG_FILE_NAME}_%i.log.gz">
<PatternLayout pattern="${LOG_PATTERN}" />
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="5MB"/>
</Policies>
<DefaultRolloverStrategy max="10"/>
</RollingFile>
<!-- 輸出 info 及更高優先級的 log 到 LOG_FILE_PATH 目錄下的 info.log 文件 -->
<RollingFile name="RollingFileInfo" fileName="${LOG_FILE_PATH}/info.log"
filePattern="${LOG_FILE_PATH}/%d{yyyy-MM-dd}/Info_${LOG_FILE_NAME}_%i.log.gz">
<PatternLayout pattern="${LOG_PATTERN}"/>
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="5MB"/>
</Policies>
<DefaultRolloverStrategy max="10"/>
</RollingFile>
</appenders>
<!-- 在 Loggers 引入 Appender 使其生效 -->
<loggers>
<!-- Logger 節點用來單獨指定 package 包下的 class 的日誌輸出格式等信息 -->
<logger name="org.springframework" level="info" additivity="false">
<!-- 指定 org.springframework 的 level 及更高優先級的日誌只在控制檯輸出 -->
<!-- additivity="false" 只在自定義的Appender中進行輸出 -->
<AppenderRef ref="Console"/>
</logger >
<Root level="info">
<!-- 用來指定項目的 Root 日誌規則,如果沒有單獨指定Logger,那麼就會默認使用 Root 日誌輸出 -->
<!-- AppenderRef 用來指定日誌輸出到哪個 Appender -->
<AppenderRef ref="Console"/>
<AppenderRef ref="FileLog"/>
<AppenderRef ref="RollingFileInfo"/>
<AppenderRef ref="RollingFileWarn"/>
<AppenderRef ref="RollingFileError"/>
</Root>
</loggers>
</configuration>
2.3、示例代碼
public class HelloSpringBoot {
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(BootApplication.class);
@RequestMapping("string")
@ResponseStatus(HttpStatus.OK)
public String helloString(){
log.trace("trace");
log.debug("debug");
log.warn("warn");
log.info("info");
log.error("error");
return "Hello Spring Boot";
}
// 其他代碼省略
}
或者 @Slf4j
@Slf4j
public class HelloSpringBoot {
@RequestMapping("string")
@ResponseStatus(HttpStatus.OK)
public String helloString(){
log.trace("trace");
log.debug("debug");
log.warn("warn");
log.info("info");
log.error("error");
return "Hello Spring Boot";
}
// 其他代碼省略
}
運行項目,通過訪問 https://localhost:8000/hello/string 可以看到如下輸出
2020-02-18 01:15:20.620 WARN [https-jsse-nio-8000-exec-10] o.x.b.HelloSpringBoot.helloString(20): warn
2020-02-18 01:15:20.620 INFO [https-jsse-nio-8000-exec-10] o.x.b.HelloSpringBoot.helloString(21): info
2020-02-18 01:15:20.621 ERROR [https-jsse-nio-8000-exec-10] o.x.b.HelloSpringBoot.helloString(22): error
2.4、日誌配置詳解
2.4.1、日誌級別
如果日誌優先級等於或者大於配置的 level 就將其輸出。如果設置為 Info,則 WARN 、ERROR 、FATAL 信息也會輸出。而 DEBUG 、TRACE 則不會。
日誌優先級排序 OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL
- TRACE : 追蹤,一般不使用,就是程序執行一步,就打個日誌
- DEBUG : 調試信息,一般作為最低級別
- INFO : 重要的提示信息
- WARN : 警告信息
- ERROR : 錯誤信息
- FATAL : 致命錯誤信息
2.4.2、 配置文件
根節點 Configuration 有兩個屬性和兩個子節點 Appender 和 Loggers
- status 指定 Log4j2 本身日誌輸出級別
- monitorInterval=“N” 自動間隔 N 秒檢測配置文件是否修改,有修改則自動重新加載配置
Appender 定義日誌輸出格式
-
Console : 輸出到控制檯
- 屬性 name : Appender 的名稱
- 屬性 target : SYSTEM_OUT 或 SYSTEM_ERR,一般設置為 SYSTEM_OUT
- 子節點 PatternLayout : 日誌輸出格式
- 子節點 ThresholdFilter : 日誌輸出級別 level
-
File : 輸出到文件
- 屬性 name : Appender 的名稱
- 屬性 fileName : 文件路徑和名稱
- 屬性 append : 重新運行項目是否保留先前的文件
- 子節點 PatternLayout : 日誌輸出格式
-
RollingFile : 滾動輸出到文件,超過某個大小時候,自動覆蓋舊日誌,建議在線上項目使用
- 屬性 name : Appender 的名稱
- 屬性 fileName : 文件路徑和名稱
- 屬性 filePattern : fileName 文件超過指定 size 時候,歸檔文件存放目錄和命名規則
- 子節點 PatternLayout : 日誌輸出格式
- 子節點 ThresholdFilter : 日誌輸出級別 level
-
子節點 Policies : 滾動輸出策略
- 子節點 : TimeBasedTriggeringPolicy 的 interval 屬性用來指定多久滾動一次,單位小時
- 子節點 : SizeBasedTriggeringPolicy 的 size 屬性指定日誌超過此大小按照 filePattern 壓縮歸檔
- 子節點 DefaultRolloverStrategy : max 屬性值指定同一文件下 filePattern 壓縮歸檔數量最大值。
Loggers
-
Logger : 單獨指定某個 package 包下的 class 輸出格式
- 屬性 name : package 的名稱
- 屬性 level:日誌級別
- 屬性 additivity : 是否只在子節點 AppenderRef 裡輸出
- 子節點 AppenderRef : 指定日誌輸出到那個 Appender
-
Root:項目 Root 日誌,如果沒有使用 Logger 單獨指定,則使用 Root 規則輸出日誌
- 屬性 level :日誌級別,如果 Appender 為指定,則使用這個
- 子節點 AppenderRef : 指定日誌輸出到那個 Appender
PatternLayout 自定義日誌輸出格式
%d{yyyy-MM-dd HH:mm:ss.SSS}: 毫秒級別的日誌產生時間
%highlight : 高亮顯示
%-5level : 日誌級別,-5表示左對齊並且固定輸出5個字符,如果不足在右邊補0
%c : 日誌名稱
%t : 線程名
%msg : 日誌內容
%n : 換行符
%C : 類名
%L : 行數
%M : 方法名
%l : 包括類名、方法名、文件名、行數
參考文檔
為什麼阿里巴巴禁止工程師直接使用日誌系統(Log4j、Logback)中的 API https://mp.weixin.qq.com/s/vCixKVXys5nTTcQQnzrs3w
https://www.cnblogs.com/keeya/p/10101547.html
SpringBoot整合log4j2日誌全解 https://www.cnblogs.com/keeya/p/10101547.html
下一節,將實戰 Spring Boot 2.X 連接 MySQL,分為 Spring Data JPA 和 MyBatis 兩大塊內容。更多內容,敬請關注《編程技術進階》。