天天看點

04、slf4j(日志門面)前言一、認識slf4j二、實際應用三、原理分析四、橋接舊的日志實作架構總結參考資料

文章目錄

  • 前言
  • 一、認識slf4j
    • 1.1、slf4j概述
    • 1.2、第三方jar包
    • 1.3、切換日志架構詳略圖
    • 1.4、相關注意點
  • 二、實際應用
    • 2.1、配合自身簡單日志實作(slf4j-simple)
    • 2.2、配置logback日志實作
    • 2.3、配置Log4j日志實作(需擴充卡)
    • 2.4、配置JUL日志實作(需擴充卡)
    • 2.4、添加slf4j-nop依賴(日志開關)
  • 三、原理分析
    • 3.1、初始綁定日志實作原理
  • 四、橋接舊的日志實作架構
    • 介紹橋接器
    • 4.1、log4j-over-slf4j橋接器使用
      • 解決過程
      • 原理分析
    • 4.2、jul、jcl橋接器
    • 三個slf4j日志實作架構與橋接器不能同時使用
  • 總結
  • 參考資料

前言

本篇部落客要介紹現如今主流的日志門面技術

slf4j

Springboot

中推薦使用該日志門面技術。其他日志架構内容可見日志專欄。

所有部落格檔案目錄索引(包含日志架構系列學習):部落格目錄索引(持續更新)

一、認識slf4j

1.1、slf4j概述

官網:http://www.slf4j.org/

使用者手冊:http://www.slf4j.org/manual.html

slf4j

(Simple Logging Facade For Java):為所有的日志架構提供了一套标準、規範的API架構,主要是提供了接口,具體的實作交由對應的日志架構,例如

Log4j

Logback

Log4j2

等。其自己本身也提供了簡單的日志實作(

slf4j-simple

)。

現如今對于一般的Java項目而言,日志架構會選擇

slf4j-api

作為門面,配置上具體的實作架構,中間使用橋接器來完成橋接。

介紹其中兩個類:日志執行個體

Logger

以及

LogFactory.getLogger()

(工廠)擷取日志執行個體。

1.2、第三方jar包

想要使用

slf4j

日志門面,需要使用第三方

jar

包,下面為

pom.xml

的對應坐标:

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.30</version>
</dependency>
           

若是想要之後切換日志架構,最好先引入指定版本的

slf4j-api

(盡管之後引入的對應的

slf4j

日志實作架構中有對應

api

依賴),來進行統一的API管理。

1.3、切換日志架構詳略圖

我們去到slf4j官網的使用者手冊網頁即可檢視到下圖:

04、slf4j(日志門面)前言一、認識slf4j二、實際應用三、原理分析四、橋接舊的日志實作架構總結參考資料

application

下面的

SLF4J API

表示slf4j的日志門面,包含三種情況:

  • ①若是隻導入

    slf4j

    日志門面沒有導入對應的日志實作架構,那麼日志功能将會是預設關閉的,不會進行日志輸出的。
  • ②藍色圖裡

    Logback

    slf4j-simple

    slf4j-nop

    出來的比較晚就遵循了

    slf4j

    API

    規範,也就是說隻要導入對應的實作就預設實作了對應的接口,來實作開發。
  • ③對于中間兩個日志實作架構

    log4j

    (

    slf4j-log4j12

    )、

    JUL

    (

    slf4j-jdk14

    )由于出現的比

    slf4j

    早,是以就沒有遵循

    slf4j

    的接口規範,是以無法進行直接綁定,中間需要加一個适配層(

    Adaptation layer

    ),通過對應的擴充卡來适配具體的日志實作架構,其對應的擴充卡其實就間接的實作了

    slf4j-api

    的接口規範。

注意:在圖中對于

logback

需要引入兩個

jar

包,不過在

maven

中有一個傳遞的思想,當配置

logback-classic

時就會預設傳遞

core

資訊,是以我們隻需要引入

logback-classic

jar

包即可。

1.4、相關注意點

在使用slf4j日志門面的過程中,若是引入了兩個日志實作架構會報以下錯誤,并會預設實作第一個引入的日志實作:

04、slf4j(日志門面)前言一、認識slf4j二、實際應用三、原理分析四、橋接舊的日志實作架構總結參考資料
  • 這裡是同時配置

    simple

    以及

    logback

    情況

注意:以

pom.xml

中配置順序有關!!!

二、實際應用

2.1、配合自身簡單日志實作(slf4j-simple)

