深刻掌握Java日誌體系,不再迷路了

點贊再看,養成習慣,公衆號搜一搜【一角錢技術】關注更多原創技術文章。本文 GitHub org_hejianhui/JavaStudy 已收錄,有個人系列文章。html

前言

對於一個應用程序來講日誌記錄是必不可少的一部分。線上問題追蹤,基於日誌的業務邏輯統計分析等都離不日誌。java領域存在多種日誌框架,目前經常使用的日誌框架包括Log4j 1,Log4j 2,Commons Logging,Slf4j,Logback,Jul。可是在咱們的系統裏面到底該怎麼使用日誌框架?還在爲弄不清commons-logging.jar、log4j.jar、sl4j-api.jar等日誌框架之間複雜的關係而感到煩惱嗎?還在爲如何統一系統的日誌輸出而感到不知所措嘛?好比,要更改Spring的日誌輸出爲Log4j 2,殊不知該引哪些jar包,只知道去百度一下所謂的博客,照着人家複製,卻沒法弄懂其中的原理?本文將弄懂其中的原理,只要你靜下心看本文,你就能爲所欲爲更改你係統裏的日誌框架,統一日誌輸出!java

日誌框架類別

記錄型日誌框架

  1. Jul (Java Util Logging):JDK中的日誌記錄工具,也常稱爲JDKLog、jdk-logging,自Java1.4以來的官方日誌實現。
  2. Log4j:Apache Log4j是一個基於Java的日誌記錄工具。它是由Ceki Gülcü獨創的,如今則是Apache軟件基金會的一個項目。 Log4j是幾種Java日誌框架之一。
  3. Log4j2:一個具體的日誌實現框架,是Log4j 1的下一個版本,與Log4j 1發生了很大的變化,Log4j 2不兼容Log4j 1
  4. Logback:一個具體的日誌實現框架,和Slf4j是同一個做者,但其性能更好(推薦使用)。

門面型日誌框架

  1. JCL:Apache基金會所屬的項目,是一套Java日誌接口,以前叫Jakarta Commons Logging,後改名爲Commons Logging
  2. SLF4J:是一套簡易Java日誌門面,自己並沒有日誌的實現。(Simple Logging Facade for Java,縮寫Slf4j)

看到這麼多日誌框架是否會以爲比較混亂,這些日誌框架之間有什麼異同,都是由誰在維護,在項目中應該如何選擇日誌框架,應該如何使用? 不要急,咱們先把這些術語概念先有個印象。讓咱們先了解一它們的發展歷史。git

日誌框架發展史

Java日誌的恩怨情仇

  • 1996年早期,歐洲安全電子市場項目組決定編寫它本身的程序跟蹤API(Tracing API)。通過不斷的完善,這個API終於成爲一個十分受歡迎的Java日誌軟件包,即Log4j(由Ceki建立)。
  • 後來Log4j成爲Apache基金會項目中的一員,Ceki也加入Apache組織。後來Log4j近乎成了Java社區的日誌標準。聽說Apache基金會還曾經建議Sun引入Log4j到Java的標準庫中,但Sun拒絕了。
  • 2002年Java1.4發佈,Sun推出了本身的日誌庫JUL(Java Util Logging),其實現基本模仿了Log4j的實現。在JUL出來之前,Log4j就已經成爲一項成熟的技術,使得Log4j在選擇上佔據了必定的優點。
  • 接着,Apache推出了Jakarta Commons Logging,JCL只是定義了一套日誌接口(其內部也提供一個Simple Log的簡單實現),支持運行時動態加載日誌組件的實現,也就是說,在你應用代碼裏,只需調用Commons Logging的接口,底層實現能夠是Log4j,也能夠是Java Util Logging。
  • 後來(2006年),Ceki不適應Apache的工做方式,離開了Apache。而後前後建立了Slf4j(日誌門面接口,相似於Commons Logging)和Logback(Slf4j的實現)兩個項目,並回瑞典建立了QOS公司,QOS官網上是這樣描述Logback的:The Generic,Reliable Fast&Flexible Logging Framework(一個通用,可靠,快速且靈活的日誌框架)。
  • Java日誌領域被劃分爲兩大陣營:Commons Logging陣營和Slf4j陣營。
  • Commons Logging在Apache大樹的籠罩下,有很大的用戶基數。但有證據代表,形式正在發生變化。2013年末有人分析了GitHub上30000個項目,統計出了最流行的100個Libraries,能夠看出Slf4j的發展趨勢更好。
  • Apache眼看有被Logback反超的勢頭,於2012-07重寫了Log4j 1.x,成立了新的項目Log4j 2, Log4j 2具備Logback的全部特性。

