slf4j與jul、log4j一、log4j二、logback的集成原理

#1 系列目錄html

#2 slf4jjava

先從一個簡單的使用案例來講明apache

##2.1 簡單的使用案例編程

private static Logger logger=LoggerFactory.getLogger(Log4jSlf4JTest.class);

public static void main(String[] args){
	if(logger.isDebugEnabled()){
		logger.debug("slf4j-log4j debug message");
	}
	if(logger.isInfoEnabled()){
		logger.debug("slf4j-log4j info message");
	}
	if(logger.isTraceEnabled()){
		logger.debug("slf4j-log4j trace message");
	}
}

上述Logger接口、LoggerFactory類都是slf4j本身定義的。api

##2.2 使用原理app

LoggerFactory.getLogger(Log4jSlf4JTest.class)的源碼以下:框架

public static Logger getLogger(String name) {
    ILoggerFactory iLoggerFactory = getILoggerFactory();
    return iLoggerFactory.getLogger(name);
}

上述獲取Log的過程大體分紅2個階段dom

  • 獲取ILoggerFactory的過程 (從字面上理解就是生產Logger的工廠)
  • 根據ILoggerFactory獲取Logger的過程

下面來詳細說明:maven

  • 1 獲取ILoggerFactory的過程ide

    又能夠分紅3個過程:

    • 1.1 從類路徑中尋找org/slf4j/impl/StaticLoggerBinder.class類

      ClassLoader.getSystemResources("org/slf4j/impl/StaticLoggerBinder.class")

      若是找到多個,則輸出 Class path contains multiple SLF4J bindings,表示有多個日誌實現與slf4j進行了綁定

      下面看下當出現多個StaticLoggerBinder的時候的輸出日誌(簡化了一些內容):

      SLF4J: Class path contains multiple SLF4J bindings.
      SLF4J: Found binding in [slf4j-log4j12-1.7.12.jar!/org/slf4j/impl/StaticLoggerBinder.class]
      SLF4J: Found binding in [logback-classic-1.1.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
      SLF4J: Found binding in [slf4j-jdk14-1.7.12.jar!/org/slf4j/impl/StaticLoggerBinder.class]
      SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
      SLF4J: Actual binding is of type [org.slf4j.impl.Log4jLoggerFactory]
    • 1.2 "隨機選取"一個StaticLoggerBinder.class來建立一個單例

      StaticLoggerBinder.getSingleton()

      這裏的"隨機選取"能夠見官方文檔說明:

      SLF4J API is designed to bind with one and only one underlying logging framework at a time. If more than one binding is present on the class path, SLF4J will emit a warning, listing the location of those bindings

      The warning emitted by SLF4J is just that, a warning. Even when multiple bindings are present,SLF4J will pick one logging framework/implementation and bind with it. The way SLF4J picks a binding is determined by the JVM and for all practical purposes should be considered random

    • 1.3 根據上述建立的StaticLoggerBinder單例,返回一個ILoggerFactory實例

      StaticLoggerBinder.getSingleton().getLoggerFactory()

    因此slf4j與其餘實際的日誌框架的集成jar包中,都會含有這樣的一個org/slf4j/impl/StaticLoggerBinder.class類文件,而且提供一個ILoggerFactory的實現

  • 2 根據ILoggerFactory獲取Logger的過程

    這就要看具體的ILoggerFactory類型了,下面的集成來詳細說明

#3 slf4j與jdk-logging集成

##3.1 須要的jar包

  • slf4j-api
  • slf4j-jdk14

對應的maven依賴爲:

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

##3.2 使用案例

private static final Logger logger=LoggerFactory.getLogger(JulSlf4jTest.class);

public static void main(String[] args){
	if(logger.isDebugEnabled()){
		logger.debug("jul debug message");
	}
	if(logger.isInfoEnabled()){
		logger.info("jul info message");
	}
	if(logger.isWarnEnabled()){
		logger.warn("jul warn message");
	}
}

上述的Logger、LoggerFactory都是slf4j本身的API中的內容,沒有jdk自帶的logging的蹤跡,而後打出來的日誌倒是經過jdk自帶的logging來輸出的,以下:

四月 28, 2015 7:33:20 下午 com.demo.log4j.JulSlf4jTest main
信息: jul info message
四月 28, 2015 7:33:20 下午 com.demo.log4j.JulSlf4jTest main
警告: jul warn message

##3.3 使用案例原理分析

先看下slf4j-jdk14 jar包中的內容:

jul與slf4j集成

從中能夠看到:

  • 的確是有org/slf4j/impl/StaticLoggerBinder.class類
  • 該StaticLoggerBinder返回的ILoggerFactory類型將會是JDK14LoggerFactory
  • JDK14LoggerAdapter就是實現了slf4j定義的Logger接口

下面梳理下整個流程:

  • 1 獲取ILoggerFactory的過程

    因爲類路徑下有org/slf4j/impl/StaticLoggerBinder.class,因此會選擇slf4j-jdk14中的StaticLoggerBinder來建立單例對象並返回ILoggerFactory,來看下StaticLoggerBinder中的ILoggerFactory是什麼類型:

    private StaticLoggerBinder() {
        loggerFactory = new org.slf4j.impl.JDK14LoggerFactory();
    }

    因此返回了JDK14LoggerFactory的實例

  • 2 根據ILoggerFactory獲取Logger的過程

    來看下JDK14LoggerFactory是如何返回一個slf4j定義的Logger接口的實例的,源碼以下:

    java.util.logging.Logger julLogger = java.util.logging.Logger.getLogger(name);
    Logger newInstance = new JDK14LoggerAdapter(julLogger);

    能夠看到,就是使用jdk自帶的logging的原生方式來先建立一個jdk本身的java.util.logging.Logger實例,參見jdk-logging的原生寫法

    而後利用JDK14LoggerAdapter將上述的java.util.logging.Logger包裝成slf4j定義的Logger實例

    因此咱們使用slf4j來進行編程,最終會委託給jdk自帶的java.util.logging.Logger去執行。

#4 slf4j與log4j1集成

##4.1 須要的jar包

  • slf4j-api
  • slf4j-log4j12
  • log4j

maven依賴分別爲:

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

<!-- slf4j-log4j -->
<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-log4j12</artifactId>
	<version>1.7.12</version>
</dependency>

<!-- log4j -->
<dependency>
	<groupId>log4j</groupId>
	<artifactId>log4j</artifactId>
	<version>1.2.17</version>
</dependency>

##4.2 使用案例

  • 第一步:編寫log4j.properties配置文件,放到類路徑下

    log4j.rootLogger = debug, console
    log4j.appender.console = org.apache.log4j.ConsoleAppender
    log4j.appender.console.layout = org.apache.log4j.PatternLayout
    log4j.appender.console.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} %m%n

    配置文件的詳細內容不是本博客關注的重點,再也不說明,自行搜索

  • 第二步:代碼中以下使用

    private static Logger logger=LoggerFactory.getLogger(Log4jSlf4JTest.class);
    
    public static void main(String[] args){
    	if(logger.isDebugEnabled()){
    		logger.debug("slf4j-log4j debug message");
    	}
    	if(logger.isInfoEnabled()){
    		logger.info("slf4j-log4j info message");
    	}
    	if(logger.isTraceEnabled()){
    		logger.trace("slf4j-log4j trace message");
    	}
    }
  • 補充說明:

    • 1 配置文件一樣能夠隨意放置,如log4j1原生方式加載配置文件的方式log4j1原生開發

    • 2 注意二者方式的不一樣:

      slf4j:  Logger logger=LoggerFactory.getLogger(Log4jSlf4JTest.class);
      log4j:  Logger logger=Logger.getLogger(Log4jTest.class);

      slf4j的Logger是slf4j定義的接口,而log4j的Logger是類。LoggerFactory是slf4j本身的類

