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

#1 系列目錄java

前面介紹了jdk自帶的logging、log4j一、log4j二、logback等實際的日誌框架web

對於開發者而言,每種日誌都有不一樣的寫法。若是咱們以實際的日誌框架來進行編寫,代碼就限制死了,以後就很難再更換日誌系統,很難作到無縫切換。apache

java web開發就常常提到一項原則:面向接口編程,而不是面向實現編程編程

因此咱們應該是按照一套統一的API來進行日誌編程,實際的日誌框架來實現這套API,這樣的話,即便更換日誌框架,也能夠作到無縫切換。api

這就是commons-logging與slf4j的初衷。app

下面就來介紹下commons-logging與slf4j這兩個門面如何與上述四個實際的日誌框架進行集成的呢框架

介紹以前先說明下日誌簡稱:maven

  • jdk自帶的logging->簡稱 jul (java-util-logging)
  • apache commons-logging->簡稱 jcl

#2 apache commons-loggingide

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

##2.1 簡單的使用案例

private static Log logger=LogFactory.getLog(JulJclTest.class);

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

上述Log、LogFactory都是commons-logging本身的接口和類

##2.2 使用原理

LogFactory.getLog(JulJclTest.class)的源碼以下:

public static Log getLog(Class clazz) throws LogConfigurationException {
    return getFactory().getInstance(clazz);
}

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

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

commons-logging默認提供的LogFactory實現:LogFactoryImpl commons-logging默認提供的Log實現:Jdk14Logger、Log4JLogger、SimpleLog。

來看下commons-logging包中的大概內容:

commons-logging包中的大體內容

下面來詳細說明:

  • 1 獲取LogFactory的過程

    從下面幾種途徑來獲取LogFactory

    • 1.1 系統屬性中獲取,即以下形式

      System.getProperty("org.apache.commons.logging.LogFactory")
    • 1.2 使用java的SPI機制,來搜尋對應的實現

      對於java的SPI機制,詳細內容能夠自行搜索,這裏再也不說明。搜尋路徑以下:

      META-INF/services/org.apache.commons.logging.LogFactory

      簡單來講就是搜尋哪些jar包中含有搜尋含有上述文件,該文件中指明瞭對應的LogFactory實現

    • 1.3 從commons-logging的配置文件中尋找

      commons-logging也是能夠擁有本身的配置文件的,名字爲commons-logging.properties,只不過目前大多數狀況下,咱們都沒有去使用它。若是使用了該配置文件,嘗試從配置文件中讀取屬性"org.apache.commons.logging.LogFactory"對應的值

    • 1.4 最後還沒找到的話,使用默認的org.apache.commons.logging.impl.LogFactoryImpl

      LogFactoryImpl是commons-logging提供的默認實現

  • 2 根據LogFactory獲取Log的過程

    這時候就須要尋找底層是選用哪一種類型的日誌

    就以commons-logging提供的默認實現爲例,來詳細看下這個過程:

    • 2.1 從commons-logging的配置文件中尋找Log實現類的類名

      從commons-logging.properties配置文件中尋找屬性爲"org.apache.commons.logging.Log"對應的Log類名

    • 2.2 從系統屬性中尋找Log實現類的類名

      即以下方式獲取:

      System.getProperty("org.apache.commons.logging.Log")
    • 2.3 若是上述方式沒找到,則從classesToDiscover屬性中尋找

      classesToDiscover屬性值以下:

      private static final String[] classesToDiscover = {
          "org.apache.commons.logging.impl.Log4JLogger",
          "org.apache.commons.logging.impl.Jdk14Logger",
          "org.apache.commons.logging.impl.Jdk13LumberjackLogger",
          "org.apache.commons.logging.impl.SimpleLog"
      };

      它會嘗試根據上述類名,依次進行建立,若是能建立成功,則使用該Log,而後返回給用戶。

下面針對具體的日誌框架,看看commons-logging是如何集成的

#3 commons-logging與jul集成

##3.1 須要的jar包

  • commons-logging

對應的maven依賴是:

<dependency>
	<groupId>commons-logging</groupId>
	<artifactId>commons-logging</artifactId>
	<version>1.2</version>
</dependency>

##3.2 使用案例

private static Log logger=LogFactory.getLog(JulJclTest.class);

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

結果輸出以下:

四月 27, 2015 11:13:33 下午 com.demo.log4j.JulJclTest main
信息: commons-logging-jcl info message

