@
之前錄過一個視訊和大家分享 Spring Boot 日誌問題,但是總感覺差點意思,因此鬆哥打算再通過一篇文章來和大家捋一捋 Java 中的日誌問題,順便我們把 Spring Boot 中的日誌問題也說清楚。
1. Java 日誌概覽
說到 Java 日誌,很多初學者可能都比較懵,因為這裡涉及到太多東西了:Apache Commons Logging
、Slf4j
、Log4j
、Log4j2
、Logback
、Java Util Logging
等等,這些框架各自有什麼作用?他們之間有什麼區別?
1.1 總體概覽
下面這張圖很好的展示了 Java 中的日誌體系:
可以看到,Java 中的日誌框架主要分為兩大類:日誌門面和日誌實現。
日誌門面
日誌門面定義了一組日誌的介面規範,它並不提供底層具體的實現邏輯。Apache Commons Logging
和 Slf4j
就屬於這一類。
日誌實現
日誌實現則是日誌具體的實現,包括日誌級別控制、日誌列印格式、日誌輸出形式(輸出到資料庫、輸出到檔案、輸出到控制檯等)。Log4j
、Log4j2
、Logback
以及 Java Util Logging
則屬於這一類。
將日誌門面和日誌實現分離其實是一種典型的門面模式,這種方式可以讓具體業務在不同的日誌實現框架之間自由切換,而不需要改動任何程式碼,開發者只需要掌握日誌門面的 API 即可。
日誌門面是不能單獨使用的,它必須和一種具體的日誌實現框架相結合使用。
那麼日誌框架是否可以單獨使用呢?
技術上來說當然沒問題,但是我們一般不會這樣做,因為這樣做可維護性很差,而且後期擴充套件不易。例如 A 開發了一個工具包使用 Log4j 列印日誌,B 引用了這個工具包,但是 B 喜歡使用 Logback 列印日誌,此時就會出現一個業務使用兩個甚至多個日誌框架,開發者也需要維護多個日誌的配置檔案。因此我們都是用日誌門面列印日誌。
1.2 日誌級別
使用日誌級別的好處在於,調整級別,就可以遮蔽掉很多除錯相關的日誌輸出。不同的日誌實現定義的日誌級別不太一樣,不過也都大同小異。
Java Util Logging
Java Util Logging
定義了 7 個日誌級別,從嚴重到普通依次是:
- SEVERE
- WARNING
- INFO
- CONFIG
- FINE
- FINER
- FINEST
因為預設級別是 INFO,因此 INFO 級別以下的日誌,不會被列印出來。
Log4j
Log4j
定義了 8 個日誌級別(除去 OFF 和 ALL,可以說分為 6 個級別),從嚴重到普通依次是:
- OFF:最高等級的,用於關閉所有日誌記錄。
- FATAL:重大錯誤,這種級別可以直接停止程式了。
- ERROR:列印錯誤和異常資訊,如果不想輸出太多的日誌,可以使用這個級別。
- WARN:警告提示。
- INFO:用於生產環境中輸出程式執行的一些重要資訊,不能濫用。
- DEBUG:用於開發過程中列印一些執行資訊。
- TRACE
- ALL 最低等級的,用於開啟所有日誌記錄。
Logback
Logback
日誌級別比較簡單,從嚴重到普通依次是:
- ERROR
- WARN
- INFO
- DEBUG
- TRACE
1.3 綜合對比
Java Util Logging
系統在 JVM
啟動時讀取配置檔案並完成初始化,一旦應用程式開始執行,就無法修改配置。另外,這種日誌實現配置也不太方便,只能在 JVM
啟動時傳遞引數,像下面這樣:
-Djava.util.logging.config.file=<config-file-name>。
由於這些侷限性,導致 Java Util Logging
並未廣泛使用。
Log4j
雖然配置繁瑣,但是一旦配置完成,使用起來就非常方便,只需要將相關的配置檔案放到 classpath
下即可。在很多情況下,Log4j
的配置檔案我們可以在不同的專案中反覆使用。
Log4j
可以和 Apache Commons Logging
搭配使用,Apache Commons Logging
會自動搜尋並使用 Log4j
,如果沒有找到 Log4j
,再使用 Java Util Logging
。
比 Log4j
+ Apache Commons Logging
組合更得人心的是 Slf4j
+ Logback
組合。
Logback
是 Slf4j
的原生實現框架,它也出自 Log4j
作者(Ceki Gülcü)之手,但是相比 Log4j
,它擁有更多的優點、特性以及更強的效能。
1.4 最佳實踐
- 如果不想新增任何依賴,使用
Java Util Logging
或框架容器已經提供的日誌介面。 - 如果比較在意效能,推薦:
Slf4j
+Logback
。 - 如果專案中已經使用了
Log4j
且沒有發現效能問題,推薦組合為:Slf4j
+Log4j2
。
2. Spring Boot 日誌實現
Spring Boot 使用 Apache Commons Logging
作為內部的日誌框架門面,它只是一個日誌介面,在實際應用中需要為該介面來指定相應的日誌實現。
Spring Boot 預設的日誌實現是 Logback
。這個很好檢視:隨便啟動一個 Spring Boot 專案,從控制檯找一行日誌,例如下面這樣:
考慮到最後的 prod 是一個可以變化的字元,我們在專案中全域性搜尋:The following profiles are active
,結果如下:
在日誌輸出的那一行 debug。然後再次啟動專案,如下圖:
此時我們就可以看到真正的日誌實現是 Logback
。
其他的諸如 Java Util Logging
、Log4j
等框架,Spring Boot 也有很好的支援。
在 Spring Boot 專案中,只要新增了如下 web 依賴,日誌依賴就自動新增進來了:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2.1 Spring Boot 日誌配置
Spring Boot 的日誌系統會自動根據 classpath 下的內容選擇合適的日誌配置,在這個過程中首選 Logback。
如果開發者需要修改日誌級別,只需要在 application.properties 檔案中通過 logging.level 字首+包名
的形式進行配置即可,例如下面這樣:
logging.level.org.springframework.web=debug
logging.level.org.hibernate=error
如果你想將日誌輸出到檔案,可以通過如下配置指定日誌檔名:
logging.file.name=javaboy.log
logging.file.name 可以只指定日誌檔名,也可以指定日誌檔案全路徑,例如下面這樣:
logging.file.name=/Users/sang/Documents/javaboy/javaboy.log
如果你只是想重新定義輸出日誌檔案的路徑,也可以使用 logging.file.path
屬性,如下:
logging.file.path=/Users/sang/Documents/javaboy
如果想對輸出到檔案中的日誌進行精細化管理,還有如下一些屬性可以配置:
- logging.logback.rollingpolicy.file-name-pattern:日誌歸檔的檔名,日誌檔案達到一定大小之後,自動進行壓縮歸檔。
- logging.logback.rollingpolicy.clean-history-on-start:是否在應用啟動時進行歸檔管理。
- logging.logback.rollingpolicy.max-file-size:日誌檔案大小上限,達到該上限後,會自動壓縮。
- logging.logback.rollingpolicy.total-size-cap:日誌檔案被刪除之前,可以容納的最大大小。
- logging.logback.rollingpolicy.max-history:日誌檔案儲存的天數。
日誌檔案歸檔這塊,小夥伴們感興趣可以自己試下,可以首先將 max-file-size 屬性調小,這樣方便看到效果:
logging.logback.rollingpolicy.max-file-size=1MB
然後新增如下介面:
@RestController
public class HelloController {
private static final Logger logger = getLogger(HelloController.class);
@GetMapping("/hello")
public void hello() {
for (int i = 0; i < 100000; i++) {
logger.info("hello javaboy");
}
}
}
訪問該介面,可以看到最終生成的日誌檔案被自動壓縮了:
application.properties 中還可以配置日誌分組。
日誌分組能夠把相關的 logger 放到一個組統一管理。
例如我們可以定義一個 tomcat 組:
logging.group.tomcat=org.apache.catalina,org.apache.coyote, org.apache.tomcat
然後統一管理 tomcat 組中的所有 logger:
logging.level.tomcat=TRACE
Spring Boot 中還預定義了兩個日誌分組 web 和 sql,如下:
不過在 application.properties 中只能實現對日誌一些非常簡單的配置,如果想實現更加細粒度的日誌配置,那就需要使用日誌實現的原生配置,例如 Logback
的 classpath:logback.xml
,Log4j
的 classpath:log4j.xml
等。如果這些日誌配置檔案存在於 classpath 下,那麼預設情況下,Spring Boot 就會自動載入這些配置檔案。
2.2 Logback 配置
2.2.1 基本配置
預設的 Logback
配置檔名有兩種:
logback.xml
:這種配置檔案會直接被日誌框架載入。logback-spring.xml
:這種配置檔案不會被日誌框架直接載入,而是由 Spring Boot 去解析日誌配置,可以使用 Spring Boot 的高階 Profile 功能。
Spring Boot 中為 Logback
提供了四個預設的配置檔案,位置在 org/springframework/boot/logging/logback/
,分別是:
- defaults.xml:提供了公共的日誌配置,日誌輸出規則等。
- console-appender.xml:使用 CONSOLE_LOG_PATTERN 新增一個ConsoleAppender。
- file-appender.xml:新增一個 RollingFileAppender。
- base.xml:為了相容舊版 Spring Boot 而提供的。
如果需要自定義 logback.xml
檔案,可以在自定義時使用這些預設的配置檔案,也可以不使用。一個典型的 logback.xml
檔案如下(resources/logback.xml):
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<include resource="org/springframework/boot/logging/logback/console-appender.xml" />
<root level="INFO">
<appender-ref ref="CONSOLE" />
</root>
<logger name="org.springframework.web" level="DEBUG"/>
</configuration>
可以通過 include 引入 Spring Boot 已經提供的配置檔案,也可以自定義。
2.2.2 輸出到檔案
如果想禁止控制檯的日誌輸出,轉而將日誌內容輸出到一個檔案,我們可以自定義一個 logback-spring.xml
檔案,並引入前面所說的 file-appender.xml
檔案。
像下面這樣(resources/logback-spring.xml
):
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}/}spring.log}"/>
<include resource="org/springframework/boot/logging/logback/file-appender.xml" />
<root level="INFO">
<appender-ref ref="FILE" />
</root>
</configuration>
2.3 Log4j 配置
如果 classpath 下存在 Log4j2
的依賴,Spring Boot 會自動進行配置。
預設情況下 classpath 下當然不存在 Log4j2
的依賴,如果想使用 Log4j2
,可以排除已有的 Logback
,然後再引入 Log4j2
,如下:
<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-log4j2</artifactId>
</dependency>
Log4j2
的配置就比較容易了,在 reources 目錄下新建 log4j2.xml 檔案,內容如下:
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="warn">
<properties>
<Property name="app_name">logging</Property>
<Property name="log_path">logs/${app_name}</Property>
</properties>
<appenders>
<console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="[%d][%t][%p][%l] %m%n" />
</console>
<RollingFile name="RollingFileInfo" fileName="${log_path}/info.log"
filePattern="${log_path}/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log.gz">
<Filters>
<ThresholdFilter level="INFO" />
<ThresholdFilter level="WARN" onMatch="DENY"
onMismatch="NEUTRAL" />
</Filters>
<PatternLayout pattern="[%d][%t][%p][%c:%L] %m%n" />
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true" />
<SizeBasedTriggeringPolicy size="2 MB" />
</Policies>
<DefaultRolloverStrategy compressionLevel="0" max="10"/>
</RollingFile>
<RollingFile name="RollingFileWarn" fileName="${log_path}/warn.log"
filePattern="${log_path}/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log.gz">
<Filters>
<ThresholdFilter level="WARN" />
<ThresholdFilter level="ERROR" onMatch="DENY"
onMismatch="NEUTRAL" />
</Filters>
<PatternLayout pattern="[%d][%t][%p][%c:%L] %m%n" />
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true" />
<SizeBasedTriggeringPolicy size="2 MB" />
</Policies>
<DefaultRolloverStrategy compressionLevel="0" max="10"/>
</RollingFile>
<RollingFile name="RollingFileError" fileName="${log_path}/error.log"
filePattern="${log_path}/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log.gz">
<ThresholdFilter level="ERROR" />
<PatternLayout pattern="[%d][%t][%p][%c:%L] %m%n" />
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true" />
<SizeBasedTriggeringPolicy size="2 MB" />
</Policies>
<DefaultRolloverStrategy compressionLevel="0" max="10"/>
</RollingFile>
</appenders>
<loggers>
<root level="info">
<appender-ref ref="Console" />
<appender-ref ref="RollingFileInfo" />
<appender-ref ref="RollingFileWarn" />
<appender-ref ref="RollingFileError" />
</root>
</loggers>
</configuration>
首先在 properties 節點中指定了應用名稱以及日誌檔案位置。
然後通過幾個不同的 RollingFile 對不同級別的日誌分別處理,不同級別的日誌將輸出到不同的檔案,並按照各自的命名方式進行壓縮。
這段配置比較程式化,小夥伴們可以儲存下來做成 IntelliJ IDEA 模版以便日常使用。
3.小結
好啦,這就是鬆哥和小夥伴們分享的 Spring Boot 日誌了,整體來說並不難,小夥伴們可以仔細品一品。
最後,鬆哥還蒐集了 50+ 個專案需求文件,想做個專案練練手的小夥伴不妨看看哦~