##4.3 使用案例原理分析

先來看下slf4j-log4j12包中的內容:

log4j與slf4j的集成

  • 的確是有org/slf4j/impl/StaticLoggerBinder.class類
  • 該StaticLoggerBinder返回的ILoggerFactory類型將會是Log4jLoggerFactory
  • Log4jLoggerAdapter就是實現了slf4j定義的Logger接口

來看下具體過程:

  • 1 獲取對應的ILoggerFactory

    從上面的slf4j的原理中咱們知道:ILoggerFactory是由StaticLoggerBinder來建立出來的,因此能夠簡單分紅2個過程:

    • 1.1 第一個過程:slf4j尋找綁定類StaticLoggerBinder

      使用ClassLoader來加載 "org/slf4j/impl/StaticLoggerBinder.class"這樣的類的url,而後就找到了slf4j-log4j12包中的StaticLoggerBinder

    • 1.2 第二個過程:建立出StaticLoggerBinder實例,並建立出ILoggerFactory

      源碼以下:

      StaticLoggerBinder.getSingleton().getLoggerFactory()

      以slf4j-log4j12中的StaticLoggerBinder爲例,建立出的ILoggerFactory爲Log4jLoggerFactory

  • 2 根據ILoggerFactory獲取Logger的過程

    來看下Log4jLoggerFactory是如何返回一個slf4j定義的Logger接口的實例的,源碼以下:

    org.apache.log4j.Logger log4jLogger;
    if (name.equalsIgnoreCase(Logger.ROOT_LOGGER_NAME))
        log4jLogger = LogManager.getRootLogger();
    else
        log4jLogger = LogManager.getLogger(name);
    
    Logger newInstance = new Log4jLoggerAdapter(log4jLogger);
    • 2.1 咱們能夠看到是經過log4j1的原生方式,即便用log4j1的LogManager來獲取,引起log4j1的加載配置文件,而後初始化,最後返回一個org.apache.log4j.Logger log4jLogger,參見log4j1原生的寫法

    • 2.2 將上述的org.apache.log4j.Logger log4jLogger封裝成Log4jLoggerAdapter,而Log4jLoggerAdapter是實現了slf4j的接口,因此咱們使用的slf4j的Logger接口實例(這裏即Log4jLoggerAdapter)都會委託給內部的org.apache.log4j.Logger實例