##3.3 使用案例分析

案例過程分析,就是看看上述commons-logging的在執行原理的過程當中是如何來走的

  • 1 獲取獲取LogFactory的過程

    • 1.1 咱們沒有配置系統屬性"org.apache.commons.logging.LogFactory"
    • 1.2 咱們沒有配置commons-logging的commons-logging.properties配置文件
    • 1.3 也沒有含有"META-INF/services/org.apache.commons.logging.LogFactory"路徑的jar包

    因此commons-logging會使用默認的LogFactoryImpl做爲LogFactory

  • 2 根據LogFactory獲取Log的過程

    • 2.1 咱們沒有配置commons-logging的commons-logging.properties配置文件
    • 2.2 咱們沒有配置系統屬性"org.apache.commons.logging.Log"

    因此就須要依次根據classesToDiscover中的類名稱進行建立。

    • 2.3 先是建立org.apache.commons.logging.impl.Log4JLogger

      建立失敗,由於該類是依賴org.apache.log4j包中的類的

    • 2.4 接着建立org.apache.commons.logging.impl.Jdk14Logger

      建立成功,因此咱們返回的就是Jdk14Logger,看下它是如何與jul集成的

      它內部有一個java.util.logging.Logger logger屬性,因此Jdk14Logger的info("commons-logging-jcl info message")操做都會轉化成由java.util.logging.Logger來實現:

      上述logger的來歷:

      logger = java.util.logging.Logger.getLogger(name);

      就是使用jul原生的方式建立的一個java.util.logging.Logger,參見jdk-logging的原生寫法

      是如何打印info信息的呢?

      使用jul原生的方式:

      logger.log(Level.WARNING,"commons-logging-jcl info message");

因爲jul默認的級別是INFO級別(見上一篇文章的說明中的配置文件jdk自帶的logging),因此只打出了以下信息:

四月 27, 2015 11:41:24 下午 com.demo.log4j.JulJclTest main
信息: commons-logging-jcl info message

原生的jdk的logging的日誌級別是FINEST、FINE、INFO、WARNING、SEVERE分別對應咱們常見的trace、debug、info、warn、error。

#4 commons-logging與log4j1集成

##4.1 須要的jar包

  • commons-logging
  • 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>

##4.2 使用案例

  • 在類路徑下加入log4j的配置文件log4j.properties

    log4j.rootLogger = trace, 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 Log logger=LogFactory.getLog(Log4jJclTest.class);
    
    public static void main(String[] args){
    	if(logger.isTraceEnabled()){
    		logger.trace("commons-logging-log4j trace message");
    	}
    	if(logger.isDebugEnabled()){
    		logger.debug("commons-logging-log4j debug message");
    	}
    	if(logger.isInfoEnabled()){
    		logger.info("commons-logging-log4j info message");
    	}
    }

代碼沒變,仍是使用commons-logging的接口和類來編程,沒有log4j的任何影子。這樣,commons-logging就與log4j集成了起來,咱們能夠經過log4j的配置文件來控制日誌的顯示級別

上述是trace級別(小於debug),因此trace、debug、info的都會顯示出來

##4.3 使用案例分析

案例過程分析,就是看看上述commons-logging的在執行原理的過程當中是如何來走的:

  • 1 獲取獲取LogFactory的過程

    同上述jcl的過程同樣,使用默認的LogFactoryImpl做爲LogFactory

  • 2 根據LogFactory獲取Log的過程

    同上述jcl的過程同樣,最終會依次根據classesToDiscover中的類名稱進行建立:

    先是建立org.apache.commons.logging.impl.Log4JLogger

    建立成功,由於此時含有log4j的jar包,因此返回的是Log4JLogger,咱們看下它與commons-logging是如何集成的:

    它內部有一個org.apache.log4j.Logger logger屬性,這個是log4j的原生Logger。因此Log4JLogger都是委託這個logger來完成的

    • 2.1 org.apache.log4j.Logger logger來歷

      org.apache.log4j.Logger.getLogger(name)

      使用原生的log4j1的寫法來生成,參見以前log4j原生的寫法log4j1原生的寫法,咱們知道上述過程會引起log4j1的配置文件的加載,以後就進入log4j1的世界了

    • 2.2 輸出日誌

      測試案例中咱們使用commons-logging輸出的日誌的形式以下(這裏的logger是org.apache.commons.logging.impl.Log4JLogger類型):

      logger.debug("commons-logging-log4j debug message");

      其實就會轉換成log4j原生的org.apache.log4j.Logger對象(就是上述獲取的org.apache.log4j.Logger類型的logger對象)的以下輸出:

      logger.debug("log4j debug message");

