'架構師必備,帶你弄清混亂的JAVA日誌體系'

Java Sun公司 咔咔侃技術 2019-09-12
"

引言

還在為弄不清commons-logging-xx.jar、log4j-xx.jar、sl4j-api-xx.jar等日誌框架之間複雜的關係而感到煩惱嗎?

還在為如何統一系統的日誌輸出而感到不知所措嘛?

您是否依然存在這樣的煩惱。比如,要更改spring的日誌輸出為log4j 2,卻不知該引哪些jar包,只知道去百度一下所謂的博客,照著人家複製,卻無法弄懂其中的原理?

不要急,不要方!本文帶你們弄懂其中的原理,只要你靜下心看本文,你就能隨心所欲更改你係統裡的日誌框架,統一日誌輸出!

正文

日誌框架發展史

早年,你工作的時候,在日誌裡使用了log4j框架來輸出,於是你代碼是這麼寫的

import org.apache.log4j.Logger;
\\\\省略
Logger logger = Logger.getLogger(Test.class);
logger.trace("trace");
\\\\省略

但是,歲月流逝,sun公司對於log4j的出現內心隱隱表示嫉妒。於是在jdk1.4版本後,增加了一個包為java.util.logging,簡稱為jul,用以對抗log4j。於是,你的領導要你把日誌框架改為jul,這時候你只能一行行的將log4j的api改為jul的api,如下所示

import java.util.logging.Logger;
\\\\省略
Logger loggger = Logger.getLogger(Test.class.getName());
logger.finest("finest");
\\\\省略

可以看出,api完全是不同的。那有沒有辦法,將這些api抽象出接口,這樣以後調用的時候,就調用這些接口就好了呢?

這個時候jcl(Jakarta Commons Logging)出現了,說jcl可能大家有點陌生,講commons-logging-xx.jar組件,大家總有印象吧。JCL 只提供 log 接口,具體的實現則在運行時動態尋找。這樣一來組件開發者只需要針對 JCL 接口開發,而調用組件的應用程序則可以在運行時搭配自己喜好的日誌實踐工具。JCL可以實現的集成方案如下圖所示

"

引言

還在為弄不清commons-logging-xx.jar、log4j-xx.jar、sl4j-api-xx.jar等日誌框架之間複雜的關係而感到煩惱嗎?

還在為如何統一系統的日誌輸出而感到不知所措嘛?

您是否依然存在這樣的煩惱。比如,要更改spring的日誌輸出為log4j 2,卻不知該引哪些jar包,只知道去百度一下所謂的博客,照著人家複製,卻無法弄懂其中的原理?

不要急,不要方!本文帶你們弄懂其中的原理,只要你靜下心看本文,你就能隨心所欲更改你係統裡的日誌框架,統一日誌輸出!

正文

日誌框架發展史

早年,你工作的時候,在日誌裡使用了log4j框架來輸出,於是你代碼是這麼寫的

import org.apache.log4j.Logger;
\\\\省略
Logger logger = Logger.getLogger(Test.class);
logger.trace("trace");
\\\\省略

但是,歲月流逝,sun公司對於log4j的出現內心隱隱表示嫉妒。於是在jdk1.4版本後,增加了一個包為java.util.logging,簡稱為jul,用以對抗log4j。於是,你的領導要你把日誌框架改為jul,這時候你只能一行行的將log4j的api改為jul的api,如下所示

import java.util.logging.Logger;
\\\\省略
Logger loggger = Logger.getLogger(Test.class.getName());
logger.finest("finest");
\\\\省略

可以看出,api完全是不同的。那有沒有辦法,將這些api抽象出接口,這樣以後調用的時候,就調用這些接口就好了呢?

這個時候jcl(Jakarta Commons Logging)出現了,說jcl可能大家有點陌生,講commons-logging-xx.jar組件,大家總有印象吧。JCL 只提供 log 接口,具體的實現則在運行時動態尋找。這樣一來組件開發者只需要針對 JCL 接口開發,而調用組件的應用程序則可以在運行時搭配自己喜好的日誌實踐工具。JCL可以實現的集成方案如下圖所示

架構師必備,帶你弄清混亂的JAVA日誌體系

jcl默認的配置:如果能找到Log4j 則默認使用log4j 實現,如果沒有則使用jul(jdk自帶的) 實現,再沒有則使用jcl內部提供的SimpleLog 實現。

於是,你在代碼裡變成這麼寫了

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
\\\\省略
Log log =LogFactory.getLog(Test.class);
log.trace('trace');
\\\\省略

至於這個Log具體的實現類,JCL會在ClassLoader中進行查找。這麼做,有三個缺點,缺點一是效率較低,二是容易引發混亂,三是在使用了自定義ClassLoader的程序中,使用JCL會引發內存洩露。

於是log4j的作者覺得jcl不好用,自己又寫了一個新的接口api,那麼就是slf4j。關於slf4j的集成圖如下所示

"

引言

還在為弄不清commons-logging-xx.jar、log4j-xx.jar、sl4j-api-xx.jar等日誌框架之間複雜的關係而感到煩惱嗎?

還在為如何統一系統的日誌輸出而感到不知所措嘛?

您是否依然存在這樣的煩惱。比如,要更改spring的日誌輸出為log4j 2,卻不知該引哪些jar包,只知道去百度一下所謂的博客,照著人家複製,卻無法弄懂其中的原理?

不要急,不要方!本文帶你們弄懂其中的原理,只要你靜下心看本文,你就能隨心所欲更改你係統裡的日誌框架,統一日誌輸出!

正文

日誌框架發展史

早年,你工作的時候,在日誌裡使用了log4j框架來輸出,於是你代碼是這麼寫的

import org.apache.log4j.Logger;
\\\\省略
Logger logger = Logger.getLogger(Test.class);
logger.trace("trace");
\\\\省略

但是,歲月流逝,sun公司對於log4j的出現內心隱隱表示嫉妒。於是在jdk1.4版本後,增加了一個包為java.util.logging,簡稱為jul,用以對抗log4j。於是,你的領導要你把日誌框架改為jul,這時候你只能一行行的將log4j的api改為jul的api,如下所示

import java.util.logging.Logger;
\\\\省略
Logger loggger = Logger.getLogger(Test.class.getName());
logger.finest("finest");
\\\\省略

可以看出,api完全是不同的。那有沒有辦法,將這些api抽象出接口,這樣以後調用的時候,就調用這些接口就好了呢?

這個時候jcl(Jakarta Commons Logging)出現了,說jcl可能大家有點陌生,講commons-logging-xx.jar組件,大家總有印象吧。JCL 只提供 log 接口,具體的實現則在運行時動態尋找。這樣一來組件開發者只需要針對 JCL 接口開發,而調用組件的應用程序則可以在運行時搭配自己喜好的日誌實踐工具。JCL可以實現的集成方案如下圖所示

