架構師之路-分析混亂的日誌體系
提起日誌,可能大家腦中會瞬間彈出很多關鍵字,比如log4j,jul,jcl,slf4j啊等等,但是在我們做一個系統架構時,在處理日誌這塊內容的時候,我們可能會面臨具體的日誌選用。而且在我們系統中難免會使用各種各樣的第三方jar包,比如我們的spring,mybatis等等,由於這些第三方jar包都有自己的日誌實現,以此如何在各種複雜的日誌使用場景下,將我們整個系統的日誌統一,便於我們統一管理呢?
當然如果你以後一直做這些crud的功能,可能這塊內容你永遠也不需要了解,因為這些你的架構師會給你處理好,當你有一天需要自己去架構一個專案的時候,這些東西是你不得不面臨的問題,以此瞭解這些日誌體系之間的關係,也是你不可或缺的。
1,首先我們來看下大家都耳熟能詳的log4j(具體使用不做說明,大家可自行百度,這裡意在說明格日誌技術之間的關係)。如下,引入log4j的jar,控制檯可以成功輸出日誌,說明我們的log4j是可以列印日誌的。
2,然後我們看下jul,這個日誌呢是jdk自帶的一個日誌技術(java.util.logging.Logger),如下實驗,可見,我們的jul自己也是可以列印日誌的(注意和log4j的日誌風格有很大差異)
3,下面我們再來看下jcl,使用jcl我們需要引入commons-logging.jar,如果有spring4及以前版本使用經驗的大兄弟,應該對這個jar包都不陌生的,因為我們的spring4的日誌技術就是採用的jcl,這個到spring5被改變了。
發現我們jcl也是可以列印日誌的,但是注意,這裡的日誌樣式跟我們前面的log4j是一樣的,那麼現在就有兩個問題了:
(1)jcl是自己列印的日誌呢?還是用我們的log4j來列印日誌的?
(2)jcl和log4j到底有什麼關係呢?
下面,我們將maven中的log4j的依賴去掉試試,我們發現這次的風格又跟我的jul一樣了,這裡我大膽猜測,jcl列印日誌可能和log4j和jul有關,可能也和其他的日誌技術有關,具體和哪些技術有什麼關係呢?原始碼,我那不妨來看看jcl的底層實現。
下圖為核心程式碼的幾個位置的截圖。
private static final String[] classesToDiscover = new String[]{ "org.apache.commons.logging.impl.Log4JLogger", "org.apache.commons.logging.impl.Jdk14Logger", "org.apache.commons.logging.impl.Jdk13LumberjackLogger", "org.apache.commons.logging.impl.SimpleLog"};
通過上面我們可以知道,在jcl中,定義了一個常量陣列,順序依次為:
"org.apache.commons.logging.impl.Log4JLogger",
"org.apache.commons.logging.impl.Jdk14Logger",
"org.apache.commons.logging.impl.Jdk13LumberjackLogger",
"org.apache.commons.logging.impl.SimpleLog"
也就是我們的:log4j,jul14,jul13,simplelog
(說明:jul在jdk3和4是一個分水嶺,在jdk4版本以前的日誌都是Jdk13LumberjackLogger,在4及以後的為Jdk14Logger,SimpleLog呢也就是我們的system.out輸出了)
原始碼也很好理解,首先在我們logFactory中getLog的時候,去迴圈這個常量陣列,依次根據取到的類名去反射獲取物件,如果類在工程中,反射可以獲取class,則返回,沒有則捕獲異常,用下一個類名繼續反射,直到獲取到log物件返回。
可以知道如果我們專案中使用了log4j的jar,則專案jcl的logFactory獲取到的log物件就是我們的log4j的log物件,然後利用我們的log4j去列印日誌,否則就使用我們的jul來輸出日誌(後面兩種基本可以忽略,因為現在的專案很少能見到jdk1.4及以前版本的了,要麼就是你很牛逼,自己去把jdk的日誌給改了,也沒必要)。
4,從上可以知道我們jcl已經很牛了,但是由於其不思進取,很早以前就不更新了,現在也僅僅只是相容我們的log4j和jul而已,因此很快就被另一種更強大的日誌技術所替代了,也就是我們的slf4j。
關於slf4j呢,我們先來看下他的官網介紹
這裡說呢,我們再使用slf4j呢,需要slf4j-api.jar這個依賴,然後還需要一個binder,我這裡呢我翻譯成繫結器。
當我們系統有一個其他的日誌,當需要使用slf4j來輸出日誌的時候,需要使用一個SLF4J Migrator(橋接器)。
其實百度上很多資料都將橋接器和繫結器弄混淆了,下面我通過一個圖來說明什麼是繫結器,什麼是橋接器:
如圖,我們現在有個app,通過slf4j列印日誌,slf4j呢需要通過一個繫結器(slf4j-jdk14-1.8.0-beta2)來繫結到我們的jul來輸出日誌,這如果這時候呢,專案經理說,需要在app中整合spring4,我們知道spring4是利用jcl來列印日誌的,這時候呢,如果考慮到系統的日誌統一,可以使用橋接器(jcl-over-slf4j)將jcl橋接到我們slf4j來,然後通過binding到jul來輸出日誌,保證系統日誌風格統一。
(備註:當然這個有很多更好的方法,我只是通過這個例子來跟大家說明下,什麼是繫結器,什麼是橋接器,以防大家將概念混淆)
其實這裡有一個問題,就是我們迴圈引用的問題,如圖,也就是我們迴圈引用的問題,依賴設定如下
可以看到會報一個棧溢位的錯誤,這是由於迴圈引用導致的,其實這個問題也很好解釋,我們繫結器繫結log4j的時候在log4j中又經過橋接器橋接到slf4j,然後又經過繫結器,依次迴圈,最後棧溢位。
當然如果你是一直寫crud的,你看永遠也不會遇到這個問題,還是那句話,如果又一天你成為了架構師(人總是要有夢想的),當你遇到這個問題的時候,希望你知道是因為這個問題引起的,從而可以找到合適的解決方法。
其實這個問題的實際場景也比較常見。
如圖,我們的app使用slf4j通過binder繫結到log4j,最後通過log4j輸出日誌。
這時候有個jar包X1,使用slf4j通過binder繫結到jul輸出日誌,然後X1需要整合X2,X2使用log4j輸出日誌,X1在整合X2的時候,使用橋接器(log4j-over-slf4j),保證X1的日誌最後都是通過jul輸出的。這之後我們整合X1的時候,便會出現,log4j的橋接器和繫結器共存的情況,上訴錯誤便模擬出來了,當然這種情況一般情況下只能取改我們的app了。![在這裡插入圖
當然我們的日誌技術還有很多很多,在我們日後自己架構系統的時候,不僅僅要考慮自己app日誌系統風格統一,可用性以及擴充套件性,同時還需要考慮到和其他技術之間的相容性等等,因此,對於各種日誌技術之間的關係相關的知識也是我們不可或缺的。
希望上述內容可以對大家有所幫助謝謝!