咱們在作項目時,日誌的記錄是必不可少的一項任務,而咱們一般是使用 apache 的 log4j 日誌管理工具。然而,在項目中,咱們常常會看到兩個 jar 包:commons-logging.jar 和 log4j.rar。爲何咱們在使用 log4j 的同時還要引入 commons-logging.jar 呢,或者說不用 commons-logging.jar 可不能夠,這二者之間究竟是怎麼的一種關係呢?
做爲記錄日誌的工具,它至少應該包含以下幾個組成部分(組件):
1. Logger
記錄器組件負責產生日誌,並可以對日誌信息進行分類篩選,控制什麼樣的日誌應該被輸出,什麼樣的日誌應該被忽略。它還有一個重要的屬性 - 日誌級別。無論何種日誌記錄工具,大概包含了以下幾種日誌級別:DEBUG, INFO, WARN, ERROR 和 FATAL。
2. Level
日誌級別組件。
3. Appender
日誌記錄工具基本上經過 Appender 組件來輸出到目的地的,一個 Appender 實例就表示了一個輸出的目的地。
4. Layout
Layout 組件負責格式化輸出的日誌信息,一個 Appender 只能有一個 Layout。
咱們再來看看 log4j.jar,打開 jar 包,咱們能夠看到 Logger.class(Logger),Level.class(Level), FileAppender.class(Appender), HTMLLayout.class(Layout)。其它的咱們先忽略不看,這幾個字節碼文件正好是記錄日誌必不可少的幾個組件。
接下來看看 commons-logging 中的 org.apache.commons.logging.Log.java 源碼: php
package org.apache.commons.logging; public interface Log { public boolean isDebugEnabled(); public boolean isErrorEnabled(); public boolean isFatalEnabled(); public boolean isInfoEnabled(); public boolean isTraceEnabled(); public boolean isWarnEnabled(); public void trace(Object message); public void trace(Object message, Throwable t); public void debug(Object message); public void debug(Object message, Throwable t); public void info(Object message); public void info(Object message, Throwable t); public void warn(Object message); public void warn(Object message, Throwable t); public void error(Object message); public void error(Object message, Throwable t); public void fatal(Object message); public void fatal(Object message, Throwable t); }
很顯然,只要實現了 Log 接口,它就是一個名副其實的 Logger 組件,也驗證了 Logger 組件具備日誌級別的屬性。繼續看 commons-logging org.apache.commons.logging.impl 包下的幾個類的源碼片斷:java
package org.apache.commons.logging.impl;
import org.apache.commons.logging.Log; import org.apache.log4j.Logger; import org.apache.log4j.Priority; import org.apache.log4j.Level; import ......
public class Log4JLogger implements Log, Serializable { // 對 org.apache.commons.logging.Log 的實現 ...... }
------------------------------------------------------------------
package org.apache.commons.logging.impl;
import org.apache.commons.logging.Log; import java.io.Serializable; import java.util.logging.Level; import java.util.logging.Logger;
public class Jdk14Logger implements Log, Serializable { // 對 org.apache.commons.logging.Log 的實現 ...... }
好了,分析到這裏,咱們應該知道,真正的記錄日誌的工具是 log4j 和 sun 公司提供的日誌工具。而 commons-logging 把這兩個(實際上,在 org.apache.commons.logging.impl 包下,commons-logging 僅僅爲咱們封裝了 log4j 和 sun logger)記錄日誌的工具從新封裝了一遍(Log4JLogger.java 和 Jdk14Logger.java),能夠認爲 org.apache.commons.logging.Log 是個傀儡,它只是提供了對外的統一接口。所以咱們只要能拿到 org.apache.commons.logging.Log,而不用關注到底使用的是 log4j 仍是 sun logger。正如咱們常常在項目中這樣寫:c++
// Run 是咱們本身寫的類,LogFactory 是一個專爲提供 Log 的工廠(abstract class) private static final Log logger = LogFactory.getLog(Run.class);
既然如此,咱們向構建路徑加了 commons-logging.jar 和 log4j.jar 兩個 jar 包,那咱們的應用程序到底使用的 log4j 仍是 sun logger 呢?咱們能不能認爲因爲加了 log4j.jar 包,就認爲系統使用的就是 log4j 呢?事實上固然不是這樣的,那我還認爲我正在使用 jdk 而認爲系統使用的是 sun logger 呢。使用 Spring 的朋友能夠在 web.xml 中看到以下 listener 片斷:web
<listener> <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class> </listener>
這是由 Spring 爲咱們提供的實現了標準的 servlet api 中的 javax.servlet.ServletContextListener 接口,用於在 web 容器啓動時作一些初始化操做。咱們逐層進入 Spring 的源碼,能夠看到以下代碼: spring
Log4jConfigurer.initLogging(location, refreshInterval);
終於找到了 org.springframework.util.Log4jConfigurer,這正是 log4j 提供給咱們的初始化日誌的類。至此,咱們終於明白了咱們系統的的確確使用的是 log4j 的日誌工具。
但是問題又來了,org.apache.commons.logging.Log 和 org.apache.log4j.Logger 這兩個類,經過包名咱們能夠發現它們都是 apache 的項目,既然以下,爲什麼要動如此大的動做搞兩個東西(指的是 commons-logging 和 log4j)出來呢?事實上,在 sun 開發 logger 前,apache 項目已經開發了功能強大的 log4j 日誌工具,並向 sun 推薦將其歸入到 jdk 的一部分,但是 sun 拒絕了 apache 的提議,sun 後來本身開發了一套記錄日誌的工具。但是如今的開源項目都使用的是 log4j,log4j 已經成了事實上的標準,但因爲又有一部分開發者在使用 sun logger,所以 apache 才推出 commons-logging,使得咱們沒必要關注咱們正在使用何種日誌工具。 apache
補充:編程
Apache針對不一樣的語言平臺爲作了一系列日誌工具包,可應用於java、.net、php、c++,這些日誌包都是免費的,使用很是方便,能夠極大提升編程效率。而且,Apache爲了讓衆多的日誌工具備一個相同操做方式,還實現作了一個通用日誌工具包:commons-logging,也稱Jakarta Commons Logging (JCL)。commons-logging是爲那些須要創建在不一樣環境下使用不一樣日誌架構的組件或庫的開發者建立的,其中包括Apache Log4j以及Java log的日誌架構。把日誌信息commons-logging的Log接口,並由commons-logging在運行時決定使用哪一種日誌架構。如今,Apache通用日誌工具commons-logging和Log4j已經成爲Java日誌的標準工具。api
JCL有兩個基本的抽象類:Log(基本記錄器)和LogFactory(負責建立Log實例)。當commons-logging.jar被加入到CLASSPATH(一般將commons-logging.jar放在web project下的WebContent\WEB-INF\lib目錄中)以後,它會合理地猜想你想用的日誌工具,而後進行自我設置,用戶根本不須要作任何設置。默認的LogFactory是按照下列的步驟去發現並決定那個日誌工具將被使用的(按照順序,尋找過程會在找到第一個工具時停止,這個順序很是重要):架構
尋找當前factory中名叫org.apache.commons.logging.Log配置屬性的值app
尋找系統中屬性中名叫org.apache.commons.logging.Log的值
若是應用程序的classpath中有log4j,則使用相關的包裝(wrapper)類(Log4JLogger)
若是應用程序運行在jdk1.4的系統中,使用相關的包裝類(Jdk14Logger)
使用簡易日誌包裝類(SimpleLog)
上述的加載順序能夠經過commons-logging中LogSource.java的源代碼能夠看出,源代碼以下,具體能夠參考註釋:
commons-logging的工做過程以下,
package log.sample; public class ca { static { // Is Log4J Available?用戶Log4J是否可用 try { /** * 經過Class.forName("org.apache.log4j.Logger"))來查找Log4J, * 只有將log4j.jar添加到classpath之後才能找到, * 這也是爲何默認狀況下只要將log4j.jar文件放在lib文件夾中 * 而不須要在common-logging.properties配置文件中進行配置就能自動使用log4j的緣由 */ if (null != Class.forName("org.apache.log4j.Logger")) { log4jIsAvailable = true; } else { log4jIsAvailable = false; } } catch (Throwable t) { log4jIsAvailable = false; } // Is JDK 1.4 Logging Available?原來同上面的Log4J try { if ((null != Class.forName("java.util.logging.Logger")) && (null != Class.forName("org.apache.commons.logging.impl.Jdk14Logger"))) { jdk14IsAvailable = true; } else { jdk14IsAvailable = false; } } catch (Throwable t) { jdk14IsAvailable = false; } // Set the default Log implementation,經過common-logging.properties配置文件來決定日誌實現方式 String name = null; try { name = System.getProperty("org.apache.commons.logging.log"); if (name == null) { name = System.getProperty("org.apache.commons.logging.Log"); } } catch (Throwable t) { } if (name != null) { try { setLogImplementation(name); } catch (Throwable t) { try { setLogImplementation ("org.apache.commons.logging.impl.NoOpLog"); } catch (Throwable u) { ; } } } else { try { if (log4jIsAvailable) {//若是log4j可用,默認優先使用Log4JLogger setLogImplementation ("org.apache.commons.logging.impl.Log4JLogger"); } else if (jdk14IsAvailable) {//第二優先使用Jdk14Logger setLogImplementation ("org.apache.commons.logging.impl.Jdk14Logger"); } else {//最後使用commoms-logging中的自帶的實現,但它不進行任何操做 setLogImplementation ("org.apache.commons.logging.impl.NoOpLog"); } } catch (Throwable t) { try { setLogImplementation ("org.apache.commons.logging.impl.NoOpLog"); } catch (Throwable u) { ; } } } } }
commonslogging自動加載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" };