若想使用自身的日志實作架構,需要引入第三方

jar

slf4j-simple

(slf4j自帶實作類):

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>1.7.5</version>
</dependency>
           
  • 其中該坐标包含了對應的

    slf4j-api

    的依賴,可以不用手動導入

    slf4j-api

  • 04、slf4j(日志門面)前言一、認識slf4j二、實際應用三、原理分析四、橋接舊的日志實作架構總結參考資料

測試程式:

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

public class LogTest {
    //擷取Logger執行個體
    public static final Logger LOGGER = LoggerFactory.getLogger(LogTest.class);

    public static void main(String[] args) {
        System.out.println(LOGGER.getName());//xyz.changlu.LogTest
        //1、列印日志記錄
        LOGGER.error("error");
        LOGGER.warn("warn");
        LOGGER.info("info");
        LOGGER.debug("debug");
        LOGGER.trace("trace");

        //2、占位符輸出
        String name = "changlu";
        int age = 20;
        LOGGER.info("報錯,name:{},age:{}",name,age);

        //3、列印堆棧資訊
        try {
            int i = 5/0;
        }catch (Exception e){
            LOGGER.error("報錯",e);
        }

    }
}
           
04、slf4j(日志門面)前言一、認識slf4j二、實際應用三、原理分析四、橋接舊的日志實作架構總結參考資料
  • 預設日志等級為

    INFO

    ,能夠實作占位符輸出,并且可以在日志等級方法中傳入異常執行個體,來列印對應的日志資訊。

注意點:若是我們隻使用日志門面而沒有導入指定的日志實作架構,調用

Logger

執行個體并調用日志方法會出現以下錯誤:

04、slf4j(日志門面)前言一、認識slf4j二、實際應用三、原理分析四、橋接舊的日志實作架構總結參考資料

2.2、配置logback日志實作

引入

logback-classic

的jar包,其中包含有

slf4j-api

以及

logback-core

的依賴,是以隻需要引入該依賴即可:

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>
           
04、slf4j(日志門面)前言一、認識slf4j二、實際應用三、原理分析四、橋接舊的日志實作架構總結參考資料

測試程式:

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

public class LogTest {
    //擷取Logger執行個體
    public static final Logger LOGGER = LoggerFactory.getLogger(LogTest.class);

    public static void main(String[] args) {
        System.out.println(LOGGER.getName());//xyz.changlu.LogTest
        //1、列印日志記錄
        LOGGER.error("error");
        LOGGER.warn("warn");
        LOGGER.info("info");
        LOGGER.debug("debug");
        LOGGER.trace("trace");

        //2、占位符輸出
        String name = "changlu";
        int age = 20;
        LOGGER.info("報錯,name:{},age:{}",name,age);

        //3、列印堆棧資訊
        try {
            int i = 5/0;
        }catch (Exception e){
            LOGGER.error("報錯",e);
        }

    }
}
           
04、slf4j(日志門面)前言一、認識slf4j二、實際應用三、原理分析四、橋接舊的日志實作架構總結參考資料

2.3、配置Log4j日志實作(需擴充卡)

①首先添加日志架構實作依賴

之前在1.3中介紹,對于

Log4j

JUL

這些比較早出現的日志實作架構需要有對應的适配層,在這裡我們引入對應的擴充卡

slf4j-log412

的依賴坐标:

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.25</version>
</dependency>
           
  • slf4j-log4j12

    坐标中實際就包含了

    Log4j

    以及

    slf4j-api

    依賴,是以我們添加該坐标即可。
04、slf4j(日志門面)前言一、認識slf4j二、實際應用三、原理分析四、橋接舊的日志實作架構總結參考資料

②添加

log4j.properties

配置檔案

# rootLogger日志等級為trace,輸出到螢幕上
log4j.rootLogger = trace,console

# console
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern= [%-5p]%r %l %d{yyyy-MM-dd HH:mm:ss:SSS} %m%n
           

測試程式:

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

public class LogTest {
    //擷取Logger執行個體
    public static final Logger LOGGER = LoggerFactory.getLogger(LogTest.class);

    public static void main(String[] args) {
        System.out.println(LOGGER.getName());//xyz.changlu.LogTest
        //1、列印日志記錄
        LOGGER.error("error");
        LOGGER.warn("warn");
        LOGGER.info("info");
        LOGGER.debug("debug");
        LOGGER.trace("trace");

        //2、占位符輸出
        String name = "changlu";
        int age = 20;
        LOGGER.info("報錯,name:{},age:{}",name,age);

        //3、列印堆棧資訊
        try {
            int i = 5/0;
        }catch (Exception e){
            LOGGER.error("報錯",e);
        }

    }
}
           