大神Ceki github

log4j

早年,你工做的時候,在日誌裏使用了log4j框架來輸出,因而你代碼是這麼寫的spring

import org.apache.log4j.Logger;
//省略...
Logger logger = Logger.getLogger(Test.class);
logger.trace("trace");
//省略...
複製代碼

jul

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

import java.util.logging.Logger;
//省略...
Logger loggger = Logger.getLogger(Test.class.getName()); 
logger.finest("finest");
//省略...
複製代碼

能夠看出,api徹底是不一樣的。那有沒有辦法,將這些api抽象出接口,這樣之後調用的時候,就調用這些接口就行了呢?設計模式

jcl

這個時候jcl(Jakarta Commons Logging)出現了,說jcl可能你們有點陌生,講commons-logging-xx.jar組件,你們總有印象吧。JCL 只提供 log 接口,具體的實現則在運行時動態尋找。這樣一來組件開發者只須要針對 JCL 接口開發,而調用組件的應用程序則能夠在運行時搭配本身喜愛的日誌實踐工具。JCL能夠實現的集成方案以下圖所示 jcl默認的配置:若是能找到Log4j 則默認使用log4j 實現,若是沒有則使用jul(jdk自帶的) 實現,再沒有則使用jcl內部提供的SimpleLog 實現。api

因而,你在代碼裏變成這麼寫了數組

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會引起內存泄露。

slf4j

因而log4j的做者(Ceki)以爲jcl很差用,本身又寫了一個新的接口api,那麼就是slf4j。

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

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 。使用門面模式的日誌框架,有利於維護和各個類的日誌處理方式的統一。

Slf4j的使用

Slf4j與其它日誌組件的關係說明

  • Slf4j的設計思想比較簡潔,使用了Facade設計模式,Slf4j自己只提供了一個slf4j-api-version.jar包,這個jar中主要是日誌的抽象接口,jar中自己並無對抽象出來的接口作實現。
  • 對於不一樣的日誌實現方案(例如Logback,Log4j...),封裝出不一樣的橋接組件(例如logback-classic-version.jar,slf4j-log4j12-version.jar),這樣使用過程當中能夠靈活的選取本身項目裏的日誌實現。

Slf4j與其它日誌組件集成圖

如圖所示,應用調了sl4j-api,即日誌門面接口。日誌門面接口自己一般並無實際的日誌輸出能力,它底層仍是須要去調用具體的日誌框架API的,也就是實際上它須要跟具體的日誌框架結合使用。因爲具體日誌框架比較多,並且互相也大都不兼容,日誌門面接口要想實現與任意日誌框架結合可能須要對應的橋接器,上圖紅框中的組件便是對應的各類橋接器!

Slf4j與其餘各類日誌組件的橋接說明

