#1 系列目錄java
前面介紹了jdk自帶的logging、log4j一、log4j二、logback等實際的日誌框架web
對於開發者而言,每種日誌都有不一樣的寫法。若是咱們以實際的日誌框架來進行編寫,代碼就限制死了,以後就很難再更換日誌系統,很難作到無縫切換。apache
java web開發就常常提到一項原則:面向接口編程,而不是面向實現編程編程
因此咱們應該是按照一套統一的API來進行日誌編程,實際的日誌框架來實現這套API,這樣的話,即便更換日誌框架,也能夠作到無縫切換。api
這就是commons-logging與slf4j的初衷。app
下面就來介紹下commons-logging與slf4j這兩個門面如何與上述四個實際的日誌框架進行集成的呢框架
介紹以前先說明下日誌簡稱:maven
#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個階段
commons-logging默認提供的LogFactory實現:LogFactoryImpl commons-logging默認提供的Log實現:Jdk14Logger、Log4JLogger、SimpleLog。
來看下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包
對應的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的過程
因此commons-logging會使用默認的LogFactoryImpl做爲LogFactory
2 根據LogFactory獲取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包
對應的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包
對應的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包的大體內容:
這裏面的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包
對應的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與logback的集成。即寫着commons-logging的API,底層倒是logback來進行輸出
而後來具體分析下整個過程的源碼實現:
1 先看下jcl-over-slf4j都有哪些內容(它能夠替代了commons-logging),以下圖
1.1 commons-logging中的Log接口和LogFactory類等
這是咱們使用commons-logging編寫須要的接口和類
1.2 去掉了commons-logging原生包中的一些Log實現和默認的LogFactoryImpl
只有SLF4JLog實現和SLF4JLogFactory
這就是jcl-over-slf4j的大體內容
這裏能夠與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與上述四者的集成放到下一篇文章