04、slf4j(日志門面)前言一、認識slf4j二、實際應用三、原理分析四、橋接舊的日志實作架構總結參考資料

2.4、配置JUL日志實作(需擴充卡)

對于

slf4j

日志門面實作

JUL

日志架構需要使用是擴充卡來實作

slf4

j的日志接口,我們直接添加對應擴充卡依賴如下:

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-jdk14</artifactId>
    <version>1.5.6</version>
</dependency>
           
  • JUL

    是我們

    jdk

    自帶的日志架構,是以不需要額外引入jar包,引入

    slf4j-jdk14

    坐标,其中就包含了

    slf4j-api

    的依賴,是以我們隻需要引入一個坐标即可。
04、slf4j(日志門面)前言一、認識slf4j二、實際應用三、原理分析四、橋接舊的日志實作架構總結參考資料

測試程式:

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

public class LogTest {
    //擷取Logger執行個體
    public static final Logger LOGGER = LoggerFactory.getLogger(LogTest.class);

    public static void main(String[] args) {
        System.out.println(LOGGER.getName());//xyz.changlu.LogTest
        //1、列印日志記錄
        LOGGER.error("error");
        LOGGER.warn("warn");
        LOGGER.info("info");
        LOGGER.debug("debug");
        LOGGER.trace("trace");

        //2、占位符輸出
        String name = "changlu";
        int age = 20;
        LOGGER.info("報錯,name:{},age:{}",name,age);

        //3、列印堆棧資訊
        try {
            int i = 5/0;
        }catch (Exception e){
            LOGGER.error("報錯",e);
        }

    }
}
           
04、slf4j(日志門面)前言一、認識slf4j二、實際應用三、原理分析四、橋接舊的日志實作架構總結參考資料

2.4、添加slf4j-nop依賴(日志開關)

當添加了

slf4j-nop

坐标後,其相當于一個日志開關,導入實作以後就不會使用任何實作架構:

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-nop</artifactId>
    <version>1.7.27</version>
</dependency>
           
04、slf4j(日志門面)前言一、認識slf4j二、實際應用三、原理分析四、橋接舊的日志實作架構總結參考資料

測試程式:

public class LogTest {
    //擷取Logger執行個體
    public static final Logger LOGGER = LoggerFactory.getLogger(LogTest.class);

    public static void main(String[] args) {
        LOGGER.error("error");
        LOGGER.warn("warn");
        LOGGER.info("info");
        LOGGER.debug("debug");
        LOGGER.trace("trace");
    }
}
           
04、slf4j(日志門面)前言一、認識slf4j二、實際應用三、原理分析四、橋接舊的日志實作架構總結參考資料
  • 預設就關閉了

    slf4j

    的日志架構使用。

三、原理分析

3.1、初始綁定日志實作原理

slf4j-api

中我們通常使用下面的方法來擷取

logger

執行個體:

獲得的執行個體實際上跟我們導入

jar

包有關,那麼它是如何進行初始配置的呢?看下面源碼:

public final class LoggerFactory {
    