jar包名 說明
slf4j-log4j12-1.7.30.jar Log4j1.2版本的橋接器,你須要將Log4j.jar加入Classpath。
slf4j-jdk14-1.7.30.jar java.util.logging的橋接器,Jdk原生日誌框架。
slf4j-nop-1.7.30.jar NOP橋接器,默默丟棄一切日誌。
slf4j-simple-1.7.30.jar 一個簡單實現的橋接器,該實現輸出全部事件到System.err. 只有Info以及高於該級別的消息被打印,在小型應用中它也許是有用的。
slf4j-jcl-1.7.30.jar Jakarta Commons Logging 的橋接器. 這個橋接器將Slf4j全部日誌委派給Jcl。
logback-classic-1.0.13.jar(requires logback-core-1.0.13.jar) Slf4j的原生實現,Logback直接實現了Slf4j的接口,所以使用Slf4j與Logback的結合使用也意味更小的內存與計算開銷

具體的介入方式參考下圖

Slf4j源碼分析

slf4j-api-version.jar中幾個核心類與接口

類與接口 用途
org.slf4j.LoggerFactory(class) 給調用方提供的建立Logger的工廠類,在編譯時綁定具體的日誌實現組件
org.slf4j.Logger(interface) 給調用方提供的日誌記錄抽象方法,例如debug(String msg),info(String msg)等方法
org.slf4j.ILoggerFactory(interface) 獲取的Logger的工廠接口,具體的日誌組件實現此接口
org.slf4j.helpers.NOPLogger(class) 對org.slf4j.Logger接口的一個沒有任何操做的實現,也是Slf4j的默認日誌實現
org.slf4j.impl.StaticLoggerBinder(class) 與具體的日誌實現組件實現的橋接類,具體的日誌實現組件須要定義org.slf4j.impl包,並在org.slf4j.impl包下提供此類,注意在slf4j-api-version.jar中不存在org.slf4j.impl.StaticLoggerBinder,在源碼包slf4j-api-version-source.jar中才存在此類

Slf4j調用過程源碼分析,只加入slf4j-api-version.jar,不加入任何實現包

pom配置

<!--只有slf4j-api依賴-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.30</version>
</dependency>
複製代碼

程序入口類

package com.niuh;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class App {
    final static Logger logger = LoggerFactory.getLogger(App.class);

    public static void main(String[] args) {
        logger.info("Hello World");
    }
}
複製代碼

源碼追蹤分析

  1. 調用LoggerFactory的getLogger()方法建立Logger

  1. 調用LoggerFactory的getILoggerFactory方法來建立ILoggerFactory

  1. 調用LoggerFactory的performInitialization方法來進行初始化

  1. 調用LoggerFactory的bind()方法

  1. 調用LoggerFactory的findPossibleStaticLoggerBinderPathSet()方法獲取StaticLoggerBinderPath集合

  1. 調用LoggerFactory的reportMultipleBindingAmbiguity()方法,記錄綁定的StaticLoggerBinder信息

  1. LoggerFactory的reportMultipleBindingAmbiguity()方法

  1. LoggerFactory的bind()方法找不到StaticLoggerBinder,拋出NoClassDefFoundError異常

  1. LoggerFactory的bind()方法捕獲NoClassDefFoundError異常,匹配到StaticLoggerBinder關鍵詞記錄信息到控制檯

  1. LoggerFactory的performInitialization()方法內部調用bind()方法結束

  1. LoggerFactory的getLogger()方法內部getILoggerFactory()方法調用完成,建立出NOPLoggerFactory,而後由NOPLoggerFactory調用內部的getLogger()方法,建立出NOPLogger

  1. App類內部的logger實際爲NOPLogger,調用logger.info()方法實際調用的是NOPLogger的info方法

Slf4j調用過程源碼分析,加入slf4j-api-version.jar,與Logback組件

Slf4j做爲門面採用Logback做爲實現或者採用其它上面提到過的組件做爲實現相似,這裏只分析採用Logback組件做爲實現

pom配置

<dependencies>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.7.30</version>
    </dependency>
    <!--logback-classic依賴logback-core,會自動級聯引入-->
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.2.3</version>
    </dependency>
</dependencies>
複製代碼

程序入口類

同上

