java常用的日志有以下幾種 :
一、jdk自帶的java.util.logging包下的日志功能, 不常用。
二、commons-logging + log4j 的搭配 。log4j是日志功能的具體實現,而commons-logging則是日志接口的聲明,它的出現也是為了解決應用和具體的日志框架解耦合的問題,它采用的是運行時動態綁定的方式來決定使用哪個日志框架。 什么是動態綁定 ?參考commons-logging-1.1.3的org.apache.commons.logging.LogFactory類的方法:
public static Log getLog(Class clazz) throws LogConfigurationException {
return getFactory().getInstance(clazz);
}
關鍵在getFactory()返回的LogFactory(日志工廠) 是什么 !
我們進一步看獲取日志工廠的方法:
public static LogFactory getFactory() throws LogConfigurationException
從419-646行:代碼比較長不列出,闡述如下:
1、獲取線程上下文ClassLoader(默認是加載應用的ClassLoader)。
2、看線程上下文ClassLoader是否有緩存的LogFactory,有就直接返回LogFactory 。
3、找classpath下面的commons-logging.properties ,如果use_tccl屬性為false,則不使用Thread ContextClassLoader .默認use_tccl 為true ;.
4、找是否有org.apache.commons.logging.LogFactory 這個系統配置項,利用Thread ContextClassLoader 加載org.apache.commons.logging.LogFactory ,並創建一個LogFactory實例(在后面要描述的jcl-over-slf4j包和commons-logging包里面都有這個類。)
5、如果還找不到,則找包含了META-INF/services/org.apache.commons.logging.LogFactory 這個文件的Jar包,找到了就用這個文件里面的LogFactory類名創建LogFactory實例
6、如果還找不到,則找commons-logging.properties 文件,利用屬性文件里面的org.apache.commons.logging.LogFactory 屬性獲取LogFactory類並創建LogFactory實例。
7、還找不到,則找org.apache.commons.logging.impl.LogFactoryImpl 這個類 創建實例。
8、創建好LogFactory實例以后,會
cacheFactory(contextClassLoader, factory);
把LogFactory對象和線程上下文ClassLoader在map中關聯起來,加速LogFactory對象的獲取。
從以上的分析來看,我們假設一種簡單的場景,
沒有org.apache.commons.logging.LogFactory 這個系統配置項,classpath下沒有包含META-INF/services/org.apache.commons.logging.LogFactory 這個文件的Jar包、沒有commons-logging.properties 文件,只有commons-logging和Log4j的jar .
此時會使用org.apache.commons.logging.impl.LogFactoryImpl 這個類(在commons-logging的jar 包里面) 來創建Log實例,而LogFactoryImpl在獲取Log類的時候,會參照下面一個順序:
private static final String[] classesToDiscover = {
LOGGING_IMPL_LOG4J_LOGGER,
"org.apache.commons.logging.impl.Jdk14Logger",
"org.apache.commons.logging.impl.Jdk13LumberjackLogger",
"org.apache.commons.logging.impl.SimpleLog"
};
來使用某一個日志器, 可以看到默認就是使用log4j日志的。
至此,我們弄清了commons-logging的動態綁定機制。
但是這種機制的問題在哪兒呢,由於它使用了ClassLoader尋找和載入底層的日志庫, 導致了象OSGI這樣的框架無法正常工作,因為OSGI的不同的插件使用自己的ClassLoader。 OSGI的這種機制保證了插件互相獨立,然而卻使Apache Common-Logging無法工作。
三、slf4j + logback的搭配.
slf4j和logback是同一個人開發的,所以不像slf4j + log4j搭配使用時,還需要加上一個所謂的適配器jar包(比如:slf4j-log4j.jar,適配器包的作用就是通過class composition的適配方式把log4j的日志轉換成slf4j的接口)。
和commons-logging在運行時確定日志框架不同,slf4j采用的方式是在編譯時靜態綁定真正的log庫, 它的原理是:
(1)用ClassLoader找包含了org.slf4j.impl.StaticLoggerBinder類的Jar包,這個Jar就包含在logback-classic.jar、slf4j-log4j等包里面 ,因此,如果在classpath下面
同時包含了這些jar的話, 程序將會出現一個警告,提示classpath下面有多個slf4j的綁定 。這也是我們在使用slf4j日志時需要注意的問題,不過slf4j並不會報錯, 而是選擇一個
jar中的StaticLoggerBinder,所以在使用的時候要特別注意不能同時包含logback-classic 、 slf4j-log4j、slf4j-jdk等橋接包 。

我們假設使用的是logback做日志框架 ,這時會拿到logback-classic.jar里面的org.slf4j.impl.StaticLoggerBinder ,這個StaticLoggerBinder獲取到日志工廠以后,

利用日志工廠獲取到ch.qos.logback.classic.Logger, 接下來使用的就是logback的日志了。
而如果是classpath下面包含的是slf4j-log4j這個橋接包, 那么拿到的就是Log4j的LogFactory,從而也就用到了log4j的日志。
四、既然slf4j的靜態綁定方式解決了commons-logging動態綁定方式在運行時可能拿不到日志接口實現類的問題,而且號稱效率比log4j要更好(為什么更好,后續還會深入分析) 那直接都換成slf4j+logback的日志方式不就行了么,但現實是很多的應用之前都是建立在commons-logging+log4j的日志方式上的,有什么辦法不改動應用的代碼,達到commons-logging日志轉到slf4j的目的么?把commons-logging.jar替換掉就好了,看下圖:

具體的原理是什么呢? 以LogFactory.getLog("loggerName")為例:
1、org.apache.commons.logging.LogFactory類被jcl-over-slf4j包里面的同包同名類替換掉了。
2、獲取到的日志工廠是一個SLF4jLogFactory ,這個日志工廠在獲取org.apache.commons.logging.Log 實例的時候,先基於前面描述的slf4j靜態綁定機制,拿到了一個org.slf4j.Logger,然后用一個適配器類做接口轉換,把slf4j的日志轉換成commons-logging的日志器。
總結下來,我們有以下幾點啟發:
1、適配器模式可以幫助我們做各種接口包的無縫集成。
2、復雜的java應用還是在classloader機制上做文章,即使是slf4j的靜態綁定機制 ,其實也是在編譯時檢查了classpath下是否有多個jar包包含StaticLoggerBinder類,
真正運行的時候由線程上下文的classloader(默認是app classloader)來加載這個StaticLoggerBinder類而已。
3、要分清Java日志系統里面各種包的作用:
1)日志接口包:commons-logging , slf4j-api
2) 日志框架包:log4j, logback,
3) 日志適配器包:slf4j-log4j,slf4j-jdk
4) 把某一個日志接口轉換到另一個日志接口的橋接包:jcl-over-slf4j,log4j-over-slf4j 等。