   public static Logger getLogger(Class<?> clazz) {
        //1、調用一個重載方法,傳入類名
        Logger logger = getLogger(clazz.getName());//見2
        if (DETECT_LOGGER_NAME_MISMATCH) {
            Class<?> autoComputedCallingClass = Util.getCallingClass();
            if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {
                Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(),
                                autoComputedCallingClass.getName()));
                Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");
            }
        }
        return logger;
    }
    
    //2、根據類名來擷取logger執行個體
    public static Logger getLogger(String name) {
        //擷取ILoggerFactory接口的實作類(接口方法是getLogger())
        ILoggerFactory iLoggerFactory = getILoggerFactory();//見3
        return iLoggerFactory.getLogger(name);
    }
    
    //3、擷取ILoggerFactory的執行個體
    public static ILoggerFactory getILoggerFactory() {
        if (INITIALIZATION_STATE == UNINITIALIZED) {
            synchronized (LoggerFactory.class) {
                if (INITIALIZATION_STATE == UNINITIALIZED) {
                    INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                    //執行初始化方法
                    performInitialization();//見4
                }
            }
        }
        ...
    }
    
    //4、執行初始化操作
    private final static void performInitialization() {
        bind();//見5
        if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
            versionSanityCheck();
        }
    }
    
    //5、綁定操作
    private final static void bind() {
        try {
            Set<URL> staticLoggerBinderPathSet = null;
            if (!isAndroid()) {
                //下面兩行比較關鍵,這行是查找可能的靜态日志執行器路徑使用Set來接收
                staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();//見6
                //檢視set中是否超過1個路徑,若是則進行視窗輸出提示資訊
                reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);//見7
            }
            //
            StaticLoggerBinder.getSingleton();
            INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
            reportActualBinding(staticLoggerBinderPathSet);
        } catch (NoClassDefFoundError ncde) {
            String msg = ncde.getMessage();
            ....
    }
    
    //6、這裡是查找有關org/slf4j/impl/StaticLoggerBinder.class路徑,都放置到set中傳回
    static Set<URL> findPossibleStaticLoggerBinderPathSet() {
        // use Set instead of list in order to deal with bug #138
        // LinkedHashSet appropriate here because it preserves insertion order
        // during iteration
        Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();
        try {
            ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
            Enumeration<URL> paths;
            if (loggerFactoryClassLoader == null) {
                //從類加載器中進行查找是否有org/slf4j/impl/StaticLoggerBinder.class路徑
                paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
            } else {
                paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
            }
            //這裡依舊是根據查找到的路徑繼續往下延伸查找并添加到Set中去
            while (paths.hasMoreElements()) {
                URL path = paths.nextElement();
                staticLoggerBinderPathSet.add(path);
            }
        } catch (IOException ioe) {
            Util.report("Error getting resources from path", ioe);
        }
        return staticLoggerBinderPathSet;
    }
}
           
52行的查找

org/slf4j/impl/StaticLoggerBinder.class

路徑操作存放到Set中傳回。

看到上面方法6中是不是有個疑惑,查找

org/slf4j/impl/StaticLoggerBinder.class

這個相關路徑有什麼用,與我們要加載對應的日志實作有什麼關系呢?

我們在本次源碼過程中引入兩個日志實作架構

slf4j-log4j12

slf4j-jdk14

(這兩個都是

slf4j

為較早出現的日志設定的擴充卡),引入jar包之後,我們嘗試搜尋一下

StaticLoggerBinder

這個類:

04、slf4j(日志門面)前言一、認識slf4j二、實際應用三、原理分析四、橋接舊的日志實作架構總結參考資料

好家夥原來

slf4j

實作的相關擴充卡的名稱都叫

StaticLoggerBinder

啊,我們繼續看下去,看下jdk14的吧(就是JUL):

04、slf4j(日志門面)前言一、認識slf4j二、實際應用三、原理分析四、橋接舊的日志實作架構總結參考資料

該工廠類中的執行個體

loggerFactory

是擷取了一個JDK14的工廠類,那麼我們繼續看向擴充卡中内容:

04、slf4j(日志門面)前言一、認識slf4j二、實際應用三、原理分析四、橋接舊的日志實作架構總結參考資料

重寫了

getLogger()

方法,其中執行個體化了

JUL

logger

執行個體,調用有參構造傳入并且建立了一個

JDK14

的擴充卡,我們再看下這個擴充卡中都做了些什麼:

04、slf4j(日志門面)前言一、認識slf4j二、實際應用三、原理分析四、橋接舊的日志實作架構總結參考資料

好家夥其中包含了各個日志等級的方法,其中都包含了

JUL

的日志操作。

53行中調用的

reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);

來報告多個綁定日志實作架構
public final class LoggerFactory {
    
    //7、來進行報告含有多個日志架構的路徑
    private static void reportMultipleBindingAmbiguity(Set<URL> binderPathSet) {
        //該方法判斷是否set中數量>1
        if (isAmbiguousStaticLoggerBinderPathSet(binderPathSet)) {//見8
            //若是超過1個的話就會報該問題(就是我們之前1.4中的注意點報錯)
            Util.report("Class path contains multiple SLF4J bindings.");
            for (URL path : binderPathSet) {
                Util.report("Found binding in [" + path + "]");
            }
            Util.report("See " + MULTIPLE_BINDINGS_URL + " for an explanation.");
        }
    }
    
    //8、判斷set中的容量大小是否>1
    private static boolean isAmbiguousStaticLoggerBinderPathSet(Set<URL> binderPathSet) {
        return binderPathSet.size() > 1;
    }
}
           

該方法就是來檢測是否有多個日志實作架構導入,若是有則報出提示資訊。