架構師必備,帶你弄清混亂的JAVA日誌體系

jcl默認的配置:如果能找到Log4j 則默認使用log4j 實現,如果沒有則使用jul(jdk自帶的) 實現,再沒有則使用jcl內部提供的SimpleLog 實現。

於是,你在代碼裡變成這麼寫了

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
\\\\省略
Log log =LogFactory.getLog(Test.class);
log.trace('trace');
\\\\省略

至於這個Log具體的實現類,JCL會在ClassLoader中進行查找。這麼做,有三個缺點,缺點一是效率較低,二是容易引發混亂,三是在使用了自定義ClassLoader的程序中,使用JCL會引發內存洩露。

於是log4j的作者覺得jcl不好用,自己又寫了一個新的接口api,那麼就是slf4j。關於slf4j的集成圖如下所示

架構師必備,帶你弄清混亂的JAVA日誌體系

如圖所示,應用調了sl4j-api,即日誌門面接口。日誌門面接口本身通常並沒有實際的日誌輸出能力,它底層還是需要去調用具體的日誌框架API的,也就是實際上它需要跟具體的日誌框架結合使用。由於具體日誌框架比較多,而且互相也大都不兼容,日誌門面接口要想實現與任意日誌框架結合可能需要對應的橋接器,上圖紅框中的組件即是對應的各種橋接器!

我們在代碼中需要寫日誌,變成下面這麼寫

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
//省略
Logger logger = LoggerFactory.getLogger(Test.class);
// 省略
logger.info("info");

在代碼中,並不會出現具體日誌框架的api。程序根據classpath中的橋接器類型,和日誌框架類型,判斷出logger.info應該以什麼框架輸出!注意了,如果classpath中不小心引了兩個橋接器,那會直接報錯的!

因此,在阿里的開發手冊上才有這麼一條

強制:應用中不可直接使用日誌系統(log4j、logback)中的 API ,而應依賴使用日誌框架 SLF4J 中的 API 。使用門面模式的日誌框架,有利於維護和各個類的日誌處理方式的統一。

ok,至此,基礎知識完畢,下面是實戰!

項目實戰

案例1:一個項目,一個模塊用log4j,另一個模塊用slf4j+log4j2,如何統一輸出?

其實在某些中小型公司,這種情況很常見。我曾經見過某公司的項目,因為研發不懂底層的日誌原理,日誌文件裡頭既有log4j.properties,又有log4j2.xml,各種API混用,慘不忍睹!

還有人用著jul的API,然後拿著log4j.properties,跑來問我,為什麼配置不生效!簡直是一言難盡!

OK,回到我們的問題,如何統一輸出!OK,這裡就要用上slf4j的適配器,slf4j提供了各種各樣的適配器,用來將某種日誌框架委託給slf4j。其最明顯的集成工作方式有如下:

"

引言

還在為弄不清commons-logging-xx.jar、log4j-xx.jar、sl4j-api-xx.jar等日誌框架之間複雜的關係而感到煩惱嗎?

還在為如何統一系統的日誌輸出而感到不知所措嘛?

您是否依然存在這樣的煩惱。比如,要更改spring的日誌輸出為log4j 2,卻不知該引哪些jar包,只知道去百度一下所謂的博客,照著人家複製,卻無法弄懂其中的原理?

不要急,不要方!本文帶你們弄懂其中的原理,只要你靜下心看本文,你就能隨心所欲更改你係統裡的日誌框架,統一日誌輸出!

正文

日誌框架發展史

早年,你工作的時候,在日誌裡使用了log4j框架來輸出,於是你代碼是這麼寫的

import org.apache.log4j.Logger;
\\\\省略
Logger logger = Logger.getLogger(Test.class);
logger.trace("trace");
\\\\省略

但是,歲月流逝,sun公司對於log4j的出現內心隱隱表示嫉妒。於是在jdk1.4版本後,增加了一個包為java.util.logging,簡稱為jul,用以對抗log4j。於是,你的領導要你把日誌框架改為jul,這時候你只能一行行的將log4j的api改為jul的api,如下所示

import java.util.logging.Logger;
\\\\省略
Logger loggger = Logger.getLogger(Test.class.getName());
logger.finest("finest");
\\\\省略

可以看出,api完全是不同的。那有沒有辦法,將這些api抽象出接口,這樣以後調用的時候,就調用這些接口就好了呢?

這個時候jcl(Jakarta Commons Logging)出現了,說jcl可能大家有點陌生,講commons-logging-xx.jar組件,大家總有印象吧。JCL 只提供 log 接口,具體的實現則在運行時動態尋找。這樣一來組件開發者只需要針對 JCL 接口開發,而調用組件的應用程序則可以在運行時搭配自己喜好的日誌實踐工具。JCL可以實現的集成方案如下圖所示

架構師必備,帶你弄清混亂的JAVA日誌體系

jcl默認的配置:如果能找到Log4j 則默認使用log4j 實現,如果沒有則使用jul(jdk自帶的) 實現,再沒有則使用jcl內部提供的SimpleLog 實現。

於是,你在代碼裡變成這麼寫了

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
\\\\省略
Log log =LogFactory.getLog(Test.class);
log.trace('trace');
\\\\省略

至於這個Log具體的實現類,JCL會在ClassLoader中進行查找。這麼做,有三個缺點,缺點一是效率較低,二是容易引發混亂,三是在使用了自定義ClassLoader的程序中,使用JCL會引發內存洩露。

於是log4j的作者覺得jcl不好用,自己又寫了一個新的接口api,那麼就是slf4j。關於slf4j的集成圖如下所示

架構師必備,帶你弄清混亂的JAVA日誌體系

如圖所示,應用調了sl4j-api,即日誌門面接口。日誌門面接口本身通常並沒有實際的日誌輸出能力,它底層還是需要去調用具體的日誌框架API的,也就是實際上它需要跟具體的日誌框架結合使用。由於具體日誌框架比較多,而且互相也大都不兼容,日誌門面接口要想實現與任意日誌框架結合可能需要對應的橋接器,上圖紅框中的組件即是對應的各種橋接器!

我們在代碼中需要寫日誌,變成下面這麼寫

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
//省略
Logger logger = LoggerFactory.getLogger(Test.class);
// 省略
logger.info("info");

在代碼中,並不會出現具體日誌框架的api。程序根據classpath中的橋接器類型,和日誌框架類型,判斷出logger.info應該以什麼框架輸出!注意了,如果classpath中不小心引了兩個橋接器,那會直接報錯的!

因此,在阿里的開發手冊上才有這麼一條

強制:應用中不可直接使用日誌系統(log4j、logback)中的 API ,而應依賴使用日誌框架 SLF4J 中的 API 。使用門面模式的日誌框架,有利於維護和各個類的日誌處理方式的統一。