上述過程最好與log4j1的原生方式對比着看,見log4j1的原生方式

#5 commons-logging與log4j2集成

##5.1 須要的jar包

  • commons-logging
  • log4j-api (log4j2的API包)
  • log4j-core (log4j2的API實現包)
  • log4j-jcl (log4j2與commons-logging的集成包)

對應的maven依賴是:

<dependency>
	<groupId>commons-logging</groupId>
	<artifactId>commons-logging</artifactId>
	<version>1.2</version>
</dependency>
<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>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-jcl</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 Log logger=LogFactory.getLog(Log4j2JclTest.class);
    
    public static void main(String[] args){
    	if(logger.isTraceEnabled()){
    		logger.trace("commons-logging-log4j trace message");
    	}
    	if(logger.isDebugEnabled()){
    		logger.debug("commons-logging-log4j debug message");
    	}
    	if(logger.isInfoEnabled()){
    		logger.info("commons-logging-log4j info message");
    	}
    }

仍然是使用commons-logging的Log接口和LogFactory來進行編寫,看不到log4j2的影子。可是這時候含有上述幾個jar包,log4j2就與commons-logging集成了起來。

##5.3 使用案例分析

案例過程分析,就是看看上述commons-logging的在執行原理的過程當中是如何來走的:

  • 1 先來看下上述 log4j-jcl(log4j2與commons-logging的集成包)的來歷:

    咱們知道,commons-logging原始的jar包中使用了默認的LogFactoryImpl做爲LogFactory,該默認的LogFactoryImpl中的classesToDiscover(到上面查看它的內容)並無log4j2對應的Log實現類。因此咱們就不能使用這個原始包中默認的LogFactoryImpl了,須要從新指定一個,而且須要給出一個apache的Log實現(該Log實現是用於log4j2的),因此就產生了log4j-jcl這個jar包,來看下這個jar包的大體內容:

    log4j2與commons-logging的集成包

    這裏面的LogFactoryImpl就是要準備替換commons-logging中默認的LogFactoryImpl(其中META-INF/services/下的那個文件起到重要的替換做用,下面詳細說)

    這裏面的Log4jLog即是針對log4j2的,而commons-logging中的原始的Log4JLogger則是針對log4j1的。它們都是commons-logging的Log接口的實現

  • 2 獲取獲取LogFactory的過程

    這個過程就和jul、log4j1的集成過程不太同樣了。經過java的SPI機制,找到了org.apache.commons.logging.LogFactory對應的實現,即在log4j-jcl包中找到的,其中META-INF/services/org.apache.commons.logging.LogFactory中的內容是:

    org.apache.logging.log4j.jcl.LogFactoryImpl

    即指明瞭使用log4j-jcl中的LogFactoryImpl做爲LogFactory

  • 3 根據LogFactory獲取Log的過程

    就來看下log4j-jcl中的LogFactoryImpl是怎麼實現的

    public class LogFactoryImpl extends LogFactory {
    
    	private final LoggerAdapter<Log> adapter = new LogAdapter();
    	//略
    }

    這個LoggerAdapter是lo4j2中的一個適配器接口類,根據log4j2生產的原生的org.apache.logging.log4j.Logger實例,將它包裝成你指定的泛型類。

    這裏使用的LoggerAdapter實現是LogAdapter,它的內容以下:

    public class LogAdapter extends AbstractLoggerAdapter<Log> {
        @Override
        protected Log newLogger(final String name, final LoggerContext context) {
            return new Log4jLog(context.getLogger(name));
        }
        @Override
        protected LoggerContext getContext() {
            return getContext(ReflectionUtil.getCallerClass(LogFactory.class));
        }
    }

    咱們能夠看到,它其實就是將原生的log4j2的Logger封裝成Log4jLog。這裏就能夠看明白了,下面來詳細的走下流程,看看是何時來初始化log4j2的:

    • 3.1 首先獲取log4j2中的重要配置對象LoggerContext,LogAdapter的實現如上面的源碼(使用父類的getContext方法),父類方法的內容以下:

      LogManager.getContext(cl, false);

      咱們能夠看到這其實就是使用log4j2的LogManager進行初始化的,至此就進入log4j2的初始化的世界了。

    • 3.2 log4j2的LoggerContext初始化完成後,該生產一個log4j2原生的Logger對象

      使用log4j2原生的方式:

      context.getLogger(name)
    • 3.3 將上述方式產生的Log4j原生的Logger實例進行包裝,包裝成Log4jLog

      new Log4jLog(context.getLogger(name));

    至此,咱們經過Log4jLog實例打印的日誌都是委託給了它內部包含的log4j2的原生Logger對象了。