源碼追蹤分析

  • 一、二、三、4同上
    1. 調用LoggerFactory的findPossibleStaticLoggerBinderPathSet()方法獲取StaticLoggerBinderPath集合

    1. 調用LoggerFactory的bind()方法的staticLoggerBinderPathSet集合對象賦值

    1. 在LoggerFactory的bind()方法中調用loback包下的StaticLoggerBinder建立單例對象

    1. 在LoggerFactory的bind()方法中調用reportActualBinding()記錄日誌加載信息

    1. LoggerFactory中INITIALIZATION_STATE的值爲SUCCESSFUL_INITIALIZATION,調用StaticLoggerBinder的單例對象獲取ILoggerFactory

    1. 此時LoggerFactory中的getLogger()方法中獲取到的ILoggerFactory其實是logback jar下的LoggerContext

    1. 此時LoggerFactory調用getLogger()方法獲取到的Logger其實是logback jar下的Logger

  • 最後輸出

Slf4j調用過程源碼分析,加入slf4j-api-version.jar,同時加入多種日誌實現組件

在項目中若是用slf4j-api做爲日誌門面,有多個日誌實現組件同時存在,例如同時存在Logback,slf4j-log4j12,slf4j-jdk14,slf4j-jcl四種實現,則在項目實際運行中,Slf4j的綁定選擇綁定方式將有Jvm肯定,而且是隨機的,這樣會和預期不符,實際使用過程當中須要避免這種狀況。

pom配置

<dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.7.30</version>
    </dependency>
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.2.3</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-jdk14</artifactId>
      <version>1.7.30</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-jcl</artifactId>
      <version>1.7.30</version>
    </dependency>
</dependencies>
複製代碼

程序入口類

同上

源碼追蹤分析

  • 基本步驟同上,這裏只追蹤主要不一樣點
  • (1) 追蹤LoggerFactory的bind()方法內部調用findPossibleStaticLoggerBinderPathSet()方法後,從classpath下4個jar包內找到StaticLoggerBinder

  • (2) 此時LoggerFactory的bind()方法內部調用reportMultipleBindingAmbiguity()方法,給出警告信息classpath下同時存在多個StaticLoggerBinder,JVM會隨機選擇一個StaticLoggerBinder

使用Slf4j時如何橋接遺留的api

在實際環境中咱們常常會遇到不一樣的組件使用的日誌框架不一樣的狀況,例如Spring Framework使用的是日誌組件是Commons Logging,XSocket依賴的則是Java Util Logging。當咱們在同一項目中使用不一樣的組件時應該若是解決不一樣組件依賴的日誌組件不一致的狀況呢?如今咱們須要統一日誌方案,統一使用Slf4j,把他們的日誌輸出重定向到Slf4j,而後Slf4j又會根據綁定器把日誌交給具體的日誌實現工具。Slf4j帶有幾個橋接模塊,能夠重定向Log4j,JCL和java.util.logging中的Api到Slf4j。

遺留的api橋接方案

jar包名 做用
log4j-over-slf4j-version.jar 將Log4j重定向到Slf4j
jcl-over-slf4j-version.jar 將Commons Logging裏的Simple Logger重定向到slf4j
jul-to-slf4j-version.jar 將Java Util Logging重定向到Slf4j

橋接方式參見下圖

使用Slf4j橋接注意事項

在使用Slf4j橋接時要注意避免造成死循環,在項目依賴的jar包中不要存在如下狀況。

多個日誌jar包造成死循環的條件 產生緣由
log4j-over-slf4j.jar和slf4j-log4j12.jar同時存在 因爲slf4j-log4j12.jar的存在會將全部日誌調用委託給log4j。但因爲同時因爲log4j-over-slf4j.jar的存在,會將全部對log4j api的調用委託給相應等值的slf4j,因此log4j-over-slf4j.jar和slf4j-log4j12.jar同時存在會造成死循環
jul-to-slf4j.jar和slf4j-jdk14.jar同時存在 因爲slf4j-jdk14.jar的存在會將全部日誌調用委託給jdk的log。但因爲同時jul-to-slf4j.jar的存在,會將全部對jul api的調用委託給相應等值的slf4j,因此jul-to-slf4j.jar和slf4j-jdk14.jar同時存在會造成死循環