ok,至此,基礎知識完畢,下面是實戰!

項目實戰

案例1:一個項目,一個模塊用log4j,另一個模塊用slf4j+log4j2,如何統一輸出?

其實在某些中小型公司,這種情況很常見。我曾經見過某公司的項目,因為研發不懂底層的日誌原理,日誌文件裡頭既有log4j.properties,又有log4j2.xml,各種API混用,慘不忍睹!

還有人用著jul的API,然後拿著log4j.properties,跑來問我,為什麼配置不生效!簡直是一言難盡!

OK,回到我們的問題,如何統一輸出!OK,這裡就要用上slf4j的適配器,slf4j提供了各種各樣的適配器,用來將某種日誌框架委託給slf4j。其最明顯的集成工作方式有如下:

架構師必備,帶你弄清混亂的JAVA日誌體系

進行選擇填空,將我們的案例裡的條件填入,顯然應該選log4j-over-slf4j適配器,就變成下面這張圖

"

引言

還在為弄不清commons-logging-xx.jar、log4j-xx.jar、sl4j-api-xx.jar等日誌框架之間複雜的關係而感到煩惱嗎?

還在為如何統一系統的日誌輸出而感到不知所措嘛?

您是否依然存在這樣的煩惱。比如,要更改spring的日誌輸出為log4j 2,卻不知該引哪些jar包,只知道去百度一下所謂的博客,照著人家複製,卻無法弄懂其中的原理?

不要急,不要方!本文帶你們弄懂其中的原理,只要你靜下心看本文,你就能隨心所欲更改你係統裡的日誌框架,統一日誌輸出!

正文

日誌框架發展史

早年,你工作的時候,在日誌裡使用了log4j框架來輸出,於是你代碼是這麼寫的

import org.apache.log4j.Logger;
\\\\省略
Logger logger = Logger.getLogger(Test.class);
logger.trace("trace");
\\\\省略

但是,歲月流逝,sun公司對於log4j的出現內心隱隱表示嫉妒。於是在jdk1.4版本後,增加了一個包為java.util.logging,簡稱為jul,用以對抗log4j。於是,你的領導要你把日誌框架改為jul,這時候你只能一行行的將log4j的api改為jul的api,如下所示

import java.util.logging.Logger;
\\\\省略
Logger loggger = Logger.getLogger(Test.class.getName());
logger.finest("finest");
\\\\省略

可以看出,api完全是不同的。那有沒有辦法,將這些api抽象出接口,這樣以後調用的時候,就調用這些接口就好了呢?

這個時候jcl(Jakarta Commons Logging)出現了,說jcl可能大家有點陌生,講commons-logging-xx.jar組件,大家總有印象吧。JCL 只提供 log 接口,具體的實現則在運行時動態尋找。這樣一來組件開發者只需要針對 JCL 接口開發,而調用組件的應用程序則可以在運行時搭配自己喜好的日誌實踐工具。JCL可以實現的集成方案如下圖所示

架構師必備,帶你弄清混亂的JAVA日誌體系

jcl默認的配置:如果能找到Log4j 則默認使用log4j 實現,如果沒有則使用jul(jdk自帶的) 實現,再沒有則使用jcl內部提供的SimpleLog 實現。

於是,你在代碼裡變成這麼寫了

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
\\\\省略
Log log =LogFactory.getLog(Test.class);
log.trace('trace');
\\\\省略

至於這個Log具體的實現類,JCL會在ClassLoader中進行查找。這麼做,有三個缺點,缺點一是效率較低,二是容易引發混亂,三是在使用了自定義ClassLoader的程序中,使用JCL會引發內存洩露。

於是log4j的作者覺得jcl不好用,自己又寫了一個新的接口api,那麼就是slf4j。關於slf4j的集成圖如下所示

架構師必備,帶你弄清混亂的JAVA日誌體系

如圖所示,應用調了sl4j-api,即日誌門面接口。日誌門面接口本身通常並沒有實際的日誌輸出能力,它底層還是需要去調用具體的日誌框架API的,也就是實際上它需要跟具體的日誌框架結合使用。由於具體日誌框架比較多,而且互相也大都不兼容,日誌門面接口要想實現與任意日誌框架結合可能需要對應的橋接器,上圖紅框中的組件即是對應的各種橋接器!

我們在代碼中需要寫日誌,變成下面這麼寫

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
//省略
Logger logger = LoggerFactory.getLogger(Test.class);
// 省略
logger.info("info");

在代碼中,並不會出現具體日誌框架的api。程序根據classpath中的橋接器類型,和日誌框架類型,判斷出logger.info應該以什麼框架輸出!注意了,如果classpath中不小心引了兩個橋接器,那會直接報錯的!

因此,在阿里的開發手冊上才有這麼一條

強制:應用中不可直接使用日誌系統(log4j、logback)中的 API ,而應依賴使用日誌框架 SLF4J 中的 API 。使用門面模式的日誌框架,有利於維護和各個類的日誌處理方式的統一。

ok,至此,基礎知識完畢,下面是實戰!

項目實戰

案例1:一個項目,一個模塊用log4j,另一個模塊用slf4j+log4j2,如何統一輸出?

其實在某些中小型公司,這種情況很常見。我曾經見過某公司的項目,因為研發不懂底層的日誌原理,日誌文件裡頭既有log4j.properties,又有log4j2.xml,各種API混用,慘不忍睹!

還有人用著jul的API,然後拿著log4j.properties,跑來問我,為什麼配置不生效!簡直是一言難盡!

OK,回到我們的問題,如何統一輸出!OK,這裡就要用上slf4j的適配器,slf4j提供了各種各樣的適配器,用來將某種日誌框架委託給slf4j。其最明顯的集成工作方式有如下:

架構師必備,帶你弄清混亂的JAVA日誌體系

進行選擇填空,將我們的案例裡的條件填入,顯然應該選log4j-over-slf4j適配器,就變成下面這張圖

架構師必備,帶你弄清混亂的JAVA日誌體系

就可以實現日誌統一為log4j2來輸出!

ps:根據適配器工作原理的不同,被適配的日誌框架並不是一定要刪除!以上圖為例,log4j這個日誌框架刪不刪都可以,你只要能保證log4j的加載順序在log4j-over-slf4j後即可。因為log4j-over-slf4j這個適配器的工作原理是,內部提供了和log4j一模一樣的api接口,因此你在程序中調用log4j的api的時候,你必須想辦法讓其走適配器的api。如果你刪了log4j這個框架,那你程序裡肯定是走log4j-over-slf4j這個組件裡的api。如果不刪log4j,只要保證其在classpth裡的順序比log4j前即可!

案例2:如何讓spring以log4j2的形式輸出?