#5 slf4j與log4j2集成

##5.1 須要的jar包

  • slf4j-api
  • log4j-api
  • log4j-core
  • log4j-slf4j-impl (用於log4j2與slf4j集成)

對應的maven依賴分別是:

<!-- slf4j -->
<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-api</artifactId>
	<version>1.7.12</version>
</dependency>
<!-- log4j2 -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.2</version>
</dependency>
<dependency>
	<groupId>org.apache.logging.log4j</groupId>
	<artifactId>log4j-core</artifactId>
	<version>2.2</version>
</dependency>
<!-- log4j-slf4j-impl -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.2</version>
</dependency>

##5.2 使用案例

  • 第一步:編寫log4j2的配置文件log4j2.xml,簡單以下:、

    <?xml version="1.0" encoding="UTF-8"?>
    <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>
  • 第二步:使用方式

    private static Logger logger=LoggerFactory.getLogger(Log4j2Slf4jTest.class);
    
    public static void main(String[] args){
    	if(logger.isTraceEnabled()){
    		logger.trace("slf4j-log4j2 trace message");
    	}
    	if(logger.isDebugEnabled()){
    		logger.debug("slf4j-log4j2 debug message");
    	}
    	if(logger.isInfoEnabled()){
    		logger.info("slf4j-log4j2 info message");
    	}
    }

##5.3 使用案例原理分析

先來看下log4j-slf4j-impl包中的內容:

log4j與slf4j的集成

  • 的確是有org/slf4j/impl/StaticLoggerBinder.class類
  • 該StaticLoggerBinder返回的ILoggerFactory類型將會是Log4jLoggerFactory(這裏的Log4jLoggerFactory與上述log4j1集成時的Log4jLoggerFactory是不同的)
  • Log4jLogger就是實現了slf4j定義的Logger接口

