天天看點

SLF4j + Log4j 、Log4j2、 Logback日志架構使用總結

作者:不三的二

從寫代碼開始,就陸陸續續接觸到了許多日志架構,常用的Log4j 、Log4j2、 Logback等。每次自己寫項目時,就copy别人的代碼或網上的demo。配置log4j.properties或者logback.xml 就能搞定。但是一直沒有理清各個架構之間的關系。然後總感覺列印日志的時候并不是随心所欲。特此簡單分析分析。

SLF4j + Log4j 、Log4j2、 Logback日志架構使用總結

常見的日志架構

SLF4j是日志門面,是一個日志标準,并非真正的日志實作,log4j、log4j2、logback才是真正的日志實作庫。也就是說log4j、log4j2、logback 這兄弟三才是 真正列印日志的。日志門面就是為了統一各個依賴的不同日志實作,便于我們自己項目中對類進行統一維護處理。

再來看看阿裡java開發規範:

1.【強制】應用中不可直接使用日志系統(Log4j、Logback)中的 API,而應依賴使用日志架構

SLF4] 中的 API,使用門面模式的日志架構,有利于維護和各個類的日志處理方式統一。

import org.slf4j. Logger;

import org.sLf4j. LoggerFactory;

private static final Logger Logger LoggerFactory.getLogger(Abc.class):

如題,這裡選用常用的SLF4j + Log4j 、Log4j2、 Logback 進行分析。

SLF4j + Log4j 、Log4j2、 Logback日志架構使用總結

SLF4j分析

slf4j是門面,大的設計模式是門面系統,而logback是直接實作了slf4j-api中的接口(已經預設實作了SLF4j的日志标準),是通過接口實作的方式完成了門面的實作。而log4j和log4j2沒有直接實作接口,是以需要個擴充卡。slf4j-log4j12和log4j-slf4j-impl就是擴充卡,将原本不能實作slf4j的變得也能實作這個标準了。添加了擴充卡後,就能使用slf4j的接口來使用log4j了。

以slf4j 的概念,共可劃分為以下4種庫:

日志接口層

slf4j-api

common-logging(apache提供的一個通用的日志接口)

jboss-logging(Hibernate內建的日志工具)

日志實作層

JUL(java.util.logging)

log4j

log4j2

logback

slf4j-simple

slf4j-nop

綁定适配(adaptation)層

slf4j-jcl

slf4j-jdk14

slf4j-log412(log4j實作slf标準)

log4j-slf4j-impl(log4j2實作slf标準)

橋接(bridge)層

jcl-over-slf4j

jul-to-slf4j

log4j-over-slf4j(log4j到slf4j的橋梁)

log4j-to-slf4j(log4j2到slf4j的橋梁)

commons-logging

commons-logging也被稱為JCL(Java Common Logging),是apache提供的一個通用的日志接口。使用者可以自由選擇第三方的日志元件作為具體實作,像log4j,或者JDK自帶的logging,commons-logging會通過動态查找的機制,在程式運作時自動找出真正使用的日志庫,規則是倘若沒有定義日志實作,則預設使用JUL日志架構作為日志實作,如果有log4j,則優先使用log4j作為日志實作。當然,commons-logging内部有一個Simple Logger的簡單實作,但是功能很弱。是以使用commons-logging,通常都是配合着log4j來使用。使用它的好處就是,代碼依賴是commons-logging而非log4j,避免了和具體的日志方案直接耦合,在有必要時,可以更改日志實作的第三方庫。

使用commons-logging的常見代碼:

import org.apache.commons.logging.Log; 
import org.apache.commons.logging.LogFactory; 
public class A { 
    private static Log logger = LogFactory.getLog(this.getClass());  
}

#使用JUL作為日志實作,僅需在maven工程中導入如下依賴:
 <dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.2</version>
</dependency>

#使用log4j作為日志實作,僅需在maven工程中導入如下依賴:
 <dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.2</version>
</dependency>
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j<artifactId>
    <version>1.2.17</version>
</dependency>           

jul-to-slf4j.jar和slf4j-jdk14.jar 二者不能同時存在

slf4j-jdk14.jar是jul到SLF4J的綁定,将強制SLF4J的調用配置設定給jul。另一方面,jul-to-slf4j.jar,加上SLF4JBridgeHandler的安裝,加上SLF4JBridgeHandler的安裝,通過調用“SLF4JBridgeHandler.install()“将jul記錄發送給SLF4J。是以,如果兩個jar檔案同時存在(SLF4JBridgeHandler已安裝),slf4的調用将被配置設定給jul, jul記錄将發送到SLF4J,導緻一個死循環。

各個庫單獨使用

  • log4j
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>           

classpath下配置檔案log4j.properties

log4j.rootLogger=INFO,console
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.target=System.out
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%p] %c: %m%n           

使用:

import org.apache.log4j.Logger;
...
static final Logger LOGGER = Logger.getLogger(Main.class);           
  • log4j2

classpath下log4j2.properties

rootLogger.level = info
rootLogger.appenderRef.stdout.ref = STDOUT

appender.console.type = Console
appender.console.name = STDOUT
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = %d{yyyy-MM-dd HH:mm:ss} [%p] %c: %m%n           

使用:

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
...
static final Logger LOGGER = LogManager.getLogger(Main.class);           
  • logback
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>           

classpath下logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%p] %c: %m%n</pattern>
        </encoder>
    </appender>
    <root level="debug">
        <appender-ref ref="console" />
    </root>