spring默認使用的是jcl輸出日誌,由於你此時並沒有引入Log4j的日誌框架,jcl會以jul做為日誌框架。此時集成圖如下

"

引言

還在為弄不清commons-logging-xx.jar、log4j-xx.jar、sl4j-api-xx.jar等日誌框架之間複雜的關係而感到煩惱嗎?

還在為如何統一系統的日誌輸出而感到不知所措嘛?

您是否依然存在這樣的煩惱。比如,要更改spring的日誌輸出為log4j 2,卻不知該引哪些jar包,只知道去百度一下所謂的博客,照著人家複製,卻無法弄懂其中的原理?

不要急,不要方!本文帶你們弄懂其中的原理,只要你靜下心看本文,你就能隨心所欲更改你係統裡的日誌框架,統一日誌輸出!

正文

日誌框架發展史

早年,你工作的時候,在日誌裡使用了log4j框架來輸出,於是你代碼是這麼寫的

import org.apache.log4j.Logger;
\\\\省略
Logger logger = Logger.getLogger(Test.class);
logger.trace("trace");
\\\\省略

但是,歲月流逝,sun公司對於log4j的出現內心隱隱表示嫉妒。於是在jdk1.4版本後,增加了一個包為java.util.logging,簡稱為jul,用以對抗log4j。於是,你的領導要你把日誌框架改為jul,這時候你只能一行行的將log4j的api改為jul的api,如下所示

import java.util.logging.Logger;
\\\\省略
Logger loggger = Logger.getLogger(Test.class.getName());
logger.finest("finest");
\\\\省略

可以看出,api完全是不同的。那有沒有辦法,將這些api抽象出接口,這樣以後調用的時候,就調用這些接口就好了呢?

這個時候jcl(Jakarta Commons Logging)出現了,說jcl可能大家有點陌生,講commons-logging-xx.jar組件,大家總有印象吧。JCL 只提供 log 接口,具體的實現則在運行時動態尋找。這樣一來組件開發者只需要針對 JCL 接口開發,而調用組件的應用程序則可以在運行時搭配自己喜好的日誌實踐工具。JCL可以實現的集成方案如下圖所示

架構師必備,帶你弄清混亂的JAVA日誌體系

jcl默認的配置:如果能找到Log4j 則默認使用log4j 實現,如果沒有則使用jul(jdk自帶的) 實現,再沒有則使用jcl內部提供的SimpleLog 實現。

於是,你在代碼裡變成這麼寫了

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
\\\\省略
Log log =LogFactory.getLog(Test.class);
log.trace('trace');
\\\\省略

至於這個Log具體的實現類,JCL會在ClassLoader中進行查找。這麼做,有三個缺點,缺點一是效率較低,二是容易引發混亂,三是在使用了自定義ClassLoader的程序中,使用JCL會引發內存洩露。

於是log4j的作者覺得jcl不好用,自己又寫了一個新的接口api,那麼就是slf4j。關於slf4j的集成圖如下所示

架構師必備,帶你弄清混亂的JAVA日誌體系

如圖所示,應用調了sl4j-api,即日誌門面接口。日誌門面接口本身通常並沒有實際的日誌輸出能力,它底層還是需要去調用具體的日誌框架API的,也就是實際上它需要跟具體的日誌框架結合使用。由於具體日誌框架比較多,而且互相也大都不兼容,日誌門面接口要想實現與任意日誌框架結合可能需要對應的橋接器,上圖紅框中的組件即是對應的各種橋接器!

我們在代碼中需要寫日誌,變成下面這麼寫

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
//省略
Logger logger = LoggerFactory.getLogger(Test.class);
// 省略
logger.info("info");

在代碼中,並不會出現具體日誌框架的api。程序根據classpath中的橋接器類型,和日誌框架類型,判斷出logger.info應該以什麼框架輸出!注意了,如果classpath中不小心引了兩個橋接器,那會直接報錯的!

因此,在阿里的開發手冊上才有這麼一條

強制:應用中不可直接使用日誌系統(log4j、logback)中的 API ,而應依賴使用日誌框架 SLF4J 中的 API 。使用門面模式的日誌框架,有利於維護和各個類的日誌處理方式的統一。

ok,至此,基礎知識完畢,下面是實戰!

項目實戰

案例1:一個項目,一個模塊用log4j,另一個模塊用slf4j+log4j2,如何統一輸出?

其實在某些中小型公司,這種情況很常見。我曾經見過某公司的項目,因為研發不懂底層的日誌原理,日誌文件裡頭既有log4j.properties,又有log4j2.xml,各種API混用,慘不忍睹!

還有人用著jul的API,然後拿著log4j.properties,跑來問我,為什麼配置不生效!簡直是一言難盡!

OK,回到我們的問題,如何統一輸出!OK,這裡就要用上slf4j的適配器,slf4j提供了各種各樣的適配器,用來將某種日誌框架委託給slf4j。其最明顯的集成工作方式有如下:

架構師必備,帶你弄清混亂的JAVA日誌體系

進行選擇填空,將我們的案例裡的條件填入,顯然應該選log4j-over-slf4j適配器,就變成下面這張圖

架構師必備,帶你弄清混亂的JAVA日誌體系

就可以實現日誌統一為log4j2來輸出!

ps:根據適配器工作原理的不同,被適配的日誌框架並不是一定要刪除!以上圖為例,log4j這個日誌框架刪不刪都可以,你只要能保證log4j的加載順序在log4j-over-slf4j後即可。因為log4j-over-slf4j這個適配器的工作原理是,內部提供了和log4j一模一樣的api接口,因此你在程序中調用log4j的api的時候,你必須想辦法讓其走適配器的api。如果你刪了log4j這個框架,那你程序裡肯定是走log4j-over-slf4j這個組件裡的api。如果不刪log4j,只要保證其在classpth裡的順序比log4j前即可!

案例2:如何讓spring以log4j2的形式輸出?

spring默認使用的是jcl輸出日誌,由於你此時並沒有引入Log4j的日誌框架,jcl會以jul做為日誌框架。此時集成圖如下

架構師必備,帶你弄清混亂的JAVA日誌體系

而你的應用中,採用了slf4j+log4j-core,即log4j2進行日誌記錄,那麼此時集成圖如下

"

引言

還在為弄不清commons-logging-xx.jar、log4j-xx.jar、sl4j-api-xx.jar等日誌框架之間複雜的關係而感到煩惱嗎?

還在為如何統一系統的日誌輸出而感到不知所措嘛?

您是否依然存在這樣的煩惱。比如,要更改spring的日誌輸出為log4j 2,卻不知該引哪些jar包,只知道去百度一下所謂的博客,照著人家複製,卻無法弄懂其中的原理?

不要急,不要方!本文帶你們弄懂其中的原理,只要你靜下心看本文,你就能隨心所欲更改你係統裡的日誌框架,統一日誌輸出!