JCL與Slf4j實現機制對比

前面介紹過門面型的日誌框架主要就兩個JCL(Commons Logging)和Slf4j,咱們來簡單瞭解下它們的區別:

JCL(Commons Logging)實現機制

JCL(Commons Logging) 是經過動態查找機制,在程序運行時,使用本身的ClassLoader尋找和載入本地具體的實現。詳細策略能夠查看commons-logging-*.jar包中的org.apache.commons.logging.impl.LogFactoryImpl.java文件。因爲Osgi不一樣的插件使用獨立的ClassLoader,Osgi的這種機制保證了插件互相獨立, 其機制限制了Commons Logging在Osgi中的正常使用。

Slf4j實現機制

Slf4j在編譯期間,靜態綁定本地的Log庫,所以能夠在Osgi中正常使用。它是經過查找類路徑下org.slf4j.impl.StaticLoggerBinder,而後在StaticLoggerBinder中進行綁定。

日誌實戰

案例一

一個項目,一個模塊用log4j,另外一個模塊用slf4j+log4j2,如何統一輸出?

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

還有人用着jul的API,而後拿着log4j.properties,跑來問我,爲何配置不生效!簡直是一言難盡!

OK,回到咱們的問題,如何統一輸出!OK,這裏就要用上slf4j的適配器,slf4j提供了各類各樣的適配器,用來將某種日誌框架委託給slf4j。其最明顯的集成工做方式有以下: 進行選擇填空,將咱們的案例裏的條件填入,根據題意應該選log4j-over-slf4j適配器,因而就變成下面這張圖

就能夠實現日誌統一爲log4j2來輸出!

PS: 根據適配器工做原理的不一樣,被適配的日誌框架並非必定要刪除!以上圖爲例,log4j這個日誌框架刪不刪均可以,你只要能保證log4j的加載順序在log4j-over-slf4j後便可。由於log4j-over-slf4j這個適配器的工做原理是,內部提供了和log4j如出一轍的api接口,所以你在程序中調用log4j的api的時候,你必須想辦法讓其走適配器的api。若是你刪了log4j這個框架,那你程序裏確定是走log4j-over-slf4j這個組件裏的api。若是不刪log4j,只要保證其在classpth裏的順序比log4j前便可!

案例二

如何讓Spring以log4j2的形式輸出?

Spring默認使用的是jcl輸出日誌,因爲你此時並無引入Log4j的日誌框架,jcl會以jul作爲日誌框架。此時集成圖以下 而你的應用中,採用了slf4j+log4j-core,即log4j2進行日誌記錄,那麼此時集成圖以下 那咱們如今須要讓Spring以log4j2的形式輸出?怎麼辦?

OK,第一種方案,走jcl-over-slf4j適配器,此時集成圖就變成下面這樣了 在這種方案下,spring框架中遇到日誌輸出的語句,就會如上圖紅線流程同樣,最終以log4J2的形式輸出!

OK,有第二種方案麼?

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

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,因而你就會出現以下尷尬的場面

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

spring4和spring5日誌中的不一樣

Spring4日誌體系

構建spring4項目,採用java+註解的方式快速構建,pom中只引入spring-context包

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>4.3.21.RELEASE</version>
    </dependency>
</dependencies>
複製代碼

運行下面的代碼,能夠看到有日誌輸出

public class MainClass {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
    }
}
複製代碼

找到打印日誌的地方,debug模式下,查看輸出日誌的Log是什麼log 能夠看出是jdk14Logger,這個在JCL中說過,這個指的是JUL,也就是說在默認spring日誌體系下,採用的是JUL,

接下來,咱們按照以前的方法引入log4j,debug運行上面的程序,再次查看日誌類型

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
複製代碼