四、橋接舊的日志實作架構

介紹橋接器

直接舉場景來說明:對于一些老項目直接使用的是

Log4j

JUL

的日志實作架構,并沒有使用到日志門面來進行管理日志架構,當項目需要疊代更新時,我們想把原先的日志實作架構切換為

logback

,此時會出現一個問題,若是我們直接将對應的日志jar包更改為

logback

,那麼項目中會出現大量報錯,因為原先引入的包是

import org.apache.log4j.Logger;

,此時就會出現問題,我們需要重新修改大量的代碼,需要耗費大量的時間與精力。

解決方案:在

slf4j

中可以使用橋接器進而讓我們不用修改一行代碼實作日志架構的切換。在slf4j中附帶了幾個橋接木塊,這些子產品對于

log4j

JCL

JUL

API

調用重定向(其實就是全限定名與原來的完全相同)。

下圖包含了對應的解決方案:

04、slf4j(日志門面)前言一、認識slf4j二、實際應用三、原理分析四、橋接舊的日志實作架構總結參考資料
  • 可用

    log4j-over-slf4j.jar

    替代

    Log4j

4.1、log4j-over-slf4j橋接器使用

解決過程

問題描述

模拟場景:老項目直接使用的是

org.apache.log4j.Logger

,現今項目疊代更新,需要使用

Logback

日志架構。

我們首先将

log4j

jar包移除,之後引入

logback-classic

依賴坐标,此時就會出現下方情況:

04、slf4j(日志門面)前言一、認識slf4j二、實際應用三、原理分析四、橋接舊的日志實作架構總結參考資料
04、slf4j(日志門面)前言一、認識slf4j二、實際應用三、原理分析四、橋接舊的日志實作架構總結參考資料
解決方案:使用橋接器

log4j-over-slf4j

引入坐标依賴:

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>log4j-over-slf4j</artifactId>
    <version>1.7.27</version>
</dependency>
           
04、slf4j(日志門面)前言一、認識slf4j二、實際應用三、原理分析四、橋接舊的日志實作架構總結參考資料
  • 不用修改任何代碼即可替換日志架構。

原理分析

04、slf4j(日志門面)前言一、認識slf4j二、實際應用三、原理分析四、橋接舊的日志實作架構總結參考資料
  • 從導入的包來看,其中的全限定類名與原本

    Log4j

    的一毛一樣,接着看是如何達到無縫銜接的。

同樣是下方的擷取

logger

執行個體方法:

import org.apache.log4j.Logger;

public class LogTest {
    
    public static final Logger LOGGER = Logger.getLogger(LogTest.class);
}
           

檢視源碼:

//即引入的log4j-over-slf4j坐标
package org.apache.log4j;
public class Logger extends Category {

    //1、
    public static Logger getLogger(Class clazz) {
        return getLogger(clazz.getName());//見2
    }
    
    //2、
    public static Logger getLogger(String name) {
        return Log4jLoggerFactory.getLogger(name);//見3
    }
    
    //4、有參構造
    protected Logger(String name) {
        super(name);//調用的是Category的有參構造 去5
    }

}

//log4j的工廠類
class Log4jLoggerFactory {
    
    //3、擷取logger執行個體
    public static Logger getLogger(String name) {
        Logger instance = (Logger)log4jLoggers.get(name);
        if (instance != null) {
            return instance;
        } else {
            //重要的點來了:注意看這個方法
            Logger newInstance = new Logger(name);//回到上面的Logger類中的4方法
            Logger oldInstance = (Logger)log4jLoggers.putIfAbsent(name, newInstance);
            return oldInstance == null ? newInstance : oldInstance;
        }
    }
}

//Loger類的父類
public class Category {
    
    protected Logger slf4jLogger;
    
    //5、有參構造
    Category(String name) {
        this.name = name;
        //注意這個方法,LoggerFactory.getLogger()擷取的是slf4j的對應Logger
        this.slf4jLogger = LoggerFactory.getLogger(name);
        if (this.slf4jLogger instanceof LocationAwareLogger) {
            this.locationAwareLogger = (LocationAwareLogger)this.slf4jLogger;
        }

    }
}
           
  • 簡單來說就是

    slf4j

    的開發者提供了一個與

    Log4j

    的全限定類名相同的一個包,其中的方法名稱與

    Log4j

    的都相同,在

    getLogger()

    方法中實際擷取到了

    slf4j

    對應的

    Logger

    執行個體。

如下圖:

log4j

是灰色表示的是移除掉,使用