上述過程最好與log4j2的原生方式對比着看,見log4j2的原生方式

#6 commons-logging與logback集成

##6.1 須要的jar包

  • jcl-over-slf4j (替代了commons-logging,下面詳細說明)
  • slf4j-api
  • logback-core
  • logback-classic

對應的maven依賴是:

<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>jcl-over-slf4j</artifactId>
	<version>1.7.12</version>
</dependency>
<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-api</artifactId>
	<version>1.7.12</version>
</dependency>
<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 Log logger=LogFactory.getLog(LogbackTest.class);
    
    public static void main(String[] args){
    	if(logger.isTraceEnabled()){
    		logger.trace("commons-logging-jcl trace message");
    	}
    	if(logger.isDebugEnabled()){
    		logger.debug("commons-logging-jcl debug message");
    	}
    	if(logger.isInfoEnabled()){
    		logger.info("commons-logging-jcl info message");
    	}
    }

徹底是用commons-logging的API來完成日誌編寫

##6.3 使用案例分析

logback自己的使用其實就和slf4j綁定了起來,如今要想指定commons-logging的底層log實現是logback,則須要2步走

  • 第一步: 先將commons-logging底層的log實現轉向slf4j (jcl-over-slf4j乾的事)
  • 第二步: 再根據slf4j的選擇底層日誌原理,咱們使之選擇上logback

這樣就能夠完成commons-logging與logback的集成。即寫着commons-logging的API,底層倒是logback來進行輸出

而後來具體分析下整個過程的源碼實現:

  • 1 先看下jcl-over-slf4j都有哪些內容(它能夠替代了commons-logging),以下圖 jcl轉向slf4j

    • 1.1 commons-logging中的Log接口和LogFactory類等

      這是咱們使用commons-logging編寫須要的接口和類

    • 1.2 去掉了commons-logging原生包中的一些Log實現和默認的LogFactoryImpl

      只有SLF4JLog實現和SLF4JLogFactory

    這就是jcl-over-slf4j的大體內容

    這裏能夠與commons-logging原生包中的內容進行下對比。原生包中的內容以下:

    commons-logging包中的大體內容

  • 2 獲取獲取LogFactory的過程

    jcl-over-slf4j包中的LogFactory和commons-logging中原生的LogFactory不同,jcl-over-slf4j中的LogFactory直接限制死,是SLF4JLogFactory,源碼以下:

    public abstract class LogFactory {
    	static LogFactory logFactory = new SLF4JLogFactory();
    	//略
    }
  • 3 根據LogFactory獲取Log的過程

    這就須要看下jcl-over-slf4j包中的SLF4JLogFactory的源碼內容:

    Log newInstance;
    Logger slf4jLogger = LoggerFactory.getLogger(name);
    if (slf4jLogger instanceof LocationAwareLogger) {
        newInstance = new SLF4JLocationAwareLog((LocationAwareLogger) slf4jLogger);
    } else {
        newInstance = new SLF4JLog(slf4jLogger);
    }

    能夠看到實際上是用slf4j的LoggerFactory先建立一個slf4j的Logger實例(這其實就是單獨使用logback的使用方式,見logback原生案例)。

    而後再將這個Logger實例封裝成common-logging定義的Log接口實現,即SLF4JLog或者SLF4JLocationAwareLog實例。

    因此咱們使用的commons-logging的Log接口實例都是委託給slf4j建立的Logger實例(slf4j的這個實例又是選擇logbakc後產生的,即slf4j產生的Logger實例最終仍是委託給logback中的Logger的)

#7 未完待續

這篇講解commons-logging與jul、log4j一、log4j二、logback的集成原理,內容很長了,就把slf4j與上述四者的集成放到下一篇文章

相關文章
相關標籤/搜索