帶你玩轉(zhuǎn)標(biāo)準(zhǔn)的 SpringBoot 日志
一、日志重要嗎
程序中的日志重要嗎?在回答這個(gè)問題前,筆者先說個(gè)事例:
?筆者印象尤深的就是去年某個(gè)同事,收到了客戶反饋的緊急bug。盡管申請(qǐng)到了日志文件,但因?yàn)楹芏嚓P(guān)鍵步驟沒有打印日志,導(dǎo)致排查進(jìn)度很慢,數(shù)個(gè)小時(shí)都沒能排查到問題,也無法給出解決對(duì)策。導(dǎo)致了客戶程序一直阻斷,最終產(chǎn)生了不少損失。事后,經(jīng)過仔細(xì)推敲,成功復(fù)現(xiàn)了這個(gè)bug,其實(shí)是一個(gè)很不起眼的數(shù)據(jù)轉(zhuǎn)換導(dǎo)致的。可因?yàn)槿罩緝?nèi)容的匱乏,排查起來難度很大。其實(shí)只要在數(shù)據(jù)轉(zhuǎn)換前后進(jìn)行日志輸出,這個(gè)問題就是一眼的事。但可惜沒如果,故事的最后,開發(fā)部門還是遭到了客戶的投訴,影響到了部門績(jī)效
?
對(duì)于剛學(xué)習(xí)編程的同學(xué),很多人都對(duì)日志滿不在乎,我們?cè)谧鯿ode review的時(shí)候,經(jīng)常發(fā)現(xiàn)一些新同學(xué)喜歡一個(gè)方法寫得很長(zhǎng),然后中間的注釋和日志都少的可憐。
坦白的說,這是很不好的習(xí)慣,這意味著日后方法出了bug,或者需要迭代,要花費(fèi)大量時(shí)間來理清方法的思路。千萬(wàn)別迷信什么“方法名、字段名起的見明知意,就可以不寫注釋與日志”,那是他們的業(yè)務(wù)場(chǎng)景不夠復(fù)雜。以筆者為例,復(fù)雜的場(chǎng)景涉及很多公式、奇特的規(guī)定,不寫注釋與日志,后續(xù)沒人能維護(hù)得了
所以請(qǐng)務(wù)必記住,日志在開發(fā)過程中非常重要。它可以幫助開發(fā)人員了解程序中發(fā)生了什么,以及在某些情況下為什么會(huì)發(fā)生錯(cuò)誤或異常。通過查看日志,開發(fā)人員可以輕松地定位并解決問題,并且可以更好地監(jiān)控和調(diào)整應(yīng)用程序的性能,在必要時(shí)進(jìn)行故障排除和安全檢查
二、日志分級(jí)
最開始的日志分級(jí)是由Syslog的開發(fā)者Eric Allman在1981年提出的。之后,這個(gè)級(jí)別分級(jí)系統(tǒng)被廣泛應(yīng)用于各種領(lǐng)域的日志記錄和信息處理中。下面我們就來介紹下常用的日志等級(jí)
-
TRACE
是最低級(jí)別的日志記錄,用于輸出最詳細(xì)的調(diào)試信息,通常用于開發(fā)調(diào)試目的。在生產(chǎn)環(huán)境中,應(yīng)該關(guān)閉 TRACE 級(jí)別的日志記錄,以避免輸出過多無用信息。
-
DEBUG
是用于輸出程序中的一些調(diào)試信息,通常用于開發(fā)過程中。像 TRACE 一樣,在生產(chǎn)環(huán)境中應(yīng)該關(guān)閉 DEBUG 級(jí)別的日志記錄。
-
INFO
用于輸出程序正常運(yùn)行時(shí)的一些關(guān)鍵信息,比如程序的啟動(dòng)、運(yùn)行日志等。通常在生產(chǎn)環(huán)境中開啟 INFO 級(jí)別的日志記錄。
-
WARN
是用于輸出一些警告信息,提示程序可能會(huì)出現(xiàn)一些異常或者錯(cuò)誤。在應(yīng)用程序中,WARN 級(jí)別的日志記錄通常用于記錄一些非致命性異常信息,以便能夠及時(shí)發(fā)現(xiàn)并處理這些問題。
-
ERROR
是用于輸出程序運(yùn)行時(shí)的一些錯(cuò)誤信息,通常表示程序出現(xiàn)了一些不可預(yù)料的錯(cuò)誤。在應(yīng)用程序中,ERROR 級(jí)別的日志記錄通常用于記錄一些致命性的異常信息,以便能夠及時(shí)發(fā)現(xiàn)并處理這些問題。
當(dāng)然,除了這五種級(jí)別以外,還有一些日志框架定義了其他級(jí)別,例如 Python 中的 CRITICAL、PHP 中的 FATAL 等。CRITICAL 和 FATAL 都是用于表示程序出現(xiàn)了致命性錯(cuò)誤或者異常,即不可恢復(fù)的錯(cuò)誤。當(dāng)然,對(duì)于我們今天要說的內(nèi)容,知道上述五種日志等級(jí)就夠了。
三、常用日志插件
-
Log4j(1999年誕生)
Log4j 是Java領(lǐng)域中最早的流行日志框架之一。它由Ceki Gülcü開發(fā),并后來由Apache軟件基金會(huì)接管。Log4j 提供了靈活的配置選項(xiàng)、多種輸出目的地、日志級(jí)別和分層日志體系。盡管Log4j 1在其時(shí)代取得了巨大的成功,但在性能和某些功能方面存在限制,因此后來演化為L(zhǎng)og4j 2。
-
SLF4J(2004年誕生)
嚴(yán)格來說,SLF4J(Simple Logging Facade for Java)并不算一個(gè)插件,而是Ceki Gülcü開發(fā)的一個(gè)日志門面接口。它為Java應(yīng)用程序提供了統(tǒng)一的日志抽象,使開發(fā)人員可以使用一致的API進(jìn)行日志記錄,而不需要直接依賴于特定的日志實(shí)現(xiàn)。SLF4J 可以與多種底層日志框架(如Logback、Log4j 2、java.util.logging等)結(jié)合使用。
-
Logback(2009年誕生)
Logback 是Ceki Gülcü開發(fā)的日志框架,他也是Log4j的作者。Logback 是Log4j 1的后續(xù)版本,旨在提供更高性能、更靈活的配置和現(xiàn)代化的日志解決方案。Logback 支持異步日志記錄、多種輸出格式、靈活的配置以及與SLF4J緊密集成。
-
Log4j 2(2014年誕生)
Log4j 2 是Apache軟件基金會(huì)開發(fā)的Log4j的下一代版本。它引入了許多新特性,如異步日志記錄、插件支持、豐富的過濾器等,旨在提供更好的性能和靈活性。Log4j 2 在設(shè)計(jì)上考慮了Log4j 1的局限性,并且支持多種配置方式。
-
小故事
不難注意到,一個(gè)有意思的小故事是,前三款日志插件都是Ceki Gülcü開發(fā)的,但 Log4j 2 并不是,雖然現(xiàn)在有很多人以為log4j2也是他寫的,但我們?cè)趃ithub上可以看到其個(gè)人說明 “Unaffiliated with log4j 2.x.” (與 log4j 2.x 無關(guān)),所以log4j2 和 logback 都自稱是log4j 的后續(xù)版本,到底誰(shuí)才算正統(tǒng)續(xù)作呢?這就留給各位讀者自己玩味了
四、外觀模式與SLF4J
在講解更多插件詳情之前,我們先來看看使用最多的SLF4J ,我們前面說了 SLF4J(Simple Logging Facade for Java)是Ceki Gülcü開發(fā)的一個(gè)日志門面接口,那么很顯然這里就用到了門面模式(即Facade 或 外觀模式),筆者比較習(xí)慣說成是外觀模式,后續(xù)就稱為外觀模式。
1. 外觀模式
定義:外觀模式是一種結(jié)構(gòu)型設(shè)計(jì)模式,它提供了一個(gè)簡(jiǎn)單的接口,封裝了底層復(fù)雜的子系統(tǒng),使得客戶端可以更方便地使用這個(gè)子系統(tǒng)
目的:外觀模式的目的是隱藏底層系統(tǒng)的復(fù)雜性,降低訪問成本。
如果說看定義有些抽象,那我們可以以生活中的例子來說,我們都知道現(xiàn)在越來越流行智能家居,也就是家庭內(nèi)裝了很多智能家電,從電視、空調(diào)、到廊燈甚至窗簾都是智能的。這類家庭往往會(huì)有一個(gè)控制中心,我們不需要手動(dòng)去開電視,只需要對(duì)著控制中心說:“小A小A,幫我打開電視,音量調(diào)到30%”,電視就會(huì)應(yīng)聲打開并調(diào)節(jié)音量
那么這樣的話,我們不需要知道怎么開電視,怎么調(diào)音量。通通都能用最簡(jiǎn)單的話語(yǔ)來調(diào)節(jié)。同理,現(xiàn)在手機(jī)上的拍照功能:感光度,對(duì)焦,白平衡這些細(xì)節(jié)都給你自動(dòng)完成了,所以這些復(fù)雜的內(nèi)容你現(xiàn)在根本不用管,只需要猛按拍照鍵即可。
這就是外觀模式的意義,外觀模式就是為了隱藏系統(tǒng)的復(fù)雜性而設(shè)計(jì)出來的,讓客戶端只對(duì)接觸到一個(gè)外觀類,而不會(huì)接觸到系統(tǒng)內(nèi)部的復(fù)雜邏輯
2. SLF4J 的誕生
在早期使用日志框架時(shí),應(yīng)用程序通常需要直接與具體的日志框架進(jìn)行耦合,這就導(dǎo)致了以下幾個(gè)問題:
-
代碼依賴性
應(yīng)用程序需要直接引用具體的日志框架,從而導(dǎo)致代碼與日志框架強(qiáng)耦合,難以滿足應(yīng)用程序?qū)θ罩究蚣艿撵`活配置。
-
日志框架不統(tǒng)一
在使用不同的日志框架時(shí),應(yīng)用程序需要根據(jù)具體的日志框架來編寫代碼,這不僅會(huì)增加開發(fā)難度,而且在多種日志框架中切換時(shí)需要進(jìn)行大量的代碼改動(dòng)。
-
性能問題
在日志輸出頻繁的情況下,由于日志框架的實(shí)現(xiàn)方式和API設(shè)計(jì)不同,可能會(huì)導(dǎo)致性能問題。
為了解決這些問題,SLF4J提供了一套通用的日志門面接口,讓應(yīng)用程序可以通過這些接口來記錄日志信息,而不需要直接引用具體的日志框架。這樣,應(yīng)用程序就可以在不同的日志框架之間進(jìn)行靈活配置和切換,同時(shí)還可以獲得更好的性能表現(xiàn)。所以,我強(qiáng)烈建議各位使用SLF4J, 而不是直接對(duì)接某個(gè)具體的日志框架。
3. SLF4J 的使用
首先,我們需要在工程內(nèi)引入包,但是如果你用了springboot,各種 spring-boot-starter 啟動(dòng)器已經(jīng)引用過了,所以引用前最好確認(rèn)下:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.32</version>
</dependency>
然后在我們要打印日志的類里加上一行 ;private static final Logger logger = LoggerFactory.getLogger(XXXX.class); 即可使用,如下:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyClass {
private static final Logger log = LoggerFactory.getLogger(MyClass.class);
//...
public static void main(String[] args) {
log.info("This is an info message.");
}
}
如果我們引用了lombok的話,也可以使用lombok的注解@Slf4j 代替上面那句話來使用 SLF4J ,如下:
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class MyClass {
public static void main(String[] args) {
log.info("This is an info message.");
}
}
但是,我們都知道SLF4J僅僅是個(gè)門面,換句話說,僅有接口而沒有實(shí)現(xiàn),如果此刻我們直接運(yùn)行,打印日志是沒有用處的
所以,我們?nèi)绻\(yùn)行,我們必須要給 SLF4J 安排上實(shí)現(xiàn),而目前最常用的就是 logback 和 log4j2 了,就讓我們接著往下看
五、雙雄之爭(zhēng)
其實(shí)關(guān)于 Logback 和 Log4j 2,網(wǎng)絡(luò)上有很多評(píng)測(cè),就不需贅述了,主要是圍繞性能方面的,從目前大家的反饋看,Log4j 2 晚出現(xiàn)好幾年,還是有后發(fā)優(yōu)勢(shì)的,性能會(huì)比 Logback 好。當(dāng)然, Logback 本身性能也很強(qiáng),對(duì)于大多數(shù)場(chǎng)景,完全是夠用的,而且配置比較直觀,是spring-boot 默認(rèn)使用的日志插件。
所以,選誰(shuí)都可以,如果不想費(fèi)神,可以直接使用spring-boot自帶的Logback,如果對(duì)日志性能要求很高,使用log4j2更保險(xiǎn),我們接下來分別介紹兩者。
1. Logback
1. 引用
由于 Logback 為 spring-boot 默認(rèn)日志框架,所以無需再引用,但對(duì)于非spring - boot 項(xiàng)目,可以做如下引用
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.12</version>
</dependency>
Logback 的核心模塊為 logback-classic,它提供了一個(gè) SLF4J 的實(shí)現(xiàn),兼容 Log4j API,可以無縫地替換 Log4j。它自身已經(jīng)包含了 logback-core 模塊,而 logback-core,顧名思義就是 logback 的核心功能,包括日志記錄器、Appender、Layout 等。其他 logback 模塊都依賴于該模塊
2. 配置
logback 可以通過 XML 或者 Groovy 配置。下面以 XML 配置為例。logback 的 XML 配置文件名稱通常為 logback.xml 或者 logback-spring.xml(在 Spring Boot 中),需要放置在 classpath 的根目錄下,
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--定義日志文件的存儲(chǔ)地址,使用Spring的屬性文件配置方式-->
<springProperty scope="context" name="log.home" source="log.home" defaultValue="logs"/>
<!--定義日志文件的路徑-->
<property name="LOG_PATH" value="${log.home}"/>
<!--定義控制臺(tái)輸出-->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%-5relative [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
<!--定義 INFO 及以上級(jí)別信息輸出到控制臺(tái)-->
<root level="INFO">
<appender-ref ref="console"/>
</root>
<!--定義所有組件的日志級(jí)別,如所有 DEBUG-->
<logger name="com.example" level="DEBUG"/>
<!-- date 格式定義 -->
<property name="LOG_DATEFORMAT" value="yyyy-MM-dd"/>
<!-- 定義日志歸檔文件名稱格式,每天生成一個(gè)日志文件 -->
<property name="ARCHIVE_PATTERN" value="${LOG_PATH}/%d{${LOG_DATEFORMAT}}/app-%d{${LOG_DATEFORMAT}}-%i.log.gz"/>
<!--定義文件輸出,會(huì)根據(jù)定義的閾值進(jìn)行切割,支持自動(dòng)歸檔壓縮過期日志-->
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--定義日志文件切割的閾值,本例是 50MB-->
<maxFileSize>50MB</maxFileSize>
<!--定義日志文件保留時(shí)間,本例是每天生成一個(gè)日志文件-->
<fileNamePattern>${ARCHIVE_PATTERN}</fileNamePattern>
<maxHistory>30</maxHistory>
<!-- zip 壓縮生成的歸檔文件 -->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>50MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!-- 刪除過期文件 -->
<cleanHistoryOnStart>true</cleanHistoryOnStart>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
<!--定義 ERROR 級(jí)別以上信息輸出到文件-->
<logger name="com.example.demo" level="ERROR" additivity="false">
<appender-ref ref="file"/>
</logger>
<!--異步輸出日志信息-->
<appender name="asyncFile" class="ch.qos.logback.classic.AsyncAppender">
<discardingThreshold>0</discardingThreshold>
<queueSize>256</queueSize>
<appender-ref ref="file"/>
</appender>
<!--定義INFO及以上級(jí)別信息異步輸出到文件-->
<logger name="com.example" level="INFO" additivity="false">
<appender-ref ref="asyncFile"/>
</logger>
</configuration>
其中,主要包括以下配置:
-
springProperty 定義了 log 文件的存儲(chǔ)路徑,可以通過 Spring 的屬性文件配置方式進(jìn)行設(shè)置,如果沒有配置則默認(rèn)存儲(chǔ)在 logs 目錄下。 -
appender 定義了日志輸出的目標(biāo),這里包括了控制臺(tái)輸出和文件輸出兩種,具體可以根據(jù)需求進(jìn)行配置。 -
root 定義了默認(rèn)的日志級(jí)別和輸出目標(biāo),默認(rèn)情況下,INFO 級(jí)別以上的日志信息會(huì)輸出到控制臺(tái),可以根據(jù)實(shí)際需求進(jìn)行修改。 -
logger 定義了不同組件的日志級(jí)別和輸出目標(biāo),例如,這里定義了 com.example 這個(gè)組件的日志級(jí)別為 DEBUG,而 com.example.demo 這個(gè)組件的日志級(jí)別為 ERROR,并將其輸出到文件中。 -
rollingPolicy 定義了日志文件的切割規(guī)則和歸檔策略,此處定義了日志文件每個(gè) 50MB 進(jìn)行切割,每天生成一個(gè)日志文件,并且壓縮和刪除過期文件,最多保留 30 天的日志文件。 -
encoder 定義了日志信息的輸出格式,具體的格式可以自行定義。 -
asyncAppender 定義了異步輸出日志的方式,對(duì)于高并發(fā)時(shí),可以使用異步輸出來提高系統(tǒng)的性能。 -
discardingThreshold 定義了異步輸出隊(duì)列的閾值,當(dāng)隊(duì)列中的數(shù)據(jù)量超過此值時(shí),會(huì)丟棄最早放入的數(shù)據(jù),此處設(shè)置為 0 表示隊(duì)列不會(huì)丟棄任何數(shù)據(jù)。 -
queueSize 定義了異步輸出隊(duì)列的大小,當(dāng)隊(duì)列滿時(shí),會(huì)等待隊(duì)列中的數(shù)據(jù)被消費(fèi)后再將數(shù)據(jù)放入隊(duì)列中,此處設(shè)置為 256。
3. 演示
我們新建一個(gè)普通工程(非spring工程),引用Logback后,把上述配置文件復(fù)制進(jìn)logback.xml,然后將工程結(jié)構(gòu)設(shè)置成如下模式
其中兩個(gè)類的代碼如下:
public class Main {
private static final Logger log = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) {
log.trace("This is a Main trace message.");
log.debug("This is a Main debug message.");
log.info("This is a Main info message.");
log.warn("This is a Main warn message.");
log.error("This is a Main error message.");
Slave.main(args);
}
}
public class Slave {
private static final Logger log = LoggerFactory.getLogger(Slave.class);
public static void main(String[] args) {
log.trace("This is a Slave trace message.");
log.debug("This is a Slave debug message.");
log.info("This is a Slave info message.");
log.warn("This is a Slave warn message.");
log.error("This is a Slave error message.");
}
}
我們想實(shí)現(xiàn)這樣的效果,首先日志要同時(shí) 輸出到控制臺(tái) 及 日志文件,且不同層級(jí)的代碼,輸出的日志層級(jí)也不同。那么我們可以對(duì)上述的xml做出一些調(diào)整:
因?yàn)槭欠荢pring項(xiàng)目,所以 springProperty 這樣的標(biāo)簽就不要用了,我們直接寫死一個(gè)日志文件地址即可。
<!--定義日志文件的路徑-->
<property name="LOG_PATH" value="D://logs/log"/>
去掉原有的那些root、logger標(biāo)簽,我們自己新建兩個(gè)logger,用于兩個(gè)不同的層級(jí)。我們想里層輸出 debug 級(jí)別,外層輸出info 級(jí)別,我們可以這么設(shè)置。并且同時(shí)輸出到控制臺(tái)及日志文件
<logger name="com.zhanfu" level="INFO">
<appender-ref ref="file" />
<appender-ref ref="console"/>
</logger>
<logger name="com.zhanfu.child" level="DEBUG" additivity="false">
<appender-ref ref="file" />
<appender-ref ref="console"/>
</logger>
當(dāng)我們運(yùn)行Main.main的時(shí)候,就可以得到以下日志,slave 能輸出debug級(jí)別,Main 只能輸出 info及以上級(jí)別
4. 細(xì)節(jié)點(diǎn)
其實(shí)我們上面的演示,有兩個(gè)細(xì)節(jié)點(diǎn),需要注意一下。一個(gè)就是我們的
<logger name="com.zhanfu.child" level="DEBUG" additivity="false">
使用了一個(gè) additivity="false" 的屬性,這其實(shí)是因?yàn)?logger 這個(gè)標(biāo)簽在鎖定某個(gè)目錄時(shí),可能會(huì)發(fā)生層級(jí)關(guān)系。比如我們的兩個(gè) logger, 一個(gè)針對(duì)的目錄是 com.zhanfu 另一個(gè)是 com.zhanfu.child ,后者就會(huì)被前者包含。
當(dāng)我們的 com.zhanfu.child.Slave 打印日志時(shí),當(dāng)然會(huì)使用后者(更精確)的設(shè)置,但前者的設(shè)置還使用嗎?就依賴于 additivity=“false”,此處如果我們把 additivity="false" (該屬性默認(rèn)值為true)去掉,再來打印日志
就會(huì)發(fā)現(xiàn),Slave 的日志打了兩遍,而且連 debug 級(jí)別的都打了兩遍,我們可以把這種邏輯理解為繼承,子類執(zhí)行一遍,父類還能在執(zhí)行一遍,但 leve 屬性還是會(huì)采用子類而非父類的。
另一點(diǎn)就是我們把 root 標(biāo)簽刪除了,root 其實(shí)是一個(gè)頂級(jí)的 logger , 其他的logger都可以視為它的子類,如果那些logger存在沒涵蓋的地方,或其沒有指定 additivity="false" ,那最后root的設(shè)置就會(huì)被使用。比如我們將設(shè)置改為如下:
<logger name="com.zhanfu" level="INFO">
<appender-ref ref="file" />
<appender-ref ref="console"/>
</logger>
<logger name="com.zhanfu.child" level="DEBUG">
<appender-ref ref="file" />
<appender-ref ref="console"/>
</logger>
<root level="WARN">
<appender-ref ref="console"/>
</root>
結(jié)果控制臺(tái)的輸出日志,Main會(huì)重復(fù)兩次,Slave 會(huì)重復(fù)三次,如下
但是因?yàn)槲覀兊?root 只配置了控制臺(tái)輸出,所以日志文件里還是不會(huì)變的
2. Log4j 2
1. 引用
對(duì)于spring-boot項(xiàng)目,除了引用 Log4j 2 我們還需要先剔除 Logback 的引用,對(duì)于普通項(xiàng)目,我們只需直接引用即可。但注意我們的原則,通過 SLF4J 來使用 Log4j2,所以引用下面這個(gè)包
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.13.3</version>
</dependency>
其內(nèi)包含 Log4j2 的實(shí)現(xiàn),和 SLF4J 的 API,如下:
2. 配置
Log4j2 的配置邏輯和 logback 是類似的,只有些細(xì)節(jié)不同,比如Logger 的首字母大寫等等,最后我們寫下這樣一個(gè) log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO" monitorInterval="30">
<Properties>
<Property name="logPath">logs</Property>
</Properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{ISO8601} [%t] %-5level %logger{36} - %msg%n" />
</Console>
<RollingFile name="File" fileName="${logPath}/example.log"
filePattern="${logPath}/example-%d{yyyy-MM-dd}-%i.log">
<PatternLayout pattern="%d{ISO8601} [%t] %-5level %logger{36} - %msg%n"/>
<Policies>
<SizeBasedTriggeringPolicy size="10 MB"/>
</Policies>
<DefaultRolloverStrategy max="4"/>
</RollingFile>
</Appenders>
<Loggers>
<Logger name="com.zhanfu.child" level="DEBUG">
<AppenderRef ref="File"/>
<AppenderRef ref="Console"/>
</Logger>
<Logger name="com.zhanfu" level="INFO">
<AppenderRef ref="File"/>
<AppenderRef ref="Console"/>
</Logger>
<Root level="WARN">
<AppenderRef ref="Console" />
</Root>
</Loggers>
</Configuration>
-
Properties
部分定義了一個(gè) logPath 屬性,方便在其他地方引用。
-
Appenders
定義了兩個(gè) Appender:Console 和 RollingFile,分別將日志輸出到控制臺(tái)和文件中。RollingFile 使用了 RollingFileAppender,并設(shè)置了日志滾動(dòng)策略和默認(rèn)的備份文件數(shù)量。
-
Loggers
定義了三個(gè) Logger:com.zhanfu.child 的日志級(jí)別為 DEBUG,com.zhanfu 的日志級(jí)別為INFO,Root Logger 的日志級(jí)別為 WARN。并指定了兩個(gè) Appender:Console 和 File。
3. 演示
由于我們的配置邏輯沒變,所以日志的結(jié)果還是一樣的:
3. 對(duì)比
Log4j2和Logback都是Java應(yīng)用程序中最流行的日志框架之一。它們均具備高度的可配置性和使用靈活性,并提供了一系列有用的功能,例如異步日志記錄和日志過濾等。下面從配置遍歷性、功能性、性能等方面進(jìn)行比較和總結(jié)。
配置遍歷性
Logback的配置文件格式相對(duì)簡(jiǎn)單,易于閱讀和修改。它支持符號(hào)來引用變量、屬性和環(huán)境變量等。此外,它還支持條件日志記錄(根據(jù)日志級(jí)別、日志記錄器名稱或時(shí)間等),以及滾動(dòng)文件的大小或日期等。
Log4j2的配置文件格式較復(fù)雜,但它在配置文件中提供了大量的選項(xiàng)來控制日志記錄。它支持在配置文件中直接聲明上下文參數(shù)、過濾器、輸出器和Appender等,這使得它的配置更加靈活。此外,Log4j2還支持異步日志記錄、日志事件序列化和性能優(yōu)化等。
總體來說,兩者都很好地支持了配置遍歷性,但Log4j2提供了更多的選項(xiàng)和更高的靈活性。
功能性
Logback提供了一系列基本的日志記錄功能,例如異步Appender、滾動(dòng)文件和GZIP壓縮等。它還支持與SLF4J一起使用,可以很容易地與其他日志框架集成。
Log4j2提供了更多的高級(jí)功能,例如異步日志記錄、性能優(yōu)化和日志事件序列化等。它還支持Lambda表達(dá)式,可以使日志記錄器更加簡(jiǎn)潔和易讀。此外,Log4j2還支持Flume和Kafka等大數(shù)據(jù)處理框架,可以方便地將日志記錄發(fā)送到這些框架中。
總體來說,Log4j2提供了更多的高級(jí)功能,并且可以更好地與大數(shù)據(jù)處理框架集成。
性能
Logback的性能很好,可以處理高吞吐量的日志記錄。它采用了異步記錄器,利用了多線程來提高性能。
Log4j2在性能方面更加強(qiáng)大。它使用了異步記錄器和多線程,還引入了RingBuffer數(shù)據(jù)結(jié)構(gòu)和Disruptor庫(kù)來加速日志事件的傳遞和處理。這使得它比Logback具有更高的吞吐量和更低的延遲。
綜上所述,Log4j2在配置靈活性、功能性和性能方面都比Logback更為強(qiáng)大。但如果需要輕量級(jí)的日志框架或者只需要基本的日志記錄功能,Logback也是一個(gè)不錯(cuò)的選擇
但如果我們同時(shí)引用了這兩者,會(huì)報(bào)錯(cuò)嗎?還是會(huì)使用其中的某一個(gè)?
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.13.3</version>
</dependency>
可以看到,SLF4J 發(fā)現(xiàn)了系統(tǒng)中同時(shí)存在兩個(gè)插件框架,并最終選擇了使用 Logback
總結(jié)
學(xué)習(xí)完本文,你應(yīng)當(dāng)對(duì)現(xiàn)在這幾個(gè)常用框架的有所了解,并能基礎(chǔ)應(yīng)用了。此次我們沒有講源碼,也沒有深入的講其配置及進(jìn)階使用,這些我們會(huì)在后面慢慢學(xué)習(xí)。但現(xiàn)在我希望你能知道的是。一定要寫好日志,一定要寫好日志,一定要寫好日志。重要的事情說三遍!這是區(qū)別新人和老鳥的一個(gè)重要依據(jù),也是讓自己排查問題更輕松的不二法門!
另外,現(xiàn)在很多中間件都自己引用了日志插件,我們作為一個(gè)整體工程在使用中間件時(shí),要及時(shí)發(fā)現(xiàn)并解決插件沖突,避免我們自己的日志配置失效,這也是一個(gè)程序員該注意的點(diǎn)。
來源:blog.csdn.net/u011709538/article/details/132363370

