一、日志重要嗎
程序中的日志重要嗎? 在回答這個問題前,筆者先說個事例:
筆者印象尤深的就是去年某個同事,收到了客戶反饋的緊急bug。盡管申請到了日志文件,但因為很多關鍵步驟沒有打印日志,導致排查進度很慢,數個小時都沒能排查到問題,也無法給出解決對策。導致了客戶程序一直阻斷,最終產生了不少損失。 事后,經過仔細推敲,成功復現了這個bug,其實是一個很不起眼的數據轉換導致的。可因為日志內容的匱乏,排查起來難度很大。其實只要在數據轉換前后進行日志輸出,這個問題就是一眼的事。但可惜沒如果,故事的最后,開發部門還是遭到了客戶的投訴,影響到了部門績效
對于剛學習編程的同學,很多人都對日志滿不在乎,我們在做code review的時候,經常發現一些新同學喜歡一個方法寫得很長,然后中間的注釋和日志都少的可憐。
坦白的說,這是很不好的習慣,這意味著日后方法出了bug,或者需要迭代,要花費大量時間來理清方法的思路。千萬別迷信什么“方法名、字段名起的見明知意,就可以不寫注釋與日志”,那是他們的業務場景不夠復雜。以筆者為例,復雜的場景涉及很多公式、奇特的規定,不寫注釋與日志,后續沒人能維護得了
所以請務必記住,日志在開發過程中非常重要。它可以幫助開發人員了解程序中發生了什么,以及在某些情況下為什么會發生錯誤或異常。通過查看日志,開發人員可以輕松地定位并解決問題,并且可以更好地監控和調整應用程序的性能,在必要時進行故障排除和安全檢查
二、日志分級
最開始的日志分級是由Syslog的開發者Eric Allman在1981年提出的。之后,這個級別分級系統被廣泛應用于各種領域的日志記錄和信息處理中。下面我們就來介紹下常用的日志等級
TRACE
是最低級別的日志記錄,用于輸出最詳細的調試信息,通常用于開發調試目的。在生產環境中,應該關閉 TRACE 級別的日志記錄,以避免輸出過多無用信息。
DEBUG
是用于輸出程序中的一些調試信息,通常用于開發過程中。像 TRACE 一樣,在生產環境中應該關閉 DEBUG 級別的日志記錄。
INFO
用于輸出程序正常運行時的一些關鍵信息,比如程序的啟動、運行日志等。通常在生產環境中開啟 INFO 級別的日志記錄。
WARN
是用于輸出一些警告信息,提示程序可能會出現一些異?;蛘咤e誤。在應用程序中,WARN 級別的日志記錄通常用于記錄一些非致命性異常信息,以便能夠及時發現并處理這些問題。
ERROR
是用于輸出程序運行時的一些錯誤信息,通常表示程序出現了一些不可預料的錯誤。在應用程序中,ERROR 級別的日志記錄通常用于記錄一些致命性的異常信息,以便能夠及時發現并處理這些問題。
當然,除了這五種級別以外,還有一些日志框架定義了其他級別,例如 Python 中的 CRITICAL、PHP 中的 FATAL 等。CRITICAL 和 FATAL 都是用于表示程序出現了致命性錯誤或者異常,即不可恢復的錯誤。當然,對于我們今天要說的內容,知道上述五種日志等級就夠了。
三、常用日志插件
Log4j(1999年誕生)
Log4j 是Java領域中最早的流行日志框架之一。它由Ceki Gülcü開發,并后來由Apache軟件基金會接管。Log4j 提供了靈活的配置選項、多種輸出目的地、日志級別和分層日志體系。盡管Log4j 1在其時代取得了巨大的成功,但在性能和某些功能方面存在限制,因此后來演化為Log4j 2。
SLF4J(2004年誕生)
嚴格來說,SLF4J(Simple Logging Facade for Java)并不算一個插件,而是Ceki Gülcü開發的一個日志門面接口。它為Java應用程序提供了統一的日志抽象,使開發人員可以使用一致的API進行日志記錄,而不需要直接依賴于特定的日志實現。SLF4J 可以與多種底層日志框架(如Logback、Log4j 2、java.util.logging等)結合使用。
Logback(2009年誕生)
Logback 是Ceki Gülcü開發的日志框架,他也是Log4j的作者。Logback 是Log4j 1的后續版本,旨在提供更高性能、更靈活的配置和現代化的日志解決方案。Logback 支持異步日志記錄、多種輸出格式、靈活的配置以及與SLF4J緊密集成。
Log4j 2(2014年誕生)
Log4j 2 是Apache軟件基金會開發的Log4j的下一代版本。它引入了許多新特性,如異步日志記錄、插件支持、豐富的過濾器等,旨在提供更好的性能和靈活性。Log4j 2 在設計上考慮了Log4j 1的局限性,并且支持多種配置方式。
小故事
不難注意到,一個有意思的小故事是,前三款日志插件都是Ceki Gülcü開發的,但 Log4j 2 并不是,雖然現在有很多人以為log4j2也是他寫的,但我們在github上可以看到其個人說明 “Unaffiliated with log4j 2.x.” (與 log4j 2.x 無關),所以log4j2 和 logback 都自稱是log4j 的后續版本,到底誰才算正統續作呢?這就留給各位讀者自己玩味了
四、外觀模式與SLF4J
在講解更多插件詳情之前,我們先來看看使用最多的SLF4J ,我們前面說了 SLF4J(Simple Logging Facade for Java)是Ceki Gülcü開發的一個日志門面接口,那么很顯然這里就用到了門面模式(即Facade 或 外觀模式),筆者比較習慣說成是外觀模式,后續就稱為外觀模式。
1. 外觀模式
定義:外觀模式是一種結構型設計模式,它提供了一個簡單的接口,封裝了底層復雜的子系統,使得客戶端可以更方便地使用這個子系統
目的:外觀模式的目的是隱藏底層系統的復雜性,降低訪問成本。
如果說看定義有些抽象,那我們可以以生活中的例子來說,我們都知道現在越來越流行智能家居,也就是家庭內裝了很多智能家電,從電視、空調、到廊燈甚至窗簾都是智能的。這類家庭往往會有一個控制中心,我們不需要手動去開電視,只需要對著控制中心說:“小A小A,幫我打開電視,音量調到30%”,電視就會應聲打開并調節音量
那么這樣的話,我們不需要知道怎么開電視,怎么調音量。通通都能用最簡單的話語來調節。同理,現在手機上的拍照功能:感光度,對焦,白平衡這些細節都給你自動完成了,所以這些復雜的內容你現在根本不用管,只需要猛按拍照鍵即可。
這就是外觀模式的意義,外觀模式就是為了隱藏系統的復雜性而設計出來的,讓客戶端只對接觸到一個外觀類,而不會接觸到系統內部的復雜邏輯
2. SLF4J 的誕生
在早期使用日志框架時,應用程序通常需要直接與具體的日志框架進行耦合,這就導致了以下幾個問題:
代碼依賴性
應用程序需要直接引用具體的日志框架,從而導致代碼與日志框架強耦合,難以滿足應用程序對日志框架的靈活配置。
日志框架不統一
在使用不同的日志框架時,應用程序需要根據具體的日志框架來編寫代碼,這不僅會增加開發難度,而且在多種日志框架中切換時需要進行大量的代碼改動。
性能問題
在日志輸出頻繁的情況下,由于日志框架的實現方式和API設計不同,可能會導致性能問題。
為了解決這些問題,SLF4J提供了一套通用的日志門面接口,讓應用程序可以通過這些接口來記錄日志信息,而不需要直接引用具體的日志框架。這樣,應用程序就可以在不同的日志框架之間進行靈活配置和切換,同時還可以獲得更好的性能表現。所以,我強烈建議各位使用SLF4J, 而不是直接對接某個具體的日志框架。
3. SLF4J 的使用
首先,我們需要在工程內引入包,但是如果你用了springboot,各種 spring-boot-starter 啟動器已經引用過了,所以引用前最好確認下:
org.slf4j slf4j-api 1.7.32
然后在我們要打印日志的類里加上一行 ;private static final Logger logger = LoggerFactory.getLogger(XXXX.class); 即可使用,如下:
importorg.slf4j.Logger; importorg.slf4j.LoggerFactory; publicclassMyClass{ privatestaticfinalLoggerlog=LoggerFactory.getLogger(MyClass.class); //... publicstaticvoidmain(String[]args){ log.info("Thisisaninfomessage."); } }
如果我們引用了lombok的話,也可以使用lombok的注解@Slf4j 代替上面那句話來使用 SLF4J ,如下:
importlombok.extern.slf4j.Slf4j; @Slf4j publicclassMyClass{ publicstaticvoidmain(String[]args){ log.info("Thisisaninfomessage."); } }
但是,我們都知道SLF4J僅僅是個門面,換句話說,僅有接口而沒有實現,如果此刻我們直接運行,打印日志是沒有用處的
所以,我們如果要運行,我們必須要給 SLF4J 安排上實現,而目前最常用的就是 logback 和 log4j2 了,就讓我們接著往下看
五、雙雄之爭
其實關于 Logback 和 Log4j 2,網絡上有很多評測,就不需贅述了,主要是圍繞性能方面的,從目前大家的反饋看,Log4j 2 晚出現好幾年,還是有后發優勢的,性能會比 Logback 好。當然, Logback 本身性能也很強,對于大多數場景,完全是夠用的,而且配置比較直觀,是spring-boot 默認使用的日志插件。
所以,選誰都可以,如果不想費神,可以直接使用spring-boot自帶的Logback,如果對日志性能要求很高,使用log4j2更保險,我們接下來分別介紹兩者。
1. Logback
1. 引用
由于 Logback 為 spring-boot 默認日志框架,所以無需再引用,但對于非spring - boot 項目,可以做如下引用
ch.qos.logback logback-classic 1.2.12
Logback 的核心模塊為 logback-classic,它提供了一個 SLF4J 的實現,兼容 Log4j API,可以無縫地替換 Log4j。它自身已經包含了 logback-core 模塊,而 logback-core,顧名思義就是 logback 的核心功能,包括日志記錄器、Appender、Layout 等。其他 logback 模塊都依賴于該模塊
2. 配置
logback 可以通過 XML 或者 Groovy 配置。下面以 XML 配置為例。logback 的 XML 配置文件名稱通常為 logback.xml 或者 logback-spring.xml(在 Spring Boot 中),需要放置在 classpath 的根目錄下,
%-5relative[%thread]%-5level%logger{35}-%msg%n ${LOG_PATH}/app.log 50MB ${ARCHIVE_PATTERN} 30 50MB true %d{yyyy-MM-ddHHss.SSS}[%thread]%-5level%logger{35}-%msg%n 0 256
其中,主要包括以下配置:
springProperty 定義了 log 文件的存儲路徑,可以通過 Spring 的屬性文件配置方式進行設置,如果沒有配置則默認存儲在 logs 目錄下。
appender 定義了日志輸出的目標,這里包括了控制臺輸出和文件輸出兩種,具體可以根據需求進行配置。
root 定義了默認的日志級別和輸出目標,默認情況下,INFO 級別以上的日志信息會輸出到控制臺,可以根據實際需求進行修改。
logger 定義了不同組件的日志級別和輸出目標,例如,這里定義了 com.example 這個組件的日志級別為 DEBUG,而 com.example.demo 這個組件的日志級別為 ERROR,并將其輸出到文件中。
rollingPolicy 定義了日志文件的切割規則和歸檔策略,此處定義了日志文件每個 50MB 進行切割,每天生成一個日志文件,并且壓縮和刪除過期文件,最多保留 30 天的日志文件。
encoder 定義了日志信息的輸出格式,具體的格式可以自行定義。
asyncAppender 定義了異步輸出日志的方式,對于高并發時,可以使用異步輸出來提高系統的性能。
discardingThreshold 定義了異步輸出隊列的閾值,當隊列中的數據量超過此值時,會丟棄最早放入的數據,此處設置為 0 表示隊列不會丟棄任何數據。
queueSize 定義了異步輸出隊列的大小,當隊列滿時,會等待隊列中的數據被消費后再將數據放入隊列中,此處設置為 256。
3. 演示
我們新建一個普通工程(非spring工程),引用Logback后,把上述配置文件復制進logback.xml,然后將工程結構設置成如下模式
其中兩個類的代碼如下:
publicclassMain{ privatestaticfinalLoggerlog=LoggerFactory.getLogger(Main.class); publicstaticvoidmain(String[]args){ log.trace("ThisisaMaintracemessage."); log.debug("ThisisaMaindebugmessage."); log.info("ThisisaMaininfomessage."); log.warn("ThisisaMainwarnmessage."); log.error("ThisisaMainerrormessage."); Slave.main(args); } } publicclassSlave{ privatestaticfinalLoggerlog=LoggerFactory.getLogger(Slave.class); publicstaticvoidmain(String[]args){ log.trace("ThisisaSlavetracemessage."); log.debug("ThisisaSlavedebugmessage."); log.info("ThisisaSlaveinfomessage."); log.warn("ThisisaSlavewarnmessage."); log.error("ThisisaSlaveerrormessage."); } }
我們想實現這樣的效果,首先日志要同時 輸出到控制臺 及 日志文件,且不同層級的代碼,輸出的日志層級也不同。那么我們可以對上述的xml做出一些調整:
因為是非Spring項目,所以 springProperty 這樣的標簽就不要用了,我們直接寫死一個日志文件地址即可。
去掉原有的那些root、logger標簽,我們自己新建兩個logger,用于兩個不同的層級。我們想里層輸出 debug 級別,外層輸出info 級別,我們可以這么設置。并且同時輸出到控制臺及日志文件
當我們運行Main.main的時候,就可以得到以下日志,slave 能輸出debug級別,Main 只能輸出 info及以上級別
4. 細節點
其實我們上面的演示,有兩個細節點,需要注意一下。一個就是我們的
使用了一個 additivity="false" 的屬性,這其實是因為 logger 這個標簽在鎖定某個目錄時,可能會發生層級關系。比如我們的兩個 logger, 一個針對的目錄是 com.zhanfu 另一個是 com.zhanfu.child ,后者就會被前者包含。
當我們的 com.zhanfu.child.Slave 打印日志時,當然會使用后者(更精確)的設置,但前者的設置還使用嗎?就依賴于 additivity=“false”,此處如果我們把 additivity="false" (該屬性默認值為true)去掉,再來打印日志
就會發現,Slave 的日志打了兩遍,而且連 debug 級別的都打了兩遍,我們可以把這種邏輯理解為繼承,子類執行一遍,父類還能在執行一遍,但 leve 屬性還是會采用子類而非父類的。
另一點就是我們把 root 標簽刪除了,root 其實是一個頂級的 logger , 其他的logger都可以視為它的子類,如果那些logger存在沒涵蓋的地方,或其沒有指定 additivity="false" ,那最后root的設置就會被使用。比如我們將設置改為如下:
結果控制臺的輸出日志,Main會重復兩次,Slave 會重復三次,如下
但是因為我們的 root 只配置了控制臺輸出,所以日志文件里還是不會變的
2. Log4j 2
1. 引用
對于spring-boot項目,除了引用 Log4j 2 我們還需要先剔除 Logback 的引用,對于普通項目,我們只需直接引用即可。但注意我們的原則,通過 SLF4J 來使用 Log4j2,所以引用下面這個包
org.apache.logging.log4j log4j-slf4j-impl 2.13.3
其內包含 Log4j2 的實現,和 SLF4J 的 API,如下:
2. 配置
Log4j2 的配置邏輯和 logback 是類似的,只有些細節不同,比如Logger 的首字母大寫等等,最后我們寫下這樣一個 log4j2.xml
logs
Properties
部分定義了一個 logPath 屬性,方便在其他地方引用。
Appenders
定義了兩個 Appender:Console 和 RollingFile,分別將日志輸出到控制臺和文件中。RollingFile 使用了 RollingFileAppender,并設置了日志滾動策略和默認的備份文件數量。
Loggers
定義了三個 Logger:com.zhanfu.child 的日志級別為 DEBUG,com.zhanfu 的日志級別為INFO,Root Logger 的日志級別為 WARN。并指定了兩個 Appender:Console 和 File。
3. 演示
由于我們的配置邏輯沒變,所以日志的結果還是一樣的:
3. 對比
Log4j2和Logback都是Java應用程序中最流行的日志框架之一。它們均具備高度的可配置性和使用靈活性,并提供了一系列有用的功能,例如異步日志記錄和日志過濾等。下面從配置遍歷性、功能性、性能等方面進行比較和總結。
配置遍歷性
Logback的配置文件格式相對簡單,易于閱讀和修改。它支持符號來引用變量、屬性和環境變量等。此外,它還支持條件日志記錄(根據日志級別、日志記錄器名稱或時間等),以及滾動文件的大小或日期等。
Log4j2的配置文件格式較復雜,但它在配置文件中提供了大量的選項來控制日志記錄。它支持在配置文件中直接聲明上下文參數、過濾器、輸出器和Appender等,這使得它的配置更加靈活。此外,Log4j2還支持異步日志記錄、日志事件序列化和性能優化等。
總體來說,兩者都很好地支持了配置遍歷性,但Log4j2提供了更多的選項和更高的靈活性。
功能性
Logback提供了一系列基本的日志記錄功能,例如異步Appender、滾動文件和GZIP壓縮等。它還支持與SLF4J一起使用,可以很容易地與其他日志框架集成。
Log4j2提供了更多的高級功能,例如異步日志記錄、性能優化和日志事件序列化等。它還支持Lambda表達式,可以使日志記錄器更加簡潔和易讀。此外,Log4j2還支持Flume和Kafka等大數據處理框架,可以方便地將日志記錄發送到這些框架中。
總體來說,Log4j2提供了更多的高級功能,并且可以更好地與大數據處理框架集成。
性能
Logback的性能很好,可以處理高吞吐量的日志記錄。它采用了異步記錄器,利用了多線程來提高性能。
Log4j2在性能方面更加強大。它使用了異步記錄器和多線程,還引入了RingBuffer數據結構和Disruptor庫來加速日志事件的傳遞和處理。這使得它比Logback具有更高的吞吐量和更低的延遲。
綜上所述,Log4j2在配置靈活性、功能性和性能方面都比Logback更為強大。但如果需要輕量級的日志框架或者只需要基本的日志記錄功能,Logback也是一個不錯的選擇
但如果我們同時引用了這兩者,會報錯嗎?還是會使用其中的某一個?
ch.qos.logback logback-classic 1.2.12 org.apache.logging.log4j log4j-slf4j-impl 2.13.3
可以看到,SLF4J 發現了系統中同時存在兩個插件框架,并最終選擇了使用 Logback
總結
學習完本文,你應當對現在這幾個常用框架的有所了解,并能基礎應用了。此次我們沒有講源碼,也沒有深入的講其配置及進階使用,這些我們會在后面慢慢學習。但現在我希望你能知道的是。一定要寫好日志,一定要寫好日志,一定要寫好日志。重要的事情說三遍!這是區別新人和老鳥的一個重要依據,也是讓自己排查問題更輕松的不二法門!
另外,現在很多中間件都自己引用了日志插件,我們作為一個整體工程在使用中間件時,要及時發現并解決插件沖突,避免我們自己的日志配置失效,這也是一個程序員該注意的點。
-
應用程序
+關注
關注
37文章
3265瀏覽量
57678 -
spring
+關注
關注
0文章
340瀏覽量
14338 -
日志
+關注
關注
0文章
138瀏覽量
10639 -
Boot
+關注
關注
0文章
149瀏覽量
35823 -
python
+關注
關注
56文章
4792瀏覽量
84628
原文標題:忽視日志吃大虧,手把手教你玩轉 SpringBoot 日志
文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論