最近在看程式碼的過程中,發現身邊的許多人在使用Java紀錄檔框架時,對於應該引入何種依賴不甚瞭解,搜尋網路上的文章,常常也是互不一致。這篇文章可以看著是Java紀錄檔框架的入門使用和實踐建議,重點介紹不同組合方式下的依賴設定及其背後的邏輯,一方面給自己備查,另外也希望對小夥伴們有所幫助。
Java紀錄檔框架家族繁雜,出於實用的原則,這裡主要介紹主流的幾個專案:SLF4J、Logback、Log4j 2,以及它們之間各種搭配用法和使用建議。
另外,由於Log4j 1專案已經在2015-08-05正式宣佈死亡(最終版本停留在2012-05-13釋出的1.2.17),因此這裡也不再討論Log4j 1,下文所有提到Log4j的地方,都是指Log4j 2。
對於紀錄檔框架,可以按照「分層」的概念來理解:介面層、實現層。開發者在使用紀錄檔框架時,建議基於介面層而非實現層進行開發,這樣的好處是,避免專案與某一個具體紀錄檔框架耦合,這是一個常見的程式設計理念,應該比較容易理解。
例如,專案最初使用SLF4J作為介面層,使用Logback作為實現層,而你的專案程式碼中使用的也是介面層的類org.slf4j.Logger
,這種情況下,當將來你想將實現層切換為Log4j時,你最需要改動依賴項,而不需要改動程式碼。
但是,如果你最初的專案程式碼中使用的並非是介面層的類,而是實現層(即Logback)的類ch.qos.logback.classic.Logger
(這可能是因為手滑,畢竟類名都是一樣的)。這種情況下,想要切換實現層,就需要改動所有涉及使用到這個類的程式碼。
由於logback-classic
中既有實現層,也包含了對介面層SLF4J的依賴,因此,最簡單的設定可以是這樣的:
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.12</version>
</dependency>
不過,就像簡述裡說的,為了避免開發者不小心誤用實現類ch.qos.logback.classic.Logger
,推薦使用如下的依賴設定,注意其中的scope設定:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.32</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.12</version>
<scope>runtime</scope>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
這裡給出一個最常見的組態檔,包含控制檯輸出、捲動檔案輸出:
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="ROLLING_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>logs/app-%d{yyyy-MM-dd-HH}-%i.log</fileNamePattern>
<!-- 單個紀錄檔檔案超過10M,則進行卷動,對檔案進行遞增編號(即%i) -->
<maxFileSize>10MB</maxFileSize>
<!-- 所有紀錄檔檔案的大小限制,超出則刪除舊檔案 -->
<totalSizeCap>5GB</totalSizeCap>
<!-- 與fileNamePattern相結合,本例中由於時間粒度是小時,因此這裡表示儲存48個小時 -->
<maxHistory>48</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="ROLLING_FILE" />
</root>
</configuration>
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
...
private static final Logger logger = LoggerFactory.getLogger(App.class);
...
logger.info("First name: {}, last name: {}", firstName, lastName);
由於log4j-slf4j-impl中既有實現層,也包含了對介面層SLF4J的依賴,因此,最簡單的設定可以是這樣的:
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.20.0</version>
</dependency>
不過,基於與上一節同樣的邏輯,推薦使用下面的設定:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.20.0</version>
<scope>runtime</scope>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO"> <!-- log4j internal log level -->
<Appenders>
<Console name="CONSOLE" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
<RollingFile name="ROLLING_FILE"
fileName="logs/log4j2/roll-by-time-and-size/app.log"
filePattern="logs/log4j2/roll-by-time-and-size/app-%d{yyyy-MM-dd-HH}-%i.log"
ignoreExceptions="false">
<PatternLayout>
<Pattern>%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n</Pattern>
</PatternLayout>
<Policies>
<!-- 啟動時,會刪除多餘的紀錄檔檔案 -->
<OnStartupTriggeringPolicy/>
<!-- 自動感知filePattern中的時間設定,本例中是按小時粒度進行卷動 -->
<TimeBasedTriggeringPolicy/>
<!-- 單個紀錄檔檔案超過10M,則進行卷動,遞增編號(即filePattern中的%i) -->
<SizeBasedTriggeringPolicy size="10M"/>
</Policies>
<!-- max設定與上面的filePattern結合,由於本例中是按小時粒度進行卷動,因此這裡表示每小時內最多產生五個編號檔案,超出這回圈覆蓋,如不設定max,則預設為7 -->
<DefaultRolloverStrategy max="5">
<Delete basePath="logs" maxDepth="1">
<!-- 最近30天,最多5GB的紀錄檔 -->
<IfFileName glob="app-*.log">
<IfAny>
<IfLastModified age="30d"/>
<IfAccumulatedFileSize exceeds="5GB"/>
</IfAny>
</IfFileName>
</Delete>
</DefaultRolloverStrategy>
</RollingFile>
</Appenders>
<Loggers>
<Root level="warn">
<AppenderRef ref="CONSOLE"/>
<AppenderRef ref="ROLLING_FILE"/>
</Root>
</Loggers>
</Configuration>
由於與上例相同,都是基於SLF4J介面層,因此使用方式相同:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
...
private static final Logger logger = LoggerFactory.getLogger(App.class);
...
logger.info("First name: {}, last name: {}", firstName, lastName);
一般我們會基於SLF4J介面層進行開發,但是如果你硬要單獨使用Log4j,也不是不可以。
最簡單的,我們可以使用以下設定:
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.20.0</version>
</dependency>
不過,由於Log4j自身也分了介面層和實現層,推薦使用如下設定:
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.20.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.20.0</version>
<scope>runtime</scope>
<exclusions>
<exclusion>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
(同上)
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
...
private static final Logger logger = LogManager.getLogger(App.class);
...
logger.info("First name: {}, last name: {}", firstName, lastName);
有人可能會說,Log4j自身拆成了介面層和實現層,是不是意味著,使用Log4j介面層的情況下,實現層還能使用別的紀錄檔系統?是的,例如可以使用「Log4j介面層 + Logback」的搭配:
<!-- 1) Log4j介面層 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.20.0</version>
</dependency>
<!-- 2) Log4j專案提供的「橋接層」,將Log4j介面層橋接到SLF4J介面層,由於Logback是基於SLF4J,因此經過橋接之後,就可以使用Logback作為實現層 -->
<!-- 注:log4j-to-slf4j含有對log4j-api的依賴,因此上面可以不用單獨列出log4j-api依賴,不過,為了邏輯清晰,還是保留 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
<version>2.20.0</version>
</dependency>
<!-- 3) Logback實現層 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.12</version>
<scope>runtime</scope>
</dependency>
如果你在開發一個玩具專案,對於紀錄檔框架的選擇和使用當然可以比較隨意,但是,如果是開發一個正經的專案,尤其是你的專案將作為公眾可用的第三方庫時,遵循最佳實踐、保持靈活性則是非常必要的,因為你不知道使用方希望在他的專案中使用什麼紀錄檔框架。
另外,我曾經作為面試官的時候,也常常詢問面試者如何設定紀錄檔框架的依賴,這是一個很簡單的題目,不過,一樣可以考察對方几個知識點,包括紀錄檔框架、解耦、Maven中的scope設定等,總之,這是一個不錯的考察程式設計常識的點。
關注作者:歡迎掃碼關注公眾號「後廠村思維導圖館」,獲取本人自建的免費ChatGPT跳板地址,長期有效。 原文連結:https://www.cnblogs.com/morvenhuang/p/17658961.html 版權宣告:本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須在文章頁面給出原文連結,否則保留追究法律責任的權利。 |