正文

日誌框架發展史

早年,你工作的時候,在日誌裡使用了log4j框架來輸出,於是你代碼是這麼寫的

import org.apache.log4j.Logger;
\\\\省略
Logger logger = Logger.getLogger(Test.class);
logger.trace("trace");
\\\\省略

但是,歲月流逝,sun公司對於log4j的出現內心隱隱表示嫉妒。於是在jdk1.4版本後,增加了一個包為java.util.logging,簡稱為jul,用以對抗log4j。於是,你的領導要你把日誌框架改為jul,這時候你只能一行行的將log4j的api改為jul的api,如下所示

import java.util.logging.Logger;
\\\\省略
Logger loggger = Logger.getLogger(Test.class.getName());
logger.finest("finest");
\\\\省略

可以看出,api完全是不同的。那有沒有辦法,將這些api抽象出接口,這樣以後調用的時候,就調用這些接口就好了呢?

這個時候jcl(Jakarta Commons Logging)出現了,說jcl可能大家有點陌生,講commons-logging-xx.jar組件,大家總有印象吧。JCL 只提供 log 接口,具體的實現則在運行時動態尋找。這樣一來組件開發者只需要針對 JCL 接口開發,而調用組件的應用程序則可以在運行時搭配自己喜好的日誌實踐工具。JCL可以實現的集成方案如下圖所示

架構師必備,帶你弄清混亂的JAVA日誌體系

jcl默認的配置:如果能找到Log4j 則默認使用log4j 實現,如果沒有則使用jul(jdk自帶的) 實現,再沒有則使用jcl內部提供的SimpleLog 實現。

於是,你在代碼裡變成這麼寫了

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
\\\\省略
Log log =LogFactory.getLog(Test.class);
log.trace('trace');
\\\\省略

至於這個Log具體的實現類,JCL會在ClassLoader中進行查找。這麼做,有三個缺點,缺點一是效率較低,二是容易引發混亂,三是在使用了自定義ClassLoader的程序中,使用JCL會引發內存洩露。

於是log4j的作者覺得jcl不好用,自己又寫了一個新的接口api,那麼就是slf4j。關於slf4j的集成圖如下所示

架構師必備,帶你弄清混亂的JAVA日誌體系

如圖所示,應用調了sl4j-api,即日誌門面接口。日誌門面接口本身通常並沒有實際的日誌輸出能力,它底層還是需要去調用具體的日誌框架API的,也就是實際上它需要跟具體的日誌框架結合使用。由於具體日誌框架比較多,而且互相也大都不兼容,日誌門面接口要想實現與任意日誌框架結合可能需要對應的橋接器,上圖紅框中的組件即是對應的各種橋接器!

我們在代碼中需要寫日誌,變成下面這麼寫

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
//省略
Logger logger = LoggerFactory.getLogger(Test.class);
// 省略
logger.info("info");

在代碼中,並不會出現具體日誌框架的api。程序根據classpath中的橋接器類型,和日誌框架類型,判斷出logger.info應該以什麼框架輸出!注意了,如果classpath中不小心引了兩個橋接器,那會直接報錯的!

因此,在阿里的開發手冊上才有這麼一條

強制:應用中不可直接使用日誌系統(log4j、logback)中的 API ,而應依賴使用日誌框架 SLF4J 中的 API 。使用門面模式的日誌框架,有利於維護和各個類的日誌處理方式的統一。

ok,至此,基礎知識完畢,下面是實戰!

項目實戰

案例1:一個項目,一個模塊用log4j,另一個模塊用slf4j+log4j2,如何統一輸出?

其實在某些中小型公司,這種情況很常見。我曾經見過某公司的項目,因為研發不懂底層的日誌原理,日誌文件裡頭既有log4j.properties,又有log4j2.xml,各種API混用,慘不忍睹!

還有人用著jul的API,然後拿著log4j.properties,跑來問我,為什麼配置不生效!簡直是一言難盡!

OK,回到我們的問題,如何統一輸出!OK,這裡就要用上slf4j的適配器,slf4j提供了各種各樣的適配器,用來將某種日誌框架委託給slf4j。其最明顯的集成工作方式有如下:

架構師必備,帶你弄清混亂的JAVA日誌體系

進行選擇填空,將我們的案例裡的條件填入,顯然應該選log4j-over-slf4j適配器,就變成下面這張圖

架構師必備,帶你弄清混亂的JAVA日誌體系

就可以實現日誌統一為log4j2來輸出!

ps:根據適配器工作原理的不同,被適配的日誌框架並不是一定要刪除!以上圖為例,log4j這個日誌框架刪不刪都可以,你只要能保證log4j的加載順序在log4j-over-slf4j後即可。因為log4j-over-slf4j這個適配器的工作原理是,內部提供了和log4j一模一樣的api接口,因此你在程序中調用log4j的api的時候,你必須想辦法讓其走適配器的api。如果你刪了log4j這個框架,那你程序裡肯定是走log4j-over-slf4j這個組件裡的api。如果不刪log4j,只要保證其在classpth裡的順序比log4j前即可!

案例2:如何讓spring以log4j2的形式輸出?

spring默認使用的是jcl輸出日誌,由於你此時並沒有引入Log4j的日誌框架,jcl會以jul做為日誌框架。此時集成圖如下

架構師必備,帶你弄清混亂的JAVA日誌體系

而你的應用中,採用了slf4j+log4j-core,即log4j2進行日誌記錄,那麼此時集成圖如下

架構師必備,帶你弄清混亂的JAVA日誌體系

那我們現在需要讓spring以log4j2的形式輸出?怎麼辦?

OK,第一種方案,走jcl-over-slf4j適配器,此時集成圖就變成下面這樣了

"

引言

還在為弄不清commons-logging-xx.jar、log4j-xx.jar、sl4j-api-xx.jar等日誌框架之間複雜的關係而感到煩惱嗎?

還在為如何統一系統的日誌輸出而感到不知所措嘛?

您是否依然存在這樣的煩惱。比如,要更改spring的日誌輸出為log4j 2,卻不知該引哪些jar包,只知道去百度一下所謂的博客,照著人家複製,卻無法弄懂其中的原理?

不要急,不要方!本文帶你們弄懂其中的原理,只要你靜下心看本文,你就能隨心所欲更改你係統裡的日誌框架,統一日誌輸出!

正文

日誌框架發展史

早年,你工作的時候,在日誌裡使用了log4j框架來輸出,於是你代碼是這麼寫的

import org.apache.log4j.Logger;
\\\\省略
Logger logger = Logger.getLogger(Test.class);
logger.trace("trace");
\\\\省略

但是,歲月流逝,sun公司對於log4j的出現內心隱隱表示嫉妒。於是在jdk1.4版本後,增加了一個包為java.util.logging,簡稱為jul,用以對抗log4j。於是,你的領導要你把日誌框架改為jul,這時候你只能一行行的將log4j的api改為jul的api,如下所示

