JAVA SpringBoot統(tǒng)一日志處理原理詳解
框架 | 日志 |
---|---|
Spring | JCL |
SpringBoot | Sfl4j–>logback |
Hibernate3 | Slf4j |
Struts2 | LoggerFactory(com.opensymphony.xwork2.util.logging.LoggerFactory) |
由于歷史迭代原因,JCL和jboss-logging日志框架,基本已經(jīng)很久沒有更新了,不太適合作為現(xiàn)在框架的主流選擇,那么剩下的選擇中log4j、slf4j是使用最多的,然而由于log4j的輸出性能問題,log4j的作者選擇重新編寫了一個日志門面–Slf4j,并且編寫了基于Slf4j的日志實現(xiàn)–logback,其輸出信息的效率遠超log4j,解決了log4j遺留下的性能問題,所以在SpringBoot框架中,默認也選擇了Slf4j來作為默認日志框架
slf4j的使用
現(xiàn)在,我們來看看slf4j的使用,引入maven依賴:
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.28</version> </dependency>
按照slf4j官方的說法,,日志記錄方法的調(diào)用,不應(yīng)該來直接調(diào)用日志的實現(xiàn)類,而是調(diào)用日志抽象層里面的實現(xiàn)方法,獲取通過日志工廠創(chuàng)建的日志實例,即可輸出對應(yīng)的日志:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class HelloWorld { public static void main(String[] args) { Logger logger = LoggerFactory.getLogger(HelloWorld.class); [圖片上傳中...(slf4j日志輸出過程.png-6f5073-1583207284091-0)] logger.info("Hello World"); } }
這里我們注意到了一點,使用slf4j的輸出日志的時候,我們也引入了logback這個基于slf4j日志門面實現(xiàn)的具體日志輸出框架,如果不指定具體的日志輸出實現(xiàn),將會找不到具體的日志輸出實例,slf4j的日志輸出過程如圖所示:
從圖中可以看到,應(yīng)用程序調(diào)用了slf4j的api接口以后,具體的實現(xiàn)則是由slf4j日志門面找到對應(yīng)的日志的系統(tǒng)來實現(xiàn)日志輸出
解決多框架日志不統(tǒng)一問題
現(xiàn)在我們再回到日志統(tǒng)一的問題上,前面已經(jīng)了解了,開發(fā)常用的框架,如Spring、mybatis等使用的框架都是框架開發(fā)者自己選擇的,如果我們每個框架就引入一個日志系統(tǒng),并且最終需要打印日志的時候,會出現(xiàn)使用n種日志系統(tǒng)平臺,并且每一種的日志打印的格式、內(nèi)容和性能都需要手動控制,不僅讓項目變大,而且增大了項目復(fù)雜度,對性能也有很大的影響,那么我們該如何讓所有的開源框架統(tǒng)一使用Slf4j來輸出呢?我們來看下slf4j官方給我們的方案,如圖所示:
從圖中我們可以看出來,官方的方案是針對不同的日志框架,開發(fā)了一套適配兼容的框架與之對應(yīng),使用這些兼容jar來替代原來的日志框架即可,例如log4j日志框架,與之對應(yīng)的就是log4j-over-slf4j.jar,并且常見的日志框架,slf4j團隊都實現(xiàn)了一套與之對應(yīng)的基于slf4j的兼容框架,關(guān)系如下:
日志框架 | slf4j兼容框架 |
---|---|
log4j | log4j-over-slf4j |
commons logging | jcl-over-slf4j |
java.util.logging | jui-to-slf4j |
SpringBoot如何處理日志關(guān)系
在使用SpringBoot的時候,我們會發(fā)現(xiàn)官方默認使用的是spring‐boot‐starter‐logging這個starter來引入日志系統(tǒng)的,我們展開該依賴的依賴圖,如下:
可以看到spring‐boot‐starter‐logging這個starter中,引入了四個日志實例的依賴,分別是logback和我們前面提到的日志兼容jar的依賴,并且最終引入了slf4j的日志門面的依賴,實現(xiàn)了統(tǒng)一日志處理。但是為什么兼容jar引入后就能解決日志輸出的問題呢?難道兼容包有什么神奇的黑科技嗎?其實不然,我們隨便展開其中的幾個兼容日志jar的包名,如圖:
原來這些日志兼容包的包名與原來的日志框架的包名完全一樣,并且完全按照slf4j的方式實現(xiàn)了一套和以前一樣的API,這樣依賴這些日志框架的開源框架在運行的時候查找對應(yīng)包名下的class也不會報錯,但熟悉java類加載機制的都知道,兩個jar的包名以及使用的class都一樣的話,加載會出現(xiàn)異常,我們進入spring‐boot‐starter‐logging的pom依賴中一探究竟,最后在maven依賴中發(fā)現(xiàn)了端倪,如Spring框架使用的是commons-logging,而在spring-boot-starter-logging中,將spring的日志依賴排除,如下:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring‐core</artifactId> <exclusions> <exclusion> <groupId>commons‐logging</groupId> <artifactId>commons‐logging</artifactId> </exclusion> </exclusions> </dependency>
這樣spring框架在運行時使用的時候,使用的就是兼容jar中的日志實例了,SpringBoot成功的完成了一次日志系統(tǒng)統(tǒng)一的偷天換日操作。
slf4j的橋接原理
通過查看SpringBoot的日志處理,我們可以大致總結(jié)如下幾步操作:
1、將系統(tǒng)中其他日志框架先排除出去;
2、用中間包來替換原有的日志框架;
3、我們導(dǎo)入slf4j其他的實現(xiàn)
通過以上的操作,即可完成日志系統(tǒng)的統(tǒng)一,但是我們開始有了新的疑惑,slf4j是怎么做到的自動查找對應(yīng)的實現(xiàn)日志,并且完成了日志的正常打印操作的呢?這個就要涉及到slf4j的橋接原理,我們先來看看slf4j源碼中關(guān)于日志調(diào)用相關(guān)的代碼:
//slf4j日志調(diào)用過程相關(guān)的代碼 //根據(jù)名稱獲取日志實例 public static Logger getLogger(String name) { ILoggerFactory iLoggerFactory = getILoggerFactory(); return iLoggerFactory.getLogger(name); } //獲取日志實例工廠并且完成日志實例的查找與初始化操作 public static ILoggerFactory getILoggerFactory() { if (INITIALIZATION_STATE == UNINITIALIZED) { INITIALIZATION_STATE = ONGOING_INITIALIZATION; //查找實現(xiàn)類 performInitialization(); } ... return StaticLoggerBinder.getSingleton().getLoggerFactory(); ... }
可以看到整個過程中是通過StaticLoggerBinder.getSingleton() 來進行初始化日志工廠操作,而StaticLoggerBinder
這個類是從哪來的呢?我們發(fā)現(xiàn)StaticLoggerBinder
類并不存在于slf4j的jar中,而是通過查找org/slf4j/impl/StaticLoggerBinder.class類的路徑來發(fā)現(xiàn)具體的實現(xiàn)類,代碼如下:
//設(shè)置默認的查找日志實例的StaticLoggerBinder路徑 private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class"; private static Set findPossibleStaticLoggerBinderPathSet() { ....... paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH); ...... }
這個時候我們就該思考一個問題,如果我們同時存在了多個StaticLoggerBinder 時會加載哪一個呢?熟悉java類加載機制可知,類加載器會按照一定的順序逐個掃描jar包目錄并且加載出來,所以先被類加載器掃描的StaticLoggerBinder會優(yōu)先被加載,具體的加載順序如下:
1.$java_home/lib 目錄下的java核心api
2.$java_home/lib/ext 目錄下的java擴展jar包
3.java -classpath/-Djava.class.path所指的目錄下的類與jar包
4.$CATALINA_HOME/common目錄下按照文件夾的順序從上往下依次加載
5.$CATALINA_HOME/server目錄下按照文件夾的順序從上往下依次加載
6.$CATALINA_BASE/shared目錄下按照文件夾的順序從上往下依次加載
7.項目/WEB-INF/classes下的class文件
8.項目/WEB-INF/lib下的jar文件
根據(jù)slf4j橋接原理改造logger
我們都知道平時使用slf4j輸出日志的時候往往獲取Logger實例來進行日志打印,但是Logger僅僅支持本地日志,不支持分布式環(huán)境的日志,而在slfj中有LogBean實例,可以支持分布式日志,包含了鏈路相關(guān)信息,那么我們是否可以改造slf4j的橋接過程,使得我們可以靈活的使用本地日志或者分布式日志呢?首先我們先看看我們需要實現(xiàn)的需求:
- logger和logbean結(jié)合,統(tǒng)一日志入口
- logbean降低代碼侵入性
- 無縫替換第三方框架中的日志,根據(jù)需求加入到分布式日志中
想要實現(xiàn)這個功能,有以下兩個思路實現(xiàn):
1.我們通過自定義appender,基于logback的appender進行擴展,可以實現(xiàn)分別輸出本地日志以及分布式日志,但是缺陷在于appender擴展性不高,很多參數(shù)信息獲取不到,例如上下文信息等
2.我們通過實現(xiàn)Logger接口,用來將Logger和LogBean聚合在一起,從而實現(xiàn)LogBean集成到Logger中,同樣此種方式的缺陷在于對于第三方框架日志,我們無能為力,無法直接替換使用,并且在使用的時候需要使用自定義的LogFactory
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
Java隊列篇之實現(xiàn)數(shù)組模擬隊列及可復(fù)用環(huán)形隊列詳解
像棧一樣,隊列(queue)也是一種線性表,它的特性是先進先出,插入在一端,刪除在另一端。就像排隊一樣,剛來的人入隊(push)要排在隊尾(rear),每次出隊(pop)的都是隊首(front)的人2021-10-10一篇文章帶你搞定SpringBoot不重啟項目實現(xiàn)修改靜態(tài)資源
這篇文章主要介紹了一篇文章帶你搞定SpringBoot不重啟項目實現(xiàn)修改靜態(tài)資源,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-09-09在Java中實現(xiàn)讓線程按照自己指定的順序執(zhí)行
這篇文章主要介紹了在Java中實現(xiàn)讓線程按照自己指定的順序執(zhí)行,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-06-06關(guān)于SpringCloud分布式系統(tǒng)中實現(xiàn)冪等性的幾種方式
這篇文章主要介紹了關(guān)于SpringCloud分布式系統(tǒng)中實現(xiàn)冪等性的幾種方式,冪等函數(shù),或冪等方法,是指可以使用相同參數(shù)重復(fù)執(zhí)行,并能獲得相同結(jié)果的函數(shù),這些函數(shù)不會影響系統(tǒng)狀態(tài),也不用擔心重復(fù)執(zhí)行會對系統(tǒng)造成改變,需要的朋友可以參考下2023-10-10java編程實現(xiàn)基于UDP協(xié)議傳輸數(shù)據(jù)的方法
這篇文章主要介紹了java編程實現(xiàn)基于UDP協(xié)議傳輸數(shù)據(jù)的方法,較為詳細的分析了UDP協(xié)議的原理及Java編程實現(xiàn)數(shù)據(jù)傳輸客戶端與服務(wù)器端的相關(guān)技巧,需要的朋友可以參考下2015-11-11