log4j-over-slf4j

04、slf4j(日志門面)前言一、認識slf4j二、實際應用三、原理分析四、橋接舊的日志實作架構總結參考資料

4.2、jul、jcl橋接器

當使用單獨的日志實作架構想要替換成如

logback

日志架構時可使用對應的橋接器來進行替代:

<!-- jul -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jul-to-slf4j</artifactId>
    <version>1.7.27</version>
</dependency>
<!--jcl -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    <version>1.7.27</version>
</dependency>
           

替換了之後别忘了引入對應要替換的日志實作架構。

三個slf4j日志實作架構與橋接器不能同時使用

以下的

jar

包不能同時出現:

  • log4j-over-slf4j.jar

    (橋接器)和

    slf4j-log4j12.jar

  • jcl-over-slf4j.jar

    slf4j-jcl.jar

  • jul-to-slf4j.jar

    slf4j-jdk14.jar

為什麼不能同時出現呢?我們借第一組進行檢視:

①首先看下相應的依賴導入

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>log4j-over-slf4j</artifactId>
    <version>1.7.27</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.25</version>
</dependency>
           
04、slf4j(日志門面)前言一、認識slf4j二、實際應用三、原理分析四、橋接舊的日志實作架構總結參考資料
  • log4j-over-slf4j

    坐标依賴
04、slf4j(日志門面)前言一、認識slf4j二、實際應用三、原理分析四、橋接舊的日志實作架構總結參考資料
04、slf4j(日志門面)前言一、認識slf4j二、實際應用三、原理分析四、橋接舊的日志實作架構總結參考資料
  • slf4j-log4j12

    坐标依賴,注意該包的全限定類名與

    log4j

    的權限定類名一緻。
    • 04、slf4j(日志門面)前言一、認識slf4j二、實際應用三、原理分析四、橋接舊的日志實作架構總結參考資料

②我們運作下程式檢視一下

import org.apache.log4j.Logger;

public class LogTest {
    //擷取Logger執行個體
    public static final Logger LOGGER = Logger.getLogger(LogTest.class);

    public static void main(String[] args) {
        LOGGER.error("error");
    }
}
           
04、slf4j(日志門面)前言一、認識slf4j二、實際應用三、原理分析四、橋接舊的日志實作架構總結參考資料

為什麼會出現這種情況呢,看下面原理圖一下子懂了:

04、slf4j(日志門面)前言一、認識slf4j二、實際應用三、原理分析四、橋接舊的日志實作架構總結參考資料
  • 其實說白了就是

    log4j

    log4j-over-slf4j

    的權限定包名都是相同的,當在

    slf4j-log4j12

    的擴充卡中若是找到

    log4j-over-slf4j

    中的

    log4j

    時此時就會出現無限死循環,也就導緻棧溢出了。
前後引入jar包位置改變不會報錯

不過我發現了一個問題:如果

pom.xml

中的坐标前後位置,兩個

jar

包都導入了也不會報錯

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.25</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>log4j-over-slf4j</artifactId>
    <version>1.7.27</version>
</dependency>
           
  • 先引入

    slf4j-log4j12

    再引入

    log4j-over-slf4j

    運作就不會報錯,可能是在

    slf4j-log4j12

    的擴充卡中找

    Logger

    時,

    pom.xml

    log4j

    首先被加載到了是以就不會報錯了。

說明:盡管這樣引入不會報錯,我們也一定不要這樣子引入橋接器與

slf4j

的日志實作架構,這樣也沒必要。

總結

1、對于

slf4j

切換日志架構我們實際上就隻需要引入

slf4j

提供的各個日志實作依賴即可(對應坐标其中包含了

slf-api

以及對應實作架構依賴)。

2、對于一開始就沒有進行使用日志門面而隻是單單使用日志架構的項目,若是想要不修改代碼進行切換日志架構,我們就要考慮使用

slf4j

橋接器來進行日志架構切換。

3、

slf4j

提供的日志架構實作盡量不要與橋接器同時使用,否則極有可能會報錯!

參考資料

[1] 視訊:2020年Java進階教程,全面學習多種java日志架構

我是長路,感謝你的耐心閱讀,如有問題請指出,我會聽取建議并進行修正。

歡迎關注我的公衆号:長路Java,其中會包含軟體安裝等其他一些資料,包含一些視訊教程以及學習路徑分享。

程式設計學習qq群:891507813 我們可以一起探讨學習

注明:轉載可,需要附帶上文章連結