import java.util.logging.Logger;
\\\\省略
Logger loggger = Logger.getLogger(Test.class.getName());
logger.finest("finest");
\\\\省略

可以看出,api完全是不同的。那有沒有辦法,將這些api抽象出接口,這樣以後調用的時候,就調用這些接口就好了呢?

這個時候jcl(Jakarta Commons Logging)出現了,說jcl可能大家有點陌生,講commons-logging-xx.jar組件,大家總有印象吧。JCL 只提供 log 接口,具體的實現則在運行時動態尋找。這樣一來組件開發者只需要針對 JCL 接口開發,而調用組件的應用程序則可以在運行時搭配自己喜好的日誌實踐工具。JCL可以實現的集成方案如下圖所示

架構師必備,帶你弄清混亂的JAVA日誌體系

jcl默認的配置:如果能找到Log4j 則默認使用log4j 實現,如果沒有則使用jul(jdk自帶的) 實現,再沒有則使用jcl內部提供的SimpleLog 實現。

於是,你在代碼裡變成這麼寫了

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
\\\\省略
Log log =LogFactory.getLog(Test.class);
log.trace('trace');
\\\\省略

至於這個Log具體的實現類,JCL會在ClassLoader中進行查找。這麼做,有三個缺點,缺點一是效率較低,二是容易引發混亂,三是在使用了自定義ClassLoader的程序中,使用JCL會引發內存洩露。

於是log4j的作者覺得jcl不好用,自己又寫了一個新的接口api,那麼就是slf4j。關於slf4j的集成圖如下所示

架構師必備,帶你弄清混亂的JAVA日誌體系

如圖所示,應用調了sl4j-api,即日誌門面接口。日誌門面接口本身通常並沒有實際的日誌輸出能力,它底層還是需要去調用具體的日誌框架API的,也就是實際上它需要跟具體的日誌框架結合使用。由於具體日誌框架比較多,而且互相也大都不兼容,日誌門面接口要想實現與任意日誌框架結合可能需要對應的橋接器,上圖紅框中的組件即是對應的各種橋接器!

我們在代碼中需要寫日誌,變成下面這麼寫

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
//省略
Logger logger = LoggerFactory.getLogger(Test.class);
// 省略
logger.info("info");

在代碼中,並不會出現具體日誌框架的api。程序根據classpath中的橋接器類型,和日誌框架類型,判斷出logger.info應該以什麼框架輸出!注意了,如果classpath中不小心引了兩個橋接器,那會直接報錯的!

因此,在阿里的開發手冊上才有這麼一條

強制:應用中不可直接使用日誌系統(log4j、logback)中的 API ,而應依賴使用日誌框架 SLF4J 中的 API 。使用門面模式的日誌框架,有利於維護和各個類的日誌處理方式的統一。

ok,至此,基礎知識完畢,下面是實戰!

項目實戰

案例1:一個項目,一個模塊用log4j,另一個模塊用slf4j+log4j2,如何統一輸出?

其實在某些中小型公司,這種情況很常見。我曾經見過某公司的項目,因為研發不懂底層的日誌原理,日誌文件裡頭既有log4j.properties,又有log4j2.xml,各種API混用,慘不忍睹!

還有人用著jul的API,然後拿著log4j.properties,跑來問我,為什麼配置不生效!簡直是一言難盡!

OK,回到我們的問題,如何統一輸出!OK,這裡就要用上slf4j的適配器,slf4j提供了各種各樣的適配器,用來將某種日誌框架委託給slf4j。其最明顯的集成工作方式有如下:

架構師必備,帶你弄清混亂的JAVA日誌體系

進行選擇填空,將我們的案例裡的條件填入,顯然應該選log4j-over-slf4j適配器,就變成下面這張圖

架構師必備,帶你弄清混亂的JAVA日誌體系

就可以實現日誌統一為log4j2來輸出!

ps:根據適配器工作原理的不同,被適配的日誌框架並不是一定要刪除!以上圖為例,log4j這個日誌框架刪不刪都可以,你只要能保證log4j的加載順序在log4j-over-slf4j後即可。因為log4j-over-slf4j這個適配器的工作原理是,內部提供了和log4j一模一樣的api接口,因此你在程序中調用log4j的api的時候,你必須想辦法讓其走適配器的api。如果你刪了log4j這個框架,那你程序裡肯定是走log4j-over-slf4j這個組件裡的api。如果不刪log4j,只要保證其在classpth裡的順序比log4j前即可!

案例2:如何讓spring以log4j2的形式輸出?

spring默認使用的是jcl輸出日誌,由於你此時並沒有引入Log4j的日誌框架,jcl會以jul做為日誌框架。此時集成圖如下

架構師必備,帶你弄清混亂的JAVA日誌體系

而你的應用中,採用了slf4j+log4j-core,即log4j2進行日誌記錄,那麼此時集成圖如下

架構師必備,帶你弄清混亂的JAVA日誌體系

那我們現在需要讓spring以log4j2的形式輸出?怎麼辦?

OK,第一種方案,走jcl-over-slf4j適配器,此時集成圖就變成下面這樣了

架構師必備,帶你弄清混亂的JAVA日誌體系

在這種方案下,spring框架中遇到日誌輸出的語句,就會如上圖紅線流程一樣,最終以log4J2的形式輸出!

OK,有第二種方案麼?

有,走jul-to-slf4j適配器,此時集成圖如下

"

引言

還在為弄不清commons-logging-xx.jar、log4j-xx.jar、sl4j-api-xx.jar等日誌框架之間複雜的關係而感到煩惱嗎?

還在為如何統一系統的日誌輸出而感到不知所措嘛?

您是否依然存在這樣的煩惱。比如,要更改spring的日誌輸出為log4j 2,卻不知該引哪些jar包,只知道去百度一下所謂的博客,照著人家複製,卻無法弄懂其中的原理?

不要急,不要方!本文帶你們弄懂其中的原理,只要你靜下心看本文,你就能隨心所欲更改你係統裡的日誌框架,統一日誌輸出!

正文

日誌框架發展史

早年,你工作的時候,在日誌裡使用了log4j框架來輸出,於是你代碼是這麼寫的

import org.apache.log4j.Logger;
\\\\省略
Logger logger = Logger.getLogger(Test.class);
logger.trace("trace");
\\\\省略

但是,歲月流逝,sun公司對於log4j的出現內心隱隱表示嫉妒。於是在jdk1.4版本後,增加了一個包為java.util.logging,簡稱為jul,用以對抗log4j。於是,你的領導要你把日誌框架改為jul,這時候你只能一行行的將log4j的api改為jul的api,如下所示