</configuration>           

使用:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
...
static final Logger LOGGER = LoggerFactory.getLogger(Main.class);           

注意:logback本身就是實作slf4j的,如上代碼中的logger本就是slf4j的。然而log4j 和 log4j2 的logger都不是slf4j的。當然是可以更新的

為啥要更新?是想要統一到一個門面标準更新:基礎庫如何更新到SLF4j标準log4j實作方式,引入slf4j-log4j12,即加一個擴充卡。

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.29</version>
</dependency>           

log4j2的實作方式,引入log4j-slf4j-impl ,也是加一個擴充卡

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.12.1</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.9.0</version>
</dependency>           

這樣組裝後就可以用slf4j的寫法了,統一了門面标準。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
...
static final Logger LOGGER = LoggerFactory.getLogger(Main.class);           

實際項目中,往往會依賴于其它的包,這些依賴包可能就是不同的日志實作方式

生産場景一:某項目依賴到的其餘三個包 的日志實作分别是 log4j、log4j2、logback 。

SLF4j + Log4j 、Log4j2、 Logback日志架構使用總結

這個時候我們不做統一處理的話,當然是不會沖突 報錯的,隻是列印的日志時候就會各自找各自的日志實作。

其中一種方式是可以 在我們的項目的redources下,新增 log4j.properties / log4j2.properties 和 logback.xml 共3個配置檔案來自定義 (覆寫) 日志輸出方式 ,這樣,輸出的日志也是會按照我們配置的進行正确輸出。

生産場景二:某項目依賴到的其餘三個包 的日志實作分别是 log4j log4j2 logback 。但是log4j 和 log4j2 已經統一了門面标準為SLF4J

SLF4j + Log4j 、Log4j2、 Logback日志架構使用總結

這個實作我們項目在列印日志的時候,就會根據類加載随機選擇一個日志實作,可能就是按照log4j的配置檔案實作方式進行日志的列印。

問題:這種情況如何指定特定的日志實作庫呢?假如期望項目使用logback,并期望統一為slf化的logback形式,隻配置一個logback.xml就能對所有依賴進行配置這個時候可以分别剔除A依賴包的和B依賴包。

<exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
            </exclusions>
      <exclusions>
                <exclusion>
                    <groupId>org.apache.logging.log4j</groupId>
                    <artifactId>log4j-slf4j-impl</artifactId>
                </exclusion>
            </exclusions>           

這個時候,隻需要在我們項目的resources目錄下配置一個logback.xml檔案即可。當然,很有可能我們在項目中是不清楚哪一個依賴包使用到了log4j 和 log4j2 的,這個時候我們是可以在 pom 檔案的最後 寫一個不存在的版本:

ps:maven加載方式按照最外層優先使用,如果引入一個不存在的,那麼maven就不會加載這個依賴了。

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>99.99.99</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>99.99.99</version>
</dependency>           

也是能夠達到去除slf化 ,進而使用統一的logback列印日志的效果的

生産場景三:某項目依賴到的其餘三個包 的日志實作分别是 log4j log4j2 logback 。但是log4j 和 log4j2 并沒有實作slf門面标準。但是又想要統一為slf化的logback形式。怎麼辦呢?

SLF4j + Log4j 、Log4j2、 Logback日志架構使用總結

也就是說要先對log4j 和 log4j2 進行slf 化才行。

思路:

1.先在整個項目下吧原有的log4j 和log4j2 剔除掉

2.再使用其它已經橋接好的log4j 和 log4j2 的包來代替slf 化的log4j 和 log4j2 的包

以下配置幾乎是萬能的,當遇到問題的時候,直接全部拷貝進去,穩定解決。

<!-- 處理單獨log4j的依賴: -->
<!-- 用log4j-over-slf4j替換log4j,使依賴中的log4j也能"實作"slf4j-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>log4j-over-slf4j</artifactId>
    <version>1.7.29</version>
</dependency>
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>99.99.99</version>
</dependency>

<!-- 處理單獨log4j2的依賴: -->
<!-- 用log4j-to-slf4j替換log4j2,使依賴中的log4j2也能"實作"slf4j -->
 <dependency>
    <groupId>org.apache.logging.log4j</groupId>
     <artifactId>log4j-to-slf4j</artifactId>
    <version>2.12.1</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>99.99.99</version>
</dependency>

<!-- 處理slf化的log4j的依賴: -->
<!-- 因為slf選binding的時候有多個候選,為防止slf4j-log4j12選中,直接去掉他 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>99.99.99</version>
</dependency>

<!-- 處理slf化的log4j2的依賴: -->
<!-- 因為slf選binding的時候有多個候選,為防止log4j-slf4j-impl選中,直接去掉他 -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>99.99.99</version>
</dependency>

<!-- 最後引個新版本的logback -->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>           

小結

  • slf4j-log4j12:與log4j聯合使用,用于使目前項目的log4j實作slf标準
  • log4j-slf4j-impl:與log4j2聯合使用,用于使目前項目的log4j2實作slf标準
  • log4j-over-slf4j:與剔除log4j聯合使用,替換log4j,使log4j實作slf。用于讓單獨用log4j的依賴能遵循slf,進而統一日志配置。
  • log4j-to-slf4j:與剔除log4j2聯合使用,替換log4j2,使log4j2實作slf。用于讓單獨用log4j2的依賴能遵循slf,進而統一日志配置。

繼續閱讀