額,此次在增長log4j jar包和配置文件的狀況下,spring4有使用了log4j,這麼像JCL呢,木錯,讓咱們在idea中打開spring4的日誌依賴結構: common-logging 這不就是JCL使用到的包嗎,能夠看出,Spring4使用的是原生的JCL,因此在有log4j的時候使用log4j打印日誌,沒有的時候使用JUL打印日誌。

Spring5日誌體系

依賴結構圖:

大致結構沒變,只是原來common-logging ,換成了spring-jcl,看名字就知道是spring自造的包,jcl,更是標註了,它使用的是JCL日誌體系。

咱們仍是經過看源碼來驗證,咱們只用debug找到spring內部一個Log,看看他的產生方式和類型。此次我給你們找了AbstractApplicationContext裏面找到產生Log的地方

進入這個方法的getLog()中,一直深刻,找到LogAdapter中的createLog()方法 能夠看出來Spring5中對日誌的生產,不在像原生JCL中那樣使用一個數組,而後進行循環產生,這裏用到的是Switch case,這個關鍵字段logApi又是在哪一部分賦值的呢?以下所示: 咱們看到是在靜態代碼塊中賦的值,爲了驗證,咱們準備用其中提到的log4j2驗證(注意:log4j不行,由於這裏的switch沒有log4j選項),首先咱們準備log4j2.xml的配置文件

<Configuration status="WARN">

    <Appenders>

        <Console name="Console" target="SYSTEM_OUT">

            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>

        </Console>

    </Appenders>

    <Loggers>

        <Root level="debug">

            <AppenderRef ref="Console"/>

        </Root>

    </Loggers>

</Configuration>
複製代碼

而後準備pom的依賴

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.8.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.10.0</version>
    </dependency>
</dependencies>
複製代碼

運行下面的代碼

public class MainClass {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
    }
}
複製代碼

結果有日誌打印出來了 因此,在Spring5中,依然使用的是JCL,可是不是原生的,是通過改造的JCL,默認使用的是JUL,而原生JCL中默認使用的是log4j。

項目中選擇日誌框架選擇

若是是在一個新的項目中建議使用Slf4j與Logback組合,這樣有以下的幾個優勢。

  • Slf4j實現機制決定Slf4j限制較少,使用範圍更廣。因爲Slf4j在編譯期間,靜態綁定本地的LOG庫使得通用性要比Commons Logging要好。

  • Logback擁有更好的性能。Logback聲稱:某些關鍵操做,好比斷定是否記錄一條日誌語句的操做,其性能獲得了顯著的提升。這個操做在Logback中須要3納秒,而在Log4J中則須要30納秒。LogBack建立記錄器(logger)的速度也更快:13毫秒,而在Log4J中須要23毫秒。更重要的是,它獲取已存在的記錄器只需94納秒,而Log4J須要2234納秒,時間減小到了1/23。跟JUL相比的性能提升也是顯著的。

  • Commons Logging開銷更高

# 在使Commons Logging時爲了減小構建日誌信息的開銷,一般的作法是
if(log.isDebugEnabled()){
  log.debug("User name: " +
    user.getName() + " buy goods id :" + good.getId());
}

# 在Slf4j陣營,你只需這麼作:
log.debug("User name:{} ,buy goods id :{}", user.getName(),good.getId());

# 也就是說,Slf4j把構建日誌的開銷放在了它確認須要顯示這條日誌以後,減小內存和Cup的開銷,使用佔位符號,代碼也更爲簡潔
複製代碼
  • Logback文檔免費。Logback的全部文檔是全面免費提供的,不象Log4J那樣只提供部分免費文檔而須要用戶去購買付費文檔。

參考資料

文章持續更新,能夠公衆號搜一搜「 一角錢技術 」第一時間閱讀, 本文 GitHub org_hejianhui/JavaStudy 已經收錄,歡迎 Star。

相關文章
相關標籤/搜索