Apache Commons Logging 是如何決定使用哪個(gè)日志實(shí)現(xiàn)類(lèi)的
Apache Commons Logging 像 SLF4J
一樣,是個(gè)通用日志框架,廣泛應(yīng)用在各個(gè)開(kāi)源組件中。說(shuō)其通用,是因?yàn)樗旧碇惶峁┝撕?jiǎn)單的日志輸出的實(shí)現(xiàn)
(org.apache.commons.logging.impl.SimpleLog和
org.apache.commons.logging.impl.NoOpLog),主要是為你統(tǒng)一使用其他專(zhuān)業(yè)日志實(shí)現(xiàn)(Log4j、jdk1.4
Logger、aavalon-Logkit)的方式,讓你在程序中看不到具體日志實(shí)現(xiàn)的代碼,以配置方式解藕。
那么 commons-logging 是怎么決定程序執(zhí)行時(shí)該使用哪個(gè)具體的日志實(shí)現(xiàn)呢?這里 commons-logging 有兩個(gè)步驟要做:
1. 定位 org.apache.commons.logging.LogFactory 的實(shí)現(xiàn)類(lèi)(這一步是關(guān)鍵)
2. 定位到的 LogFactory 實(shí)現(xiàn)類(lèi)決定使用哪個(gè) org.apache.commons.logging.Log 實(shí)現(xiàn)
那現(xiàn)在我們把注意力主要集中在 commons-logging 如何定位 LogFactory 實(shí)現(xiàn)類(lèi)上來(lái)。org.apche.commons.logging.LogFactory 是一個(gè)抽象類(lèi),所以需要一個(gè) LogFactory 具體類(lèi)。
通常我們用使用 commons-logging 時(shí)是在代碼中聲明:
Log log = LogFactory.getLog(UnmiTestLog.class);
在 getLog() 中是通過(guò) getFactory() 方法獲得具體的 LogFactory 實(shí)現(xiàn)類(lèi),究竟也體現(xiàn)在這個(gè)方法中,所以這里非常有必要把這個(gè)方法的代碼拉出來(lái)。下面是 commons-loggin1.0.3 的 LogFactory.getFactory() 代碼,在新版代碼定位 LogFactory 的邏輯是一樣的。
在代碼中,我已加上注釋?zhuān)芯彺娴?LogFactory 實(shí)現(xiàn)類(lèi),取緩存中的,注意緩存是與當(dāng)前應(yīng)用的類(lèi)加載器關(guān)聯(lián)的。若緩存中沒(méi)有的話按 1、2、3、4 的順序來(lái)找,現(xiàn)在就來(lái)說(shuō)說(shuō)查找 LogFactory 的順序:
1. 從系統(tǒng)屬性中查找鍵為 org.apache.commons.logging.LogFactory 的值作為 LogFactory 的實(shí)現(xiàn)類(lèi);卻通過(guò) System.getProperty("org.apache.commons.logging.LogFactory") 獲得
2. 使用 JDK1.3 jar 的 Service Provider Interface(SPI) 類(lèi)發(fā)現(xiàn)機(jī)制,從配置文件 META-INF/services/org.apache.commons.logging.LogFactory 的的第一行讀取 LogFactory 的實(shí)現(xiàn)類(lèi)名。這個(gè) META-INF/services/org.apache.commons.logging.LogFactory 文件可以是某個(gè) Web 應(yīng)用的根目錄中;也可以在 classpath 下,如某個(gè) Jar 包中,WebRoot/WEB-INF/classes 中等。這里需多加留心下 META-INF/services/org.apache.commons.logging.LogFactory 這個(gè)目錄層次及文件名。
3. 在 Classpath 下的 commons-logging.properties 文件中的,找到 org.apache.commons.logging.LogFactory 屬性值作為 LogFactory 實(shí)現(xiàn)類(lèi)
4. 前面三步未找個(gè) LogFactory 的實(shí)現(xiàn)類(lèi),或有任何異常的情況下,就用默認(rèn)的實(shí)現(xiàn)類(lèi),即 LogFactory 為我們準(zhǔn)備的 org.apache.commons.logging.impl.LogFactoryImpl
明白了以上的順序,可以幫助我們理解和解決一些實(shí)際的問(wèn)題,例如,為什么可以不用 commons-logging.properties 也是使用的 log4j 日志實(shí)現(xiàn),部署在 WAS 下的應(yīng)用 log4j 怎么就不能輸出日志了呢?
一 般,某個(gè)具體的 LogFactory 類(lèi)對(duì)應(yīng)就會(huì)使用與其相應(yīng)的 Logger 實(shí)現(xiàn),如 Log4jFactory.getLog() 得到的是 Log4JLogger 實(shí)例,WAS 的 TrLogFactory.getLog() 返回的是 TrLog 實(shí)例。
老師們教我們用 commons-logging 時(shí)也許會(huì)讓我們?cè)?classpath 下放一個(gè) commons-logging.properties 文件,并在這個(gè)文件中寫(xiě)上一行:
org.apache.commons.logging.LogFactory=org.apache.commons.logging.impl.Log4jFactory
或者是這么兩行:
org.apache.cmmons.logging.LogFactory=org.apache.commons.logging.impl.LogFactoryImpl
org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger
然 而我們基本都是用的 Log4j 來(lái)輸出日志,其實(shí)不管 commons-logging.properties 是第一種寫(xiě)法還是第二種寫(xiě)法都是多余的,回望 LogFactory.getFactory() 方法,還要再看看 org.apache.commons.logging.impl.LogFactoryImpl 的 getLogClassName() 方法便可知。
LogFactory.getFactory() 在前面三步找不到 LogFactory 實(shí)現(xiàn)類(lèi)時(shí),就會(huì)用默認(rèn)的 LogFactoryImpl,而默認(rèn)的 LogFactoryImpl.getLog() 時(shí),又會(huì)根據(jù)以下四個(gè)順序來(lái)決定返回什么 Log 實(shí)例,在 LogFactoryImpl.getLogClassName() 中體現(xiàn)了:
1. commons-logging.properties 中的 org.apache.commons.logging.Log 指定的 Log 實(shí)現(xiàn)類(lèi)
2. Log4j 可用就用 org.apache.commons.logging.impl.Log4JLogger
3. Jdk1.4 Logger 可用就用 org.apcache.commons.logging.impl.Jdk14Logger(JDK1.4 開(kāi)始自帶)
4. SimpleLog 可用就用 org.apache.commons.logging.impl.SimpleLog(commons-logging 自帶)
所 以這就是為什么了,使用了 commons-logging 的框架類(lèi),只要扔個(gè) log4j 的 jar,根本不用 commons-logging.properties 文件就會(huì)用 log4j 來(lái)輸出日志,當(dāng)然 log4j 自己的配置文件 log4j.xml 或 log4j.properties 是需要的。
那為什么在 Tomcat 或別的應(yīng)用服務(wù)器中 log4j 能正常輸出日志,一放到 WAS 下卻不靈了呢?原因是在 $WAS_HOME/lib/ws-commons-logging.jar 中有個(gè)文件 commons-logging.properties,其中有一行 org.apache.commons.logging.LogFactory=com.ibm.ws.commons.logging.TrLogFactory, 雖然你的應(yīng)用中可能也有一個(gè) commons-logging.properties,可是很不幸,WAS 自己的 commons-logging.properties 優(yōu)先了,原因是類(lèi)加載器的委托機(jī)制在作用,所以最終 log4j 沒(méi)派上用場(chǎng),被 com.ibm.ws.commons.logging.TrLog 取而代之了,解決辦法是要搶在它之前,比系統(tǒng)屬性中聲明 LogFactory 實(shí)現(xiàn)類(lèi),或是在 META-INF/services/org.apache.commons.logging.LogFactory 中指名 Log4jFactory 實(shí)現(xiàn)類(lèi)名。
以后在使用 commons-logging 通用日志框架時(shí),若出現(xiàn)什么問(wèn)應(yīng)具體情況具體分析,相信都不會(huì)變離本篇中能解釋的情況。
那么 commons-logging 是怎么決定程序執(zhí)行時(shí)該使用哪個(gè)具體的日志實(shí)現(xiàn)呢?這里 commons-logging 有兩個(gè)步驟要做:
1. 定位 org.apache.commons.logging.LogFactory 的實(shí)現(xiàn)類(lèi)(這一步是關(guān)鍵)
2. 定位到的 LogFactory 實(shí)現(xiàn)類(lèi)決定使用哪個(gè) org.apache.commons.logging.Log 實(shí)現(xiàn)
那現(xiàn)在我們把注意力主要集中在 commons-logging 如何定位 LogFactory 實(shí)現(xiàn)類(lèi)上來(lái)。org.apche.commons.logging.LogFactory 是一個(gè)抽象類(lèi),所以需要一個(gè) LogFactory 具體類(lèi)。
通常我們用使用 commons-logging 時(shí)是在代碼中聲明:
Log log = LogFactory.getLog(UnmiTestLog.class);
在 getLog() 中是通過(guò) getFactory() 方法獲得具體的 LogFactory 實(shí)現(xiàn)類(lèi),究竟也體現(xiàn)在這個(gè)方法中,所以這里非常有必要把這個(gè)方法的代碼拉出來(lái)。下面是 commons-loggin1.0.3 的 LogFactory.getFactory() 代碼,在新版代碼定位 LogFactory 的邏輯是一樣的。
- public static LogFactory getFactory() throws LogConfigurationException {
- // Identify the class loader we will be using
- // 找到應(yīng)用自身所用的加載器
- //在 WAS 5.1 下是 com.ibm.ws.classloader.CompoundClassLoader
- ClassLoader contextClassLoader =
- (ClassLoader)AccessController.doPrivileged(
- new PrivilegedAction() {
- public Object run() {
- return getContextClassLoader();
- }
- });
- // Return any previously registered factory for this class loader
- // 看看是否有緩存的與此類(lèi)加載器關(guān)聯(lián)的 LogFactory 實(shí)例,有則返回
- LogFactory factory = getCachedFactory(contextClassLoader);
- if (factory != null)
- return factory;
- // Load properties file..
- // will be used one way or another in the end.
- // 加載應(yīng)用的 Classpath 下的屬性文件 commons-logging.properties
- // FACTORY_PROPERTIES 常量值是 commons-logging.properties
- // commons-logging 一般在這個(gè)文件里指定 LogFactory 的實(shí)現(xiàn)類(lèi)
- // 注意,它只是去加載這個(gè)屬性文件,并不馬上用里面配置的 LogFactory 類(lèi)
- Properties props=null;
- try {
- InputStream stream = getResourceAsStream(contextClassLoader,
- FACTORY_PROPERTIES);
- if (stream != null) {
- props = new Properties();
- props.load(stream);
- stream.close();
- }
- } catch (IOException e) {
- } catch (SecurityException e) {
- }
- /**** 從下面開(kāi)始就是 commons-logging 按什么順找到 LogFactory 實(shí)現(xiàn)類(lèi) ****/
- // First, try the system property
- // 1. 查找系統(tǒng)屬性 FACTORY_PROPERTY(org.apache.commons.logging.LogFactory)
- // 的值所對(duì)應(yīng)的 LogFactory 實(shí)現(xiàn)類(lèi)
- try {
- String factoryClass = System.getProperty(FACTORY_PROPERTY);
- if (factoryClass != null) {
- factory = newFactory(factoryClass, contextClassLoader);
- }
- } catch (SecurityException e) {
- ; // ignore
- }
- // Second, try to find a service by using the JDK1.3 jar
- // discovery mechanism. This will allow users to plug a logger
- // by just placing it in the lib/ directory of the webapp ( or in
- // CLASSPATH or equivalent ). This is similar with the second
- // step, except that it uses the (standard?) jdk1.3 location in the jar.
- // 2. 使用 JDK1.3 jar 的 Service Provider Interface(SPI) 類(lèi)發(fā)現(xiàn)機(jī)制
- // 從配置文件 SERVICE_ID(META-INF/services/org.apache.commons.logging.LogFactory)
- // 的第一行讀取 LogFactory 的實(shí)現(xiàn)類(lèi)名
- // 這個(gè) META-INF 目錄可以是 WebRoot 的 META-INF,也可以是 classpath 下的 META-INF 目錄
- if (factory == null) {
- try {
- InputStream is = getResourceAsStream(contextClassLoader,
- SERVICE_ID);
- if( is != null ) {
- // This code is needed by EBCDIC and other strange systems.
- // It's a fix for bugs reported in xerces
- BufferedReader rd;
- try {
- rd = new BufferedReader(new InputStreamReader(is, "UTF-8"));
- } catch (java.io.UnsupportedEncodingException e) {
- rd = new BufferedReader(new InputStreamReader(is));
- }
- String factoryClassName = rd.readLine();
- rd.close();
- if (factoryClassName != null &&
- ! "".equals(factoryClassName)) {
- factory= newFactory( factoryClassName, contextClassLoader );
- }
- }
- } catch( Exception ex ) {
- ;
- }
- }
- // Third try a properties file.
- // If the properties file exists, it'll be read and the properties
- // used. IMHO ( costin ) System property and JDK1.3 jar service
- // should be enough for detecting the class name. The properties
- // should be used to set the attributes ( which may be specific to
- // the webapp, even if a default logger is set at JVM level by a
- // system property )
- // 3. 現(xiàn)在才輪到用前面加載的 commons-logging.properties 文件中的
- // FACTORY_PROPERTY(org.apache.commons.logging.LogFactory) 屬性指定的 LogFactory 實(shí)現(xiàn)類(lèi)
- if (factory == null && props != null) {
- String factoryClass = props.getProperty(FACTORY_PROPERTY);
- if (factoryClass != null) {
- factory = newFactory(factoryClass, contextClassLoader);
- }
- }
- // Fourth, try the fallback implementation class
- // 4. 前面幾步?jīng)]有找到 LogFactory 的實(shí)現(xiàn)類(lèi)或有異常的話就用默認(rèn)的實(shí)現(xiàn)類(lèi)
- // 即 LogFactory 為我們準(zhǔn)備的 FACTORY_DEFAULT(org.apache.commons.logging.impl.LogFactoryImpl)
- if (factory == null) {
- factory = newFactory(FACTORY_DEFAULT, LogFactory.class.getClassLoader());
- }
- if (factory != null) {
- /**
- * Always cache using context class loader..
- * 緩存所用的實(shí)現(xiàn)類(lèi),以后直接使用緩沖中的 LogFactory 實(shí)現(xiàn)類(lèi)
- */
- cacheFactory(contextClassLoader, factory);
- if( props!=null ) {
- Enumeration names = props.propertyNames();
- while (names.hasMoreElements()) {
- String name = (String) names.nextElement();
- String value = props.getProperty(name);
- factory.setAttribute(name, value);
- }
- }
- }
- return factory;
- }
在代碼中,我已加上注釋?zhuān)芯彺娴?LogFactory 實(shí)現(xiàn)類(lèi),取緩存中的,注意緩存是與當(dāng)前應(yīng)用的類(lèi)加載器關(guān)聯(lián)的。若緩存中沒(méi)有的話按 1、2、3、4 的順序來(lái)找,現(xiàn)在就來(lái)說(shuō)說(shuō)查找 LogFactory 的順序:
1. 從系統(tǒng)屬性中查找鍵為 org.apache.commons.logging.LogFactory 的值作為 LogFactory 的實(shí)現(xiàn)類(lèi);卻通過(guò) System.getProperty("org.apache.commons.logging.LogFactory") 獲得
2. 使用 JDK1.3 jar 的 Service Provider Interface(SPI) 類(lèi)發(fā)現(xiàn)機(jī)制,從配置文件 META-INF/services/org.apache.commons.logging.LogFactory 的的第一行讀取 LogFactory 的實(shí)現(xiàn)類(lèi)名。這個(gè) META-INF/services/org.apache.commons.logging.LogFactory 文件可以是某個(gè) Web 應(yīng)用的根目錄中;也可以在 classpath 下,如某個(gè) Jar 包中,WebRoot/WEB-INF/classes 中等。這里需多加留心下 META-INF/services/org.apache.commons.logging.LogFactory 這個(gè)目錄層次及文件名。
3. 在 Classpath 下的 commons-logging.properties 文件中的,找到 org.apache.commons.logging.LogFactory 屬性值作為 LogFactory 實(shí)現(xiàn)類(lèi)
4. 前面三步未找個(gè) LogFactory 的實(shí)現(xiàn)類(lèi),或有任何異常的情況下,就用默認(rèn)的實(shí)現(xiàn)類(lèi),即 LogFactory 為我們準(zhǔn)備的 org.apache.commons.logging.impl.LogFactoryImpl
明白了以上的順序,可以幫助我們理解和解決一些實(shí)際的問(wèn)題,例如,為什么可以不用 commons-logging.properties 也是使用的 log4j 日志實(shí)現(xiàn),部署在 WAS 下的應(yīng)用 log4j 怎么就不能輸出日志了呢?
一 般,某個(gè)具體的 LogFactory 類(lèi)對(duì)應(yīng)就會(huì)使用與其相應(yīng)的 Logger 實(shí)現(xiàn),如 Log4jFactory.getLog() 得到的是 Log4JLogger 實(shí)例,WAS 的 TrLogFactory.getLog() 返回的是 TrLog 實(shí)例。
老師們教我們用 commons-logging 時(shí)也許會(huì)讓我們?cè)?classpath 下放一個(gè) commons-logging.properties 文件,并在這個(gè)文件中寫(xiě)上一行:
org.apache.commons.logging.LogFactory=org.apache.commons.logging.impl.Log4jFactory
或者是這么兩行:
org.apache.cmmons.logging.LogFactory=org.apache.commons.logging.impl.LogFactoryImpl
org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger
然 而我們基本都是用的 Log4j 來(lái)輸出日志,其實(shí)不管 commons-logging.properties 是第一種寫(xiě)法還是第二種寫(xiě)法都是多余的,回望 LogFactory.getFactory() 方法,還要再看看 org.apache.commons.logging.impl.LogFactoryImpl 的 getLogClassName() 方法便可知。
LogFactory.getFactory() 在前面三步找不到 LogFactory 實(shí)現(xiàn)類(lèi)時(shí),就會(huì)用默認(rèn)的 LogFactoryImpl,而默認(rèn)的 LogFactoryImpl.getLog() 時(shí),又會(huì)根據(jù)以下四個(gè)順序來(lái)決定返回什么 Log 實(shí)例,在 LogFactoryImpl.getLogClassName() 中體現(xiàn)了:
1. commons-logging.properties 中的 org.apache.commons.logging.Log 指定的 Log 實(shí)現(xiàn)類(lèi)
2. Log4j 可用就用 org.apache.commons.logging.impl.Log4JLogger
3. Jdk1.4 Logger 可用就用 org.apcache.commons.logging.impl.Jdk14Logger(JDK1.4 開(kāi)始自帶)
4. SimpleLog 可用就用 org.apache.commons.logging.impl.SimpleLog(commons-logging 自帶)
所 以這就是為什么了,使用了 commons-logging 的框架類(lèi),只要扔個(gè) log4j 的 jar,根本不用 commons-logging.properties 文件就會(huì)用 log4j 來(lái)輸出日志,當(dāng)然 log4j 自己的配置文件 log4j.xml 或 log4j.properties 是需要的。
那為什么在 Tomcat 或別的應(yīng)用服務(wù)器中 log4j 能正常輸出日志,一放到 WAS 下卻不靈了呢?原因是在 $WAS_HOME/lib/ws-commons-logging.jar 中有個(gè)文件 commons-logging.properties,其中有一行 org.apache.commons.logging.LogFactory=com.ibm.ws.commons.logging.TrLogFactory, 雖然你的應(yīng)用中可能也有一個(gè) commons-logging.properties,可是很不幸,WAS 自己的 commons-logging.properties 優(yōu)先了,原因是類(lèi)加載器的委托機(jī)制在作用,所以最終 log4j 沒(méi)派上用場(chǎng),被 com.ibm.ws.commons.logging.TrLog 取而代之了,解決辦法是要搶在它之前,比系統(tǒng)屬性中聲明 LogFactory 實(shí)現(xiàn)類(lèi),或是在 META-INF/services/org.apache.commons.logging.LogFactory 中指名 Log4jFactory 實(shí)現(xiàn)類(lèi)名。
以后在使用 commons-logging 通用日志框架時(shí),若出現(xiàn)什么問(wèn)應(yīng)具體情況具體分析,相信都不會(huì)變離本篇中能解釋的情況。
posted on 2009-05-15 10:45 百科 閱讀(642) 評(píng)論(0) 編輯 收藏