阿里巴巴開發(fā)手冊強(qiáng)制使用SLF4J作為門面擔(dān)當(dāng)?shù)拿孛?,我搞清楚?/h1>
之前已經(jīng)詳細(xì)、全面地介紹了 Log4j,相信小伙伴們已經(jīng)完全掌握了。那我在讀嵩山版的阿里巴巴開發(fā)手冊(沒有的小伙伴,記著找我要)的時候,就發(fā)現(xiàn)了一條「強(qiáng)制」性質(zhì)的日志規(guī)約:
應(yīng)用中不可以直接使用日志系統(tǒng)(Log4j、Logback)中的 API,而應(yīng)該使用日志框架中的 API,比如說 SLF4J,使用門面模式的日志框架,有利于維護(hù)和統(tǒng)一各個類的日志處理方式。
(為什么我把這段文字手敲了下來呢,因?yàn)槲野l(fā)現(xiàn)阿里巴巴開發(fā)手冊上的有語病,瞧下面紅色標(biāo)出的部分)
(維護(hù)和統(tǒng)一,把統(tǒng)一放在最后面讀起來真的是別扭,和的有點(diǎn)牽強(qiáng),請問手冊的小編是數(shù)學(xué)老師教的語文吧?)
那看到這條強(qiáng)制性的規(guī)約,我就忍不住想要問:“為什么阿里巴巴開發(fā)手冊會強(qiáng)制使用 SLF4J 作為 Log4J 的門面擔(dān)當(dāng)呢?”究竟這背后藏了什么“不可告人”的秘密?
(請小伙伴們自行配上 CCTV 12 臺的那種 BGM)
PS:順帶給小伙伴們普及一點(diǎn)小知識,阿里巴巴開發(fā)手冊上出現(xiàn)的 Jakarta 其實(shí)是 Apache 軟件基金會下的一個開源項(xiàng)目。其實(shí) Commons 是以前隸屬于 Jakarta,現(xiàn)在是作為 Apache 下的一個單獨(dú)項(xiàng)目,阿里巴巴開發(fā)手冊上的描述已經(jīng)不太恰當(dāng)了,換成是 Apache Commons Logging 會更合適一點(diǎn)。
(忍不住又給阿里巴巴開發(fā)手冊挑了一個毛病,請原諒我“一絲不茍”的做事態(tài)度)
01、SLF4J 是什么
SLF4J 是 Simple Logging Facade for Java 的縮寫(for≈4),也就是簡易的日志門面,以外觀模式(Facade pattern,一種設(shè)計模式,為子系統(tǒng)中的一組接口提供一個統(tǒng)一的高層接口,使得子系統(tǒng)更容易使用)實(shí)現(xiàn),支持 java.util.logging、Log4J 和 Logback。
SLF4J 的作者就是 Log4J 和 Logback 的作者,他的 GitHub 主頁長下面這樣:
一股秋風(fēng)瑟瑟的清冷感撲面而來,有沒有?可能巨佬不屑于維護(hù)他的 GitHub 主頁吧?我的 GitHub 主頁夠凄慘了,沒想到巨佬比我還慘,終于可以吹牛逼地說,“我,沉默王二,GitHub 主頁比 SLF4J、Log4J 和 Logback 的作者 Ceki Gulcu 綠多了。。。。。。”
1996 年初,歐洲安全電子市場項(xiàng)目決定編寫自己的跟蹤 API,最后該 API 演變成了 Log4j,已經(jīng)推出就備受寵愛。
2002 年 2 月,Sun 推出了自己的日志包 java.util.logging(可稱 JUL),據(jù)說實(shí)現(xiàn)思想借鑒了 Log4j,畢竟此時的 Log4j 已經(jīng)很成熟了。
2002 年 8 月,Apache 就推出了自己的日志包,也就是阿里巴巴開發(fā)手冊上提到的 JCL(Jakarta Commons Logging)。JCL 的野心很大,它在 JUL 和 Log4j 的基礎(chǔ)上提供了一個抽象層的接口,方便使用者在 JUL 和 Log4j 之間切換。
但 JCL 好像并不怎么招人喜歡,有人是這樣抱怨的:
Ceki Gulcu 也覺得 JCL 不好,要不然他也不會在 2005 年自己擼一個名叫 SLF4J 的新項(xiàng)目,對吧?但出來混總是要付出代價的,SLF4J 只有接口,沒有實(shí)現(xiàn),總不能強(qiáng)逼著 Java 和 Apache 去實(shí)現(xiàn) SLF4J 接口吧?這太難了,不現(xiàn)實(shí)。
但巨佬之所以稱之為巨佬,是因?yàn)樗麚碛谐銎胀ㄈ说捏@人之處,他在 SLF4J 和 JUL、Log4j、JCL 之間搭了三座橋:
巨佬動手,豐衣足食,有沒有?狠起來連自己的 Log4j 都搭個橋。
面對巨佬的霸氣,我只想弱弱地說一句,“ SLF4J 這個門面擔(dān)當(dāng),你以為好當(dāng)?shù)陌?”
02、SLF4J 解決了什么痛點(diǎn)
春秋戰(zhàn)國的時候,每個國家都有自己的貨幣,用別國的貨幣也不合適,對吧?那在發(fā)生貿(mào)易的時候就比較麻煩了,貨幣不統(tǒng)一,就沒法直接交易,因?yàn)樨泿趴赡懿坏葍r。
那秦始皇統(tǒng)一六國后,就推出了新的貨幣政策,全國都用一種貨幣,那之前的問題就解決掉了。
你看,同樣的道理,日志系統(tǒng)有 JUL、JCL,Ceki Gulcu 自己又寫了 2 種,Log4j 和 Logback,各有各的優(yōu)缺點(diǎn),再加上使用者千千萬,蘿卜白菜各有所愛,這就導(dǎo)致不同的應(yīng)用可能會用不同的日志系統(tǒng)。
假設(shè)我們正在開發(fā)一套系統(tǒng),打算用 SLF4J 作為門面,Log4j 作為日志系統(tǒng),我們在項(xiàng)目中使用了 A 框架,而 A 框架的門面是 JCL,日志系統(tǒng)是 JUL,那就相等于要維護(hù)兩套日志系統(tǒng),對吧?
這就難受了!
Ceki Gulcu 想到了這個問題,并且?guī)臀覀兘鉀Q了!來看 SLF4J 官網(wǎng)給出的解決方案。
- 使用 jcl-over-slf4j.jar 替換 commons-logging.jar
- 引入 jul-to-slf4j.jar
為了模擬這個過程,我們來建一個使用 JCL 的項(xiàng)目。
第一步,在 pom.xml 文件中引入 commons-logging.jar:
- <dependency>
- <groupId>commons-logging</groupId>
- <artifactId>commons-logging</artifactId>
- <version>1.2</version>
- </dependency>
第二步,新建測試類:
- package com.itwanger;
- import org.apache.commons.logging.Log;
- import org.apache.commons.logging.LogFactory;
- /**
- * @author 微信搜「沉默王二」,回復(fù)關(guān)鍵字 PDF
- */
- public class Demo {
- private static Log logger = LogFactory.getLog(Demo.class);
- public static void main(String[] args) {
- logger.info("jcl");
- }
- }
該類會通過 LogFactory 獲取一個 Log 對象,并且使用 info() 方法打印一行日志。
調(diào)試這段代碼的過程中你會發(fā)現(xiàn),Log 的實(shí)現(xiàn)有四種:
如果沒有綁定 Log4j 的話,就會默認(rèn)選擇 Jdk14Logger——它返回的 Logger 對象,正是 java.util.logging.Logger,也就是 JUL。
因此,就可以在控制臺看到以下信息:
- 10月 21, 2020 3:13:30 下午 com.itwanger.Demo main
- 信息: jcl
怎么把使用 JCL 的項(xiàng)目改造成使用 SLF4J 的呢?
第三步,使用 jcl-over-slf4j.jar 替換 commons-logging.jar,并加入 jul-to-slf4j.jar、slf4j-log4j12.jar(會自動引入 slf4j-api.jar 和 log4j.jar):
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>jcl-over-slf4j</artifactId>
- <version>1.7.25</version>
- </dependency>
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>jul-to-slf4j</artifactId>
- <version>1.7.29</version>
- </dependency>
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-log4j12</artifactId>
- <version>1.7.25</version>
- </dependency>
第四步,在 resources 目錄下創(chuàng)建 log4j.properties 文件,內(nèi)容如下所示:
- ### 設(shè)置###
- log4j.rootLogger = debug,stdout,D
- ### 輸出信息到控制臺 ###
- log4j.appender.stdout = org.apache.log4j.ConsoleAppender
- log4j.appender.stdout.Target = System.out
- log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
- log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
- ### 輸出DEBUG 級別以上的日志到=debug.log ###
- log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
- log4j.appender.D.File = debug.log
- log4j.appender.D.Append = true
- log4j.appender.D.Threshold = DEBUG
- log4j.appender.D.layout = org.apache.log4j.PatternLayout
- log4j.appender.D.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
再次運(yùn)行 Demo 類,你會發(fā)現(xiàn) target 目錄下會生成一個名叫 debug.log 的文件,內(nèi)容如下所示:
- 2020-10-21 15:32:06 [ main:0 ] - [ INFO ] jcl
并且可以在控制臺看到以下信息:
- [INFO ] 2020-10-21 15:32:06,192 method:com.itwanger.Demo.main(Demo.java:12)
- jcl
仔細(xì)對比一下,你就會發(fā)現(xiàn),這次輸出的格式和之前不一樣,這就是因?yàn)?Log4j 和 JUL 的日志格式不同導(dǎo)致的。
另外,你有沒有發(fā)現(xiàn)?我們并沒有改動測試類 Demo,它里面使用的仍然是 JCL 獲取 Log 的方式:
- private static Log logger = LogFactory.getLog(Demo.class);
但輸出的格式已經(jīng)切換到 Log4j 了!
SLF4J 除了提供這種解決方案,綁定 Log4j 替換 JUL 和 JCL;還提供了綁定 Logback 替換 JUL、JCL、Log4j 的方案:
還有綁定 JUL 替換 JCL 和 Log4j 的方案:
太強(qiáng)了,有木有?有的話請在留言區(qū)敲出 666。
03、SLF4J 比 Log4J 強(qiáng)在哪
SLF4J 除了解決掉以上的痛點(diǎn),幫助我們的應(yīng)用程序獨(dú)立于任何特定的日志系統(tǒng),還有一個非常牛逼的功能,那就是 SLF4J 在打印日志的時候使用了占位符 {},它有點(diǎn)類似于 String 類的 format() 方法(使用 %s 等填充參數(shù)),但更加便捷,這在很大程度上提高了程序的性能。
眾所周知,字符串是不可變的,字符串拼接會創(chuàng)建很多不必要的字符串對象,極大的消耗了內(nèi)存空間。但 Log4J 在打印帶參數(shù)的日志時,只能使用字符串拼接的方式:
- String name = "沉默王二";
- int age = 18;
- logger.debug(name + ",年紀(jì):" + age + ",是個非常不要臉的程序員");
非常笨重,但加入了 SLF4J 后,這個問題迎刃而解。我們來看一下在 Log4j 項(xiàng)目中加入 SLF4J 的詳細(xì)的步驟。
第一步,把 log4j 的依賴替換為 slf4j-log4j12(Maven 會自動引入 slf4j-api.jar 和 log4j.jar):
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-log4j12</artifactId>
- <version>1.7.25</version>
- </dependency>
第二步,在 resources 目錄下創(chuàng)建 log4j.properties 文件,內(nèi)容和 Log4j 那一篇完全相同:
- ### 設(shè)置###
- log4j.rootLogger = debug,stdout,D,E
- ### 輸出信息到控制臺 ###
- log4j.appender.stdout = org.apache.log4j.ConsoleAppender
- log4j.appender.stdout.Target = System.out
- log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
- log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
- ### 輸出DEBUG 級別以上的日志到=debug.log ###
- log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
- log4j.appender.D.File = debug.log
- log4j.appender.D.Append = true
- log4j.appender.D.Threshold = DEBUG
- log4j.appender.D.layout = org.apache.log4j.PatternLayout
- log4j.appender.D.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
- ### 輸出ERROR 級別以上的日志到=error.log ###
- log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
- log4j.appender.E.File =error.log
- log4j.appender.E.Append = true
- log4j.appender.E.Threshold = ERROR
- log4j.appender.E.layout = org.apache.log4j.PatternLayout
- log4j.appender.E.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
第三步,新建測試類:
- package com.itwanger;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- /**
- * @author 微信搜「沉默王二」,回復(fù)關(guān)鍵字 PDF
- */
- public class Log4jSLF4JDemo {
- private static final Logger logger = LoggerFactory.getLogger(Log4jSLF4JDemo.class);
- public static void main(String[] args) {
- logger.debug("{},是個非常不要臉的程序員","沉默王二");
- }
- }
看到了吧,使用占位符要比“+”操作符方便的多。并且此時不再需要 isDebugEnabled() 先進(jìn)行判斷,debug() 方法會在字符串拼接之前執(zhí)行。
如果只是 Log4J 的話,會先進(jìn)行字符串拼接,再執(zhí)行 debug() 方法,來看示例代碼:
- String name = "沉默王二";
- int age = 18;
- logger.debug(name + ",年紀(jì):" + age + ",是個非常不要臉的程序員");
在調(diào)試這段代碼的時候,你會發(fā)現(xiàn)的,如下圖所示:
這也就意味著,如果日志系統(tǒng)的級別不是 DEBUG,就會多執(zhí)行了字符串拼接的操作,白白浪費(fèi)了性能。
注意,阿里巴巴開發(fā)手冊上還有一條「強(qiáng)制」級別的規(guī)約:
這是因?yàn)槿绻麉?shù)是基本數(shù)據(jù)類型的話,會先進(jìn)行自動裝箱(Integer.valueOf())。測試代碼如下所示:
- logger.debug("沉默王二,{}歲", 18);
通過反編譯工具就可以看得到:
- logger.debug("\u6C89\u9ED8\u738B\u4E8C\uFF0C{}\u5C81", Integer.valueOf(18));
如果參數(shù)需要調(diào)用其他方法的話,debug() 方法會隨后調(diào)用。
也就是說,如果不 isDebugEnabled() 的話,在不是 DEBUG 級別的情況下,會多執(zhí)行自動裝箱和調(diào)用其他方法的操作——程序的性能就下降了!
測試類運(yùn)行的結(jié)果和之前 Log4J 的一樣,小伙伴們可以點(diǎn)擊鏈接跳轉(zhuǎn)到 Log4j 那篇對比下。
04、總結(jié)
簡單總結(jié)一下這篇文章哈。
1)在使用日志系統(tǒng)的時候,一定要使用 SLF4J 作為門面擔(dān)當(dāng)。
2)SLF4J 可以統(tǒng)一日志系統(tǒng),作為上層的抽象接口,不需要關(guān)注底層的日志實(shí)現(xiàn),可以是 Log4j,也可以是 Logback,或者 JUL、JCL。
3)SLF4J 在打印日志的時候可以使用占位符,既提高了程序性能(臨時字符串少了,垃圾回收的工作量就小),又讓代碼變得美觀統(tǒng)一。
本文轉(zhuǎn)載自微信公眾號「沉默王二」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系沉默王二公眾號。