import java.util.logging.Logger;
\\\\省略
Logger loggger = Logger.getLogger(Test.class.getName());
logger.finest("finest");
\\\\省略

可以看出,api完全是不同的。那有沒有辦法,將這些api抽象出接口,這樣以後調用的時候,就調用這些接口就好了呢?

這個時候jcl(Jakarta Commons Logging)出現了,說jcl可能大家有點陌生,講commons-logging-xx.jar組件,大家總有印象吧。JCL 只提供 log 接口,具體的實現則在運行時動態尋找。這樣一來組件開發者只需要針對 JCL 接口開發,而調用組件的應用程序則可以在運行時搭配自己喜好的日誌實踐工具。JCL可以實現的集成方案如下圖所示

架構師必備,帶你弄清混亂的JAVA日誌體系

jcl默認的配置:如果能找到Log4j 則默認使用log4j 實現,如果沒有則使用jul(jdk自帶的) 實現,再沒有則使用jcl內部提供的SimpleLog 實現。

於是,你在代碼裡變成這麼寫了

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
\\\\省略
Log log =LogFactory.getLog(Test.class);
log.trace('trace');
\\\\省略

至於這個Log具體的實現類,JCL會在ClassLoader中進行查找。這麼做,有三個缺點,缺點一是效率較低,二是容易引發混亂,三是在使用了自定義ClassLoader的程序中,使用JCL會引發內存洩露。

於是log4j的作者覺得jcl不好用,自己又寫了一個新的接口api,那麼就是slf4j。關於slf4j的集成圖如下所示

架構師必備,帶你弄清混亂的JAVA日誌體系

如圖所示,應用調了sl4j-api,即日誌門面接口。日誌門面接口本身通常並沒有實際的日誌輸出能力,它底層還是需要去調用具體的日誌框架API的,也就是實際上它需要跟具體的日誌框架結合使用。由於具體日誌框架比較多,而且互相也大都不兼容,日誌門面接口要想實現與任意日誌框架結合可能需要對應的橋接器,上圖紅框中的組件即是對應的各種橋接器!

我們在代碼中需要寫日誌,變成下面這麼寫

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
//省略
Logger logger = LoggerFactory.getLogger(Test.class);
// 省略
logger.info("info");

在代碼中,並不會出現具體日誌框架的api。程序根據classpath中的橋接器類型,和日誌框架類型,判斷出logger.info應該以什麼框架輸出!注意了,如果classpath中不小心引了兩個橋接器,那會直接報錯的!

因此,在阿里的開發手冊上才有這麼一條

強制:應用中不可直接使用日誌系統(log4j、logback)中的 API ,而應依賴使用日誌框架 SLF4J 中的 API 。使用門面模式的日誌框架,有利於維護和各個類的日誌處理方式的統一。

ok,至此,基礎知識完畢,下面是實戰!

項目實戰

案例1:一個項目,一個模塊用log4j,另一個模塊用slf4j+log4j2,如何統一輸出?

其實在某些中小型公司,這種情況很常見。我曾經見過某公司的項目,因為研發不懂底層的日誌原理,日誌文件裡頭既有log4j.properties,又有log4j2.xml,各種API混用,慘不忍睹!

還有人用著jul的API,然後拿著log4j.properties,跑來問我,為什麼配置不生效!簡直是一言難盡!

OK,回到我們的問題,如何統一輸出!OK,這裡就要用上slf4j的適配器,slf4j提供了各種各樣的適配器,用來將某種日誌框架委託給slf4j。其最明顯的集成工作方式有如下:

架構師必備,帶你弄清混亂的JAVA日誌體系

進行選擇填空,將我們的案例裡的條件填入,顯然應該選log4j-over-slf4j適配器,就變成下面這張圖

架構師必備,帶你弄清混亂的JAVA日誌體系

就可以實現日誌統一為log4j2來輸出!

ps:根據適配器工作原理的不同,被適配的日誌框架並不是一定要刪除!以上圖為例,log4j這個日誌框架刪不刪都可以,你只要能保證log4j的加載順序在log4j-over-slf4j後即可。因為log4j-over-slf4j這個適配器的工作原理是,內部提供了和log4j一模一樣的api接口,因此你在程序中調用log4j的api的時候,你必須想辦法讓其走適配器的api。如果你刪了log4j這個框架,那你程序裡肯定是走log4j-over-slf4j這個組件裡的api。如果不刪log4j,只要保證其在classpth裡的順序比log4j前即可!

案例2:如何讓spring以log4j2的形式輸出?

spring默認使用的是jcl輸出日誌,由於你此時並沒有引入Log4j的日誌框架,jcl會以jul做為日誌框架。此時集成圖如下

架構師必備,帶你弄清混亂的JAVA日誌體系

而你的應用中,採用了slf4j+log4j-core,即log4j2進行日誌記錄,那麼此時集成圖如下

架構師必備,帶你弄清混亂的JAVA日誌體系

那我們現在需要讓spring以log4j2的形式輸出?怎麼辦?

OK,第一種方案,走jcl-over-slf4j適配器,此時集成圖就變成下面這樣了

架構師必備,帶你弄清混亂的JAVA日誌體系

在這種方案下,spring框架中遇到日誌輸出的語句,就會如上圖紅線流程一樣,最終以log4J2的形式輸出!

OK,有第二種方案麼?

有,走jul-to-slf4j適配器,此時集成圖如下

架構師必備,帶你弄清混亂的JAVA日誌體系

ps:這種情況下,記得在代碼中執行

SLF4JBridgeHandler.removeHandlersForRootLogger();
SLF4JBridgeHandler.install();

這樣jul-to-slf4j適配器才能正常工作。

天啦嚕!要死循環

假設,我們在應用中調用了sl4j-api,但是呢,你引了四個jar包,slf4j-api-xx.jar,slf4j-log4j12-xx.jar,log4j-xx.jar,log4j-over-slf4j-xx.jar,於是你就會出現如下尷尬的場面

"

引言

還在為弄不清commons-logging-xx.jar、log4j-xx.jar、sl4j-api-xx.jar等日誌框架之間複雜的關係而感到煩惱嗎?

還在為如何統一系統的日誌輸出而感到不知所措嘛?

您是否依然存在這樣的煩惱。比如,要更改spring的日誌輸出為log4j 2,卻不知該引哪些jar包,只知道去百度一下所謂的博客,照著人家複製,卻無法弄懂其中的原理?

不要急,不要方!本文帶你們弄懂其中的原理,只要你靜下心看本文,你就能隨心所欲更改你係統裡的日誌框架,統一日誌輸出!

正文

日誌框架發展史

早年,你工作的時候,在日誌裡使用了log4j框架來輸出,於是你代碼是這麼寫的

import org.apache.log4j.Logger;
\\\\省略
Logger logger = Logger.getLogger(Test.class);
logger.trace("trace");
\\\\省略