來看下具體過程:

  • 1 獲取對應的ILoggerFactory

    • 1.1 第一個過程:slf4j尋找綁定類StaticLoggerBinder

      使用ClassLoader來加載 "org/slf4j/impl/StaticLoggerBinder.class"這樣的類的url,而後就找到了log4j-slf4j-impl包中的StaticLoggerBinder

    • 1.2 第二個過程:建立出StaticLoggerBinder實例,並建立出ILoggerFactory

      log4j-slf4j-impl包中的StaticLoggerBinder返回的ILoggerFactory是Log4jLoggerFactory

  • 2 根據ILoggerFactory獲取Logger的過程

    來看下Log4jLoggerFactory是如何返回一個slf4j定義的Logger接口的實例的,源碼以下:

    @Override
    protected Logger newLogger(final String name, final LoggerContext context) {
        final String key = Logger.ROOT_LOGGER_NAME.equals(name) ? LogManager.ROOT_LOGGER_NAME : name;
        return new Log4jLogger(context.getLogger(key), name);
    }
    
    @Override
    protected LoggerContext getContext() {
        final Class<?> anchor = ReflectionUtil.getCallerClass(FQCN, PACKAGE);
        return anchor == null ? LogManager.getContext() : getContext(ReflectionUtil.getCallerClass(anchor));
    }
    • 2.1 咱們能夠看到是經過log4j2的原生方式,即便用log4j2的LoggerContext來獲取,返回一個org.apache.logging.log4j.core.Logger即log4j2定義的Logger接口實例,參見log4j2原生的寫法

    • 2.2 將上述的org.apache.logging.log4j.core.Logger封裝成Log4jLogger,而Log4jLogger是實現了slf4j的Logger接口的,因此咱們使用的slf4j的Logger接口實例(這裏即Log4jLogger)都會委託給內部的log4j2定義的Logger實例。

    上述獲取LoggerContext的過程也是log4j2的原生方式:

    LogManager.getContext()

    該操做會去加載log4j2的配置文件,引起log4j2的初始化

#6 slf4j與logback集成

##6.1 須要的jar包

  • slf4j-api
  • logback-core
  • logback-classic(已含有對slf4j的集成包)

對應的maven依賴爲:

<!-- slf4j-api -->
<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-api</artifactId>
	<version>1.7.12</version>
</dependency>
<!-- logback -->
<dependency> 
	<groupId>ch.qos.logback</groupId> 
	<artifactId>logback-core</artifactId> 
	<version>1.1.3</version> 
</dependency> 
<dependency> 
    <groupId>ch.qos.logback</groupId> 
    <artifactId>logback-classic</artifactId> 
    <version>1.1.3</version> 
</dependency>

##6.2 使用案例

  • 第一步:編寫logback的配置文件logback.xml,簡單以下:

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
      <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
          <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
      </appender>
      <root level="DEBUG">          
        <appender-ref ref="STDOUT" />
      </root>  
    </configuration>
  • 第二步:使用方式

    private static final Logger logger=LoggerFactory.getLogger(LogbackTest.class);
    
    public static void main(String[] args){
    	if(logger.isDebugEnabled()){
    		logger.debug("slf4j-logback debug message");
    	}
    	if(logger.isInfoEnabled()){
    		logger.info("slf4j-logback info message");
    	}
    	if(logger.isTraceEnabled()){
    		logger.trace("slf4j-logback trace message");
    	}
    }

##6.3 使用案例原理分析

先來看下logback-classic包中與slf4j集成的內容:

log4j與slf4j的集成

  • 的確是有org/slf4j/impl/StaticLoggerBinder.class類

  • 該StaticLoggerBinder返回的ILoggerFactory類型將會是LoggerContext(logback的對象)

  • logback本身定義的ch.qos.logback.classic.Logger類就是實現了slf4j定義的Logger接口

  • 1 獲取對應的ILoggerFactory

    • 1.1 第一個過程:slf4j尋找綁定類StaticLoggerBinder

      使用ClassLoader來加載 "org/slf4j/impl/StaticLoggerBinder.class"這樣的類的url,而後就找到了logback-classic包中的StaticLoggerBinder

    • 1.2 第二個過程:建立出StaticLoggerBinder實例,並建立出ILoggerFactory

      logback-classic包中的StaticLoggerBinder返回的ILoggerFactory是LoggerContext(logback的對象)

      建立出單例後,同時會引起logback的初始化,這時候logback就要去尋找一系列的配置文件,嘗試加載並解析。

  • 2 根據ILoggerFactory獲取Logger的過程

    來看下LoggerContext(logback的對象)是如何返回一個slf4j定義的Logger接口的實例的:

    該LoggerContext(logback的對象)返回的ch.qos.logback.classic.Logger(logback的原生Logger對象)就是slf4j的Logger實現類。

#7 未完待續

本篇文章講解了slf4j與jdk-logging、log4j一、log4j二、logback的集成原理,下一篇也是最後一篇來總結下

  • 各類jar包的總結
  • commons-logging、slf4j與其餘日誌框架的集成總結
  • 實現已有的日誌框架無縫切換到別的日誌框架(如已使用log4j進行日誌記錄的代碼最終轉到logback來輸出)
  • jar包衝突說明
相關文章
相關標籤/搜索