但是,歲月流逝,sun公司對於log4j的出現內心隱隱表示嫉妒。於是在jdk1.4版本後,增加了一個包為java.util.logging,簡稱為jul,用以對抗log4j。於是,你的領導要你把日誌框架改為jul,這時候你只能一行行的將log4j的api改為jul的api,如下所示

import java.util.logging.Logger;
\\\\省略
Logger loggger = Logger.getLogger(Test.class.getName());
logger.finest("finest");
\\\\省略

可以看出,api完全是不同的。那有沒有辦法,將這些api抽象出接口,這樣以後調用的時候,就調用這些接口就好了呢?

這個時候jcl(Jakarta Commons Logging)出現了,說jcl可能大家有點陌生,講commons-logging-xx.jar組件,大家總有印象吧。JCL 只提供 log 接口,具體的實現則在運行時動態尋找。這樣一來組件開發者只需要針對 JCL 接口開發,而調用組件的應用程序則可以在運行時搭配自己喜好的日誌實踐工具。JCL可以實現的集成方案如下圖所示

架構師必備,帶你弄清混亂的JAVA日誌體系

jcl默認的配置:如果能找到Log4j 則默認使用log4j 實現,如果沒有則使用jul(jdk自帶的) 實現,再沒有則使用jcl內部提供的SimpleLog 實現。

於是,你在代碼裡變成這麼寫了

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
\\\\省略
Log log =LogFactory.getLog(Test.class);
log.trace('trace');
\\\\省略

至於這個Log具體的實現類,JCL會在ClassLoader中進行查找。這麼做,有三個缺點,缺點一是效率較低,二是容易引發混亂,三是在使用了自定義ClassLoader的程序中,使用JCL會引發內存洩露。

於是log4j的作者覺得jcl不好用,自己又寫了一個新的接口api,那麼就是slf4j。關於slf4j的集成圖如下所示

架構師必備,帶你弄清混亂的JAVA日誌體系

如圖所示,應用調了sl4j-api,即日誌門面接口。日誌門面接口本身通常並沒有實際的日誌輸出能力,它底層還是需要去調用具體的日誌框架API的,也就是實際上它需要跟具體的日誌框架結合使用。由於具體日誌框架比較多,而且互相也大都不兼容,日誌門面接口要想實現與任意日誌框架結合可能需要對應的橋接器,上圖紅框中的組件即是對應的各種橋接器!

我們在代碼中需要寫日誌,變成下面這麼寫

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
//省略
Logger logger = LoggerFactory.getLogger(Test.class);
// 省略
logger.info("info");

在代碼中,並不會出現具體日誌框架的api。程序根據classpath中的橋接器類型,和日誌框架類型,判斷出logger.info應該以什麼框架輸出!注意了,如果classpath中不小心引了兩個橋接器,那會直接報錯的!

因此,在阿里的開發手冊上才有這麼一條

強制:應用中不可直接使用日誌系統(log4j、logback)中的 API ,而應依賴使用日誌框架 SLF4J 中的 API 。使用門面模式的日誌框架,有利於維護和各個類的日誌處理方式的統一。

ok,至此,基礎知識完畢,下面是實戰!

項目實戰

案例1:一個項目,一個模塊用log4j,另一個模塊用slf4j+log4j2,如何統一輸出?

其實在某些中小型公司,這種情況很常見。我曾經見過某公司的項目,因為研發不懂底層的日誌原理,日誌文件裡頭既有log4j.properties,又有log4j2.xml,各種API混用,慘不忍睹!

還有人用著jul的API,然後拿著log4j.properties,跑來問我,為什麼配置不生效!簡直是一言難盡!

OK,回到我們的問題,如何統一輸出!OK,這裡就要用上slf4j的適配器,slf4j提供了各種各樣的適配器,用來將某種日誌框架委託給slf4j。其最明顯的集成工作方式有如下:

架構師必備,帶你弄清混亂的JAVA日誌體系

進行選擇填空,將我們的案例裡的條件填入,顯然應該選log4j-over-slf4j適配器,就變成下面這張圖

架構師必備,帶你弄清混亂的JAVA日誌體系

就可以實現日誌統一為log4j2來輸出!

ps:根據適配器工作原理的不同,被適配的日誌框架並不是一定要刪除!以上圖為例,log4j這個日誌框架刪不刪都可以,你只要能保證log4j的加載順序在log4j-over-slf4j後即可。因為log4j-over-slf4j這個適配器的工作原理是,內部提供了和log4j一模一樣的api接口,因此你在程序中調用log4j的api的時候,你必須想辦法讓其走適配器的api。如果你刪了log4j這個框架,那你程序裡肯定是走log4j-over-slf4j這個組件裡的api。如果不刪log4j,只要保證其在classpth裡的順序比log4j前即可!

案例2:如何讓spring以log4j2的形式輸出?

spring默認使用的是jcl輸出日誌,由於你此時並沒有引入Log4j的日誌框架,jcl會以jul做為日誌框架。此時集成圖如下

架構師必備,帶你弄清混亂的JAVA日誌體系

而你的應用中,採用了slf4j+log4j-core,即log4j2進行日誌記錄,那麼此時集成圖如下

架構師必備,帶你弄清混亂的JAVA日誌體系

那我們現在需要讓spring以log4j2的形式輸出?怎麼辦?

OK,第一種方案,走jcl-over-slf4j適配器,此時集成圖就變成下面這樣了

架構師必備,帶你弄清混亂的JAVA日誌體系

在這種方案下,spring框架中遇到日誌輸出的語句,就會如上圖紅線流程一樣,最終以log4J2的形式輸出!

OK,有第二種方案麼?

有,走jul-to-slf4j適配器,此時集成圖如下

架構師必備,帶你弄清混亂的JAVA日誌體系

ps:這種情況下,記得在代碼中執行

SLF4JBridgeHandler.removeHandlersForRootLogger();
SLF4JBridgeHandler.install();

這樣jul-to-slf4j適配器才能正常工作。

天啦嚕!要死循環

假設,我們在應用中調用了sl4j-api,但是呢,你引了四個jar包,slf4j-api-xx.jar,slf4j-log4j12-xx.jar,log4j-xx.jar,log4j-over-slf4j-xx.jar,於是你就會出現如下尷尬的場面

架構師必備,帶你弄清混亂的JAVA日誌體系

如上圖所示,在這種情況下,你調用了slf4j-api,就會陷入死循環中!slf4j-api去調了slf4j-log4j12,slf4j-log4j12又去調用了log4j,log4j去調用了log4j-over-slf4j。最終,log4j-over-slf4j又調了slf4j-api,陷入死循環!

作者:孤獨煙 出處: http://rjzheng.cnblogs.com/本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。

"

相關推薦

推薦中...