commons-logging日誌實現解耦
阿新 • • 發佈:2017-07-21
truct illegal 工廠 man int 行為 efault context buffered 一、需要解耦
日誌是實際應用中的一個重要部分,日誌系統也有許多開源的實現,如java.util.logging, logback, log4j系列等。
在使用日誌系統時,如果與具體的日誌實現耦合太深,如使用log4j作為日誌的實現,在每一處需要打印日誌的地方都會創建日誌實例:
logger = LogManager.getLogger("instanceName");
當由於性能或者其他方面的需求需要更換日誌實現時,如log4j升級到log4j2,就不得不替換每一處日誌實例得創建,將會是一個噩夢般的工作。
因此需要將應用與具體的日誌實現解耦。commons-logging提供了日誌實現解耦方案,當然commons-logging也提供了簡單的日誌實現。
commons-logging的官網地址為:http://commons.apache.org/proper/commons-logging/
其中對commons-logging的介紹:
The Logging package is an ultra-thin bridge between different logging implementations. A library that uses the commons-logging API can be used with any logging implementation at runtime. Commons-logging comes with support for a number of popular logging implementations, and writing adapters for others is a reasonably simple task.
二、commons-logging簡單日誌實現:
commons-logging除了提供解耦合方案外,也提供了org.apache.commons.logging.impl.SimpleLog作為簡單的日誌實現。
使用SimpleLog,需要做相應配置:
1. 配置commons-logging.properties
在classpath根路徑放置名為commons-logging.properties的文件(該路徑不可改變),內容為:
org.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog
2. 創建Log實例
名為配置好了之後就可以創建Log實例並直接使用:
private static final Log logger = LogFactory.getLog(CommonsLogTest.class);
輸出為,默認日誌級別為INFO
3. 輸出配置
當然,SimpleLog也可以做一些簡單的輸出配置,打開SimpleLog的源碼,可以看到在初始化時:
static {
// Add props from the resource simplelog.properties
InputStream in = getResourceAsStream("simplelog.properties");
if(null != in) {
try {
simpleLogProps.load(in);
in.close();
} catch(java.io.IOException e) {
// ignored
}
}
showLogName = getBooleanProperty(systemPrefix + "showlogname", showLogName);
showShortName = getBooleanProperty(systemPrefix + "showShortLogname", showShortName);
showDateTime = getBooleanProperty(systemPrefix + "showdatetime", showDateTime);
if(showDateTime) {
dateTimeFormat = getStringProperty(systemPrefix + "dateTimeFormat",
dateTimeFormat);
try {
dateFormatter = new SimpleDateFormat(dateTimeFormat);
} catch(IllegalArgumentException e) {
// If the format pattern is invalid - use the default format
dateTimeFormat = DEFAULT_DATE_TIME_FORMAT;
dateFormatter = new SimpleDateFormat(dateTimeFormat);
}
}
}
可以看到最開始試圖從simplelog.properties讀取配置,若不存在後面指定了默認配置,因此只要在classpath根路徑放置simplelog.properties文件即可控制日誌輸出行為。
可以從http://commons.apache.org/proper/commons-logging/apidocs/org/apache/commons/logging/impl/SimpleLog.html 的關於SimpleLog的類介紹中了解配置詳情:
Simple implementation of Log that sends all enabled log messages, for all defined loggers, to System.err. The following system properties are supported to configure the behavior of this logger:
三、commons-logging解耦原理 commons-logging最核心有用的功能是解耦,它的SimpleLog實現性能比不上其他實現,如log4j等。 首先,日誌實例是通過LogFactory的getLog(String)方法創建的: LogFatory是一個抽象類,它負責加載具體的日誌實現,分析其Factory getFactory()方法: public static org.apache.commons.logging.LogFactory getFactory() throws LogConfigurationException { // Identify the class loader we will be using ClassLoader contextClassLoader = getContextClassLoaderInternal(); if (contextClassLoader == null) { // This is an odd enough situation to report about. This // output will be a nuisance on JDK1.1, as the system // classloader is null in that environment. if (isDiagnosticsEnabled()) { logDiagnostic("Context classloader is null."); } } // Return any previously registered factory for this class loader org.apache.commons.logging.LogFactory factory = getCachedFactory(contextClassLoader); if (factory != null) { return factory; } if (isDiagnosticsEnabled()) { logDiagnostic( "[LOOKUP] LogFactory implementation requested for the first time for context classloader " + objectId(contextClassLoader)); logHierarchy("[LOOKUP] ", contextClassLoader); } // Load properties file. // // If the properties file exists, then its contents are used as // "attributes" on the LogFactory implementation class. One particular // property may also control which LogFactory concrete subclass is // used, but only if other discovery mechanisms fail.. // // As the properties file (if it exists) will be used one way or // another in the end we may as well look for it first. // classpath根目錄下尋找commons-logging.properties Properties props = getConfigurationFile(contextClassLoader, FACTORY_PROPERTIES); // Determine whether we will be using the thread context class loader to // load logging classes or not by checking the loaded properties file (if any). // classpath根目錄下commons-logging.properties是否配置use_tccl ClassLoader baseClassLoader = contextClassLoader; if (props != null) { String useTCCLStr = props.getProperty(TCCL_KEY); if (useTCCLStr != null) { // The Boolean.valueOf(useTCCLStr).booleanValue() formulation // is required for Java 1.2 compatibility. if (Boolean.valueOf(useTCCLStr).booleanValue() == false) { // Don‘t use current context classloader when locating any // LogFactory or Log classes, just use the class that loaded // this abstract class. When this class is deployed in a shared // classpath of a container, it means webapps cannot deploy their // own logging implementations. It also means that it is up to the // implementation whether to load library-specific config files // from the TCCL or not. baseClassLoader = thisClassLoader; } } } // 這裏真正開始決定使用哪個factory // 首先,嘗試查找vm系統屬性org.apache.commons.logging.LogFactory,其是否指定factory // Determine which concrete LogFactory subclass to use. // First, try a global system property if (isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] Looking for system property [" + FACTORY_PROPERTY + "] to define the LogFactory subclass to use..."); } try { String factoryClass = getSystemProperty(FACTORY_PROPERTY, null); if (factoryClass != null) { if (isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] Creating an instance of LogFactory class ‘" + factoryClass + "‘ as specified by system property " + FACTORY_PROPERTY); } factory = newFactory(factoryClass, baseClassLoader, contextClassLoader); } else { if (isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] No system property [" + FACTORY_PROPERTY + "] defined."); } } } catch (SecurityException e) { if (isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] A security exception occurred while trying to create an" + " instance of the custom factory class" + ": [" + trim(e.getMessage()) + "]. Trying alternative implementations..."); } // ignore } catch (RuntimeException e) { // This is not consistent with the behaviour when a bad LogFactory class is // specified in a services file. // // One possible exception that can occur here is a ClassCastException when // the specified class wasn‘t castable to this LogFactory type. if (isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] An exception occurred while trying to create an" + " instance of the custom factory class" + ": [" + trim(e.getMessage()) + "] as specified by a system property."); } throw e; } // 第二,嘗試使用java spi服務發現機制,載META-INF/services下尋找org.apache.commons.logging.LogFactory實現 // Second, try to find a service by using the JDK1.3 class // discovery mechanism, which involves putting a file with the name // of an interface class in the META-INF/services directory, where the // contents of the file is a single line specifying a concrete class // that implements the desired interface. if (factory == null) { if (isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] Looking for a resource file of name [" + SERVICE_ID + "] to define the LogFactory subclass to use..."); } try { // META-INF/services/org.apache.commons.logging.LogFactory, SERVICE_ID final 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)) { if (isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] Creating an instance of LogFactory class " + factoryClassName + " as specified by file ‘" + SERVICE_ID + "‘ which was present in the path of the context classloader."); } factory = newFactory(factoryClassName, baseClassLoader, contextClassLoader); } } else { // is == null if (isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] No resource file with name ‘" + SERVICE_ID + "‘ found."); } } } catch (Exception ex) { // note: if the specified LogFactory class wasn‘t compatible with LogFactory // for some reason, a ClassCastException will be caught here, and attempts will // continue to find a compatible class. if (isDiagnosticsEnabled()) { logDiagnostic( "[LOOKUP] A security exception occurred while trying to create an" + " instance of the custom factory class" + ": [" + trim(ex.getMessage()) + "]. Trying alternative implementations..."); } // ignore } } // 第三,嘗試從classpath根目錄下的commons-logging.properties中查找org.apache.commons.logging.LogFactory屬性指定的factory // Third try looking into the properties file read earlier (if found) if (factory == null) { if (props != null) { if (isDiagnosticsEnabled()) { logDiagnostic( "[LOOKUP] Looking in properties file for entry with key ‘" + FACTORY_PROPERTY + "‘ to define the LogFactory subclass to use..."); } String factoryClass = props.getProperty(FACTORY_PROPERTY); if (factoryClass != null) { if (isDiagnosticsEnabled()) { logDiagnostic( "[LOOKUP] Properties file specifies LogFactory subclass ‘" + factoryClass + "‘"); } factory = newFactory(factoryClass, baseClassLoader, contextClassLoader); // TODO: think about whether we need to handle exceptions from newFactory } else { if (isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] Properties file has no entry specifying LogFactory subclass."); } } } else { if (isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] No properties file available to determine" + " LogFactory subclass from.."); } } } // 最後,使用後備factory實現,org.apache.commons.logging.impl.LogFactoryImpl // Fourth, try the fallback implementation class if (factory == null) { if (isDiagnosticsEnabled()) { logDiagnostic( "[LOOKUP] Loading the default LogFactory implementation ‘" + FACTORY_DEFAULT + "‘ via the same classloader that loaded this LogFactory" + " class (ie not looking in the context classloader)."); } // Note: unlike the above code which can try to load custom LogFactory // implementations via the TCCL, we don‘t try to load the default LogFactory // implementation via the context classloader because: // * that can cause problems (see comments in newFactory method) // * no-one should be customising the code of the default class // Yes, we do give up the ability for the child to ship a newer // version of the LogFactoryImpl class and have it used dynamically // by an old LogFactory class in the parent, but that isn‘t // necessarily a good idea anyway. factory = newFactory(FACTORY_DEFAULT, thisClassLoader, contextClassLoader); } if (factory != null) { /** * Always cache using context class loader. */ 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; } 可以看出,抽象類LogFactory加載具體實現的步驟如下: 1. 從vm系統屬性org.apache.commons.logging.LogFactory 2. 使用SPI服務發現機制,發現org.apache.commons.logging.LogFactory的實現 3. 查找classpath根目錄commons-logging.properties的org.apache.commons.logging.LogFactory屬性是否指定factory實現 4. 使用默認factory實現,org.apache.commons.logging.impl.LogFactoryImpl LogFactory的getLog()方法返回類型是org.apache.commons.logging.Log接口,提供了從trace到fatal方法。可以確定,如果日誌實現提供者只要實現該接口,並且使用繼承自org.apache.commons.logging.LogFactory的子類創建Log,必然可以構建一個松耦合的日誌系統。
四、log4j+commons-logging解耦 使用commons-logging解耦log4j,可按照下列步驟: 1. 將log4j和commons-logging依賴放入classpath: <!--commons-logging--> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> <!-- log4j --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> 2. 配置log4j.xml或者log4j.properties,放入類路徑根目錄; 3. 使用commons-logging得LogFactory獲取日誌實例 log4j提供了LogManager.getLogger(String) 方法創建日誌實例,但是為了日誌實現的解耦,必須使用commons-logging提供的抽象工廠LogFactory創建日誌實例: Log logger = LogFactory.getLog(String) 接下來就可以使用logger輸出日誌信息了。 log4j如何被commons-logging加載? 根據上述commons-logging解耦的原理,如果log4j是通過繼承抽象工廠org.apache.commons.logging.LogFactory來創建實現org.apache.commons.logging.Log接口的 日誌實例的,那麽就可以通過3種方法讓commons-logging加載log4j,分別是: 1. 從vm系統屬性org.apache.commons.logging.LogFactory 2. 使用SPI服務發現機制,發現org.apache.commons.logging.LogFactory的實現 3. 查找classpath根目錄commons-logging.properties的org.apache.commons.logging.LogFactory屬性是否指定factory實現 但是,查看log4j的jar包,發現在META-INF下面並沒有名為services得文件夾,更別說org.apache.commons.logging.LogFactory文件了: 而且,Logger類也沒有實現commons-logging提供的Log接口: log4j中也沒有找到繼承org.apache.commons.logging.LogFactory的類。 那麽,log4j是如何被commons-logging加載的呢? 按照上述LogFactory尋找factory實現的流程,LogFactory在找不到org.apache.commons.logging.LogFactory實現時,會使用默認實現org.apache.commons.logging.impl.LogFactoryImpl。 通過分析LogFactoryImpl的getInstance()方法,其調用以下方法獲得logger實例: protected Log newInstance(String name) throws LogConfigurationException { Log instance; try { if (logConstructor == null) { instance = discoverLogImplementation(name); } else { Object params[] = { name }; instance = (Log) logConstructor.newInstance(params); } if (logMethod != null) { Object params[] = { this }; logMethod.invoke(instance, params); } return instance; } catch (LogConfigurationException lce) { // this type of exception means there was a problem in discovery // and we‘ve already output diagnostics about the issue, etc.; // just pass it on throw lce; } catch (InvocationTargetException e) { // A problem occurred invoking the Constructor or Method // previously discovered Throwable c = e.getTargetException(); throw new LogConfigurationException(c == null ? e : c); } catch (Throwable t) { handleThrowable(t); // may re-throw t // A problem occurred invoking the Constructor or Method // previously discovered throw new LogConfigurationException(t); } } discoverLogImplementation方法如下: 1. 該方法首先查找用戶是否指定了日誌實現 2. classesToDiscover定義如下: private static final String[] classesToDiscover = { LOGGING_IMPL_LOG4J_LOGGER, // org.apache.commons.logging.impl.Log4JLogger "org.apache.commons.logging.impl.Jdk14Logger", "org.apache.commons.logging.impl.Jdk13LumberjackLogger", "org.apache.commons.logging.impl.SimpleLog" }; 用戶沒有指定日誌實現的情況下,按照classesToDiscover數組元素的順序,依次創建對應Log實例,直到返回成功創建的Log實例 private Log discoverLogImplementation(String logCategory) throws LogConfigurationException { if (isDiagnosticsEnabled()) { logDiagnostic("Discovering a Log implementation..."); } initConfiguration(); Log result = null; // See if the user specified the Log implementation to use // 查找用戶是否指定了日誌實現? String specifiedLogClassName = findUserSpecifiedLogClassName(); if (specifiedLogClassName != null) { if (isDiagnosticsEnabled()) { logDiagnostic("Attempting to load user-specified log class ‘" + specifiedLogClassName + "‘..."); } result = createLogFromClass(specifiedLogClassName, logCategory, true); if (result == null) { StringBuffer messageBuffer = new StringBuffer("User-specified log class ‘"); messageBuffer.append(specifiedLogClassName); messageBuffer.append("‘ cannot be found or is not useable."); // Mistyping or misspelling names is a common fault. // Construct a good error message, if we can informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LOG4J_LOGGER); informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_JDK14_LOGGER); informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LUMBERJACK_LOGGER); informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_SIMPLE_LOGGER); throw new LogConfigurationException(messageBuffer.toString()); } return result; } // No user specified log; try to discover what‘s on the classpath // // Note that we deliberately loop here over classesToDiscover and // expect method createLogFromClass to loop over the possible source // classloaders. The effect is: // for each discoverable log adapter // for each possible classloader // see if it works // // It appears reasonable at first glance to do the opposite: // for each possible classloader // for each discoverable log adapter // see if it works // // The latter certainly has advantages for user-installable logging // libraries such as log4j; in a webapp for example this code should // first check whether the user has provided any of the possible // logging libraries before looking in the parent classloader. // Unfortunately, however, Jdk14Logger will always work in jvm>=1.4, // and SimpleLog will always work in any JVM. So the loop would never // ever look for logging libraries in the parent classpath. Yet many // users would expect that putting log4j there would cause it to be // detected (and this is the historical JCL behaviour). So we go with // the first approach. A user that has bundled a specific logging lib // in a webapp should use a commons-logging.properties file or a // service file in META-INF to force use of that logging lib anyway, // rather than relying on discovery. if (isDiagnosticsEnabled()) { logDiagnostic( "No user-specified Log implementation; performing discovery" + " using the standard supported logging implementations..."); } // 用戶沒有指定日誌實現的情況下,按照classesToDiscover數組元素的順序,依次創建對應Log實例,直到返回成功創建的Log實例 for(int i=0; i<classesToDiscover.length && result == null; ++i) { result = createLogFromClass(classesToDiscover[i], logCategory, true); } if (result == null) { throw new LogConfigurationException ("No suitable Log implementation"); } return result; } findUserSpecifiedLogClassName方法如下: 1. 該方法首先試圖獲取commons-logging.properties 中的org.apache.commons.logging.Log指定的日誌實現 2. 試圖獲取commons-logging.properties 中的org.apache.commons.logging.log指定的日誌實現 3. 試圖獲取vm系統屬性org.apache.commons.logging.Log指定的日誌實現 4. 試圖獲取vm系統屬性org.apache.commons.logging.log指定的日誌實現 5. 都沒有返回null private String findUserSpecifiedLogClassName() { if (isDiagnosticsEnabled()) { logDiagnostic("Trying to get log class from attribute ‘" + LOG_PROPERTY + "‘"); } // 試圖獲取commons-logging.properties 中的org.apache.commons.logging.Log指定的日誌實現 String specifiedClass = (String) getAttribute(LOG_PROPERTY); if (specifiedClass == null) { // @deprecated if (isDiagnosticsEnabled()) { logDiagnostic("Trying to get log class from attribute ‘" + LOG_PROPERTY_OLD + "‘"); } // 試圖獲取commons-logging.properties 中的org.apache.commons.logging.log指定的日誌實現 specifiedClass = (String) getAttribute(LOG_PROPERTY_OLD); } if (specifiedClass == null) { if (isDiagnosticsEnabled()) { logDiagnostic("Trying to get log class from system property ‘" + LOG_PROPERTY + "‘"); } try { // 試圖獲取vm系統屬性org.apache.commons.logging.Log指定的日誌實現 specifiedClass = getSystemProperty(LOG_PROPERTY, null); } catch (SecurityException e) { if (isDiagnosticsEnabled()) { logDiagnostic("No access allowed to system property ‘" + LOG_PROPERTY + "‘ - " + e.getMessage()); } } } if (specifiedClass == null) { // @deprecated if (isDiagnosticsEnabled()) { logDiagnostic("Trying to get log class from system property ‘" + LOG_PROPERTY_OLD + "‘"); } try { // 試圖獲取vm系統屬性org.apache.commons.logging.log指定的日誌實現 specifiedClass = getSystemProperty(LOG_PROPERTY_OLD, null); } catch (SecurityException e) { if (isDiagnosticsEnabled()) { logDiagnostic("No access allowed to system property ‘" + LOG_PROPERTY_OLD + "‘ - " + e.getMessage()); } } } // Remove any whitespace; it‘s never valid in a classname so its // presence just means a user mistake. As we know what they meant, // we may as well strip the spaces. if (specifiedClass != null) { specifiedClass = specifiedClass.trim(); } return specifiedClass; } 如此一來,如果像上述一樣沒有指定任何org.apache.commons.logging.Log的實現,那麽commons-logging首先使用的是 org.apache.commons.logging.impl.Log4JLogger作為Log實現,可以看到Log4jLogger是實現了Log接口的: public class Log4JLogger implements Log, Serializable; Log4jLogger則是通過組合log4j提供的org.apache.log4j.Logger,來提供trace到fatal功能。 在log4j的jar包沒有導入到classpath之前,這個類是無法通過編譯的。 到現在應該知道了commons-logging是如何與log4j實現解耦的了,不是通過commons-logging提供的抽象工廠,而是通過 org.apache.commons.logging.impl.LogFactoryImpl加載具體的日誌實現。不得不說,在我看來這種設計極其不好,完全可以用抽象工廠提供的日誌實現加載!
五、log4j2+commons-logging解耦 log4j2是Apache推出的log4j的升級版本,在性能等各方面都做了提升。 要想使用commons-logging解耦log4j2,可按照下列步驟: 1. 導入log4j2, commons-logging, log4j-jcl依賴 其中,log4j-jcl是用來作為log4j和commons-logging的橋接的 <!--log4j2--> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.7</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.7</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-jcl</artifactId> <version>2.7</version> </dependency> 2. 配置好log4j2依賴的配置 log4j2支持了更多的配置方式,包括xml和json格式;若不指定配置文件位置,將其放置在classpath根路徑 3. 創建日誌實例使用 log4j2也提供了自己的日誌實例創建的方法: logger = LogManager.getLogger(Log4jTwoTest.class); 為了實現解耦,需要使用commons-logging提供的抽象共產LogFactory.getLog(String)方法獲取日誌實例即可; 解耦原理: log4j-jcl提供了解耦的實現,觀察其jar包: 該文件內容為:org.apache.logging.log4j.jcl.LogFactoryImpl 根據commons-logging抽象日誌工廠的第二步驟spi服務發現規則,抽象工廠使用org.apache.logging.log4j.jcl.LogFactoryImpl作為實際產生日誌實現的工廠。 分析LogFactoryImpl的代碼: private final LoggerAdapter<Log> adapter = new LogAdapter(); private final ConcurrentMap<String, Object> attributes = new ConcurrentHashMap<>(); @Override public Log getInstance(final String name) throws LogConfigurationException { return adapter.getLogger(name); } Log實例是由LogAdapter類的getLogger(String)方法創建的,其繼承了抽象適配器AbstractLoggerAdapter<Log>的getLogger方法: public L getLogger(String name) { LoggerContext context = this.getContext(); ConcurrentMap loggers = this.getLoggersInContext(context); Object logger = loggers.get(name); if(logger != null) { return logger; } else { loggers.putIfAbsent(name, this.newLogger(name, context)); return loggers.get(name); } } 最終調用LogAdapter的newLogger()方法產生日誌實例: @Override protected Log newLogger(final String name, final LoggerContext context) { return new Log4jLog(context.getLogger(name)); } Log4jLog組合了log4j2中的ExtendedLogger提供日誌打印功能。 可以看出,log4j2和commons-logging的解耦,完全使用了commons-logging提供的spi服務發現機制。 參考: SimpleLog類介紹: http://commons.apache.org/proper/commons-logging/apidocs/org/apache/commons/logging/impl/SimpleLog.html commons.logging1.1.1源代碼研究(2)-- 基本使用及配置文件 http://blog.csdn.net/gtuu0123/article/details/4509884 使用commons.logging中的SimpleLog顯示調試和日誌信息 http://blog.csdn.net/gxmark/article/details/7338253 Apache Commons Logging 是如何決定使用哪個日誌實現類的 http://blog.csdn.net/sunnydogzhou/article/details/5642319
二、commons-logging簡單日誌實現:
org.apache.commons.logging.simplelog.defaultlog
org.apache.commons.logging.simplelog.log.xxxxx
- Logging detail level for a SimpleLog instance named "xxxxx". Must be one of ("trace", "debug", "info", "warn", "error", or "fatal"). If not specified, the default logging detail level is used.org.apache.commons.logging.simplelog.showlogname
- Set totrue
if you want the Log instance name to be included in output messages. Defaults tofalse
.org.apache.commons.logging.simplelog.showShortLogname
- Set totrue
if you want the last component of the name to be included in output messages. Defaults totrue
.org.apache.commons.logging.simplelog.showdatetime
- Set totrue
if you want the current date and time to be included in output messages. Default isfalse
.org.apache.commons.logging.simplelog.dateTimeFormat
- The date and time format to be used in the output messages. The pattern describing the date and time format is the same that is used injava.text.SimpleDateFormat
. If the format is not specified or is invalid, the default format is used. The default format isyyyy/MM/dd HH:mm:ss:SSS zzz
.
三、commons-logging解耦原理 commons-logging最核心有用的功能是解耦,它的SimpleLog實現性能比不上其他實現,如log4j等。 首先,日誌實例是通過LogFactory的getLog(String)方法創建的: LogFatory是一個抽象類,它負責加載具體的日誌實現,分析其Factory getFactory()方法: public static org.apache.commons.logging.LogFactory getFactory() throws LogConfigurationException { // Identify the class loader we will be using ClassLoader contextClassLoader = getContextClassLoaderInternal(); if (contextClassLoader == null) { // This is an odd enough situation to report about. This // output will be a nuisance on JDK1.1, as the system // classloader is null in that environment. if (isDiagnosticsEnabled()) { logDiagnostic("Context classloader is null."); } } // Return any previously registered factory for this class loader org.apache.commons.logging.LogFactory factory = getCachedFactory(contextClassLoader); if (factory != null) { return factory; } if (isDiagnosticsEnabled()) { logDiagnostic( "[LOOKUP] LogFactory implementation requested for the first time for context classloader " + objectId(contextClassLoader)); logHierarchy("[LOOKUP] ", contextClassLoader); } // Load properties file. // // If the properties file exists, then its contents are used as // "attributes" on the LogFactory implementation class. One particular // property may also control which LogFactory concrete subclass is // used, but only if other discovery mechanisms fail.. // // As the properties file (if it exists) will be used one way or // another in the end we may as well look for it first. // classpath根目錄下尋找commons-logging.properties Properties props = getConfigurationFile(contextClassLoader, FACTORY_PROPERTIES); // Determine whether we will be using the thread context class loader to // load logging classes or not by checking the loaded properties file (if any). // classpath根目錄下commons-logging.properties是否配置use_tccl ClassLoader baseClassLoader = contextClassLoader; if (props != null) { String useTCCLStr = props.getProperty(TCCL_KEY); if (useTCCLStr != null) { // The Boolean.valueOf(useTCCLStr).booleanValue() formulation // is required for Java 1.2 compatibility. if (Boolean.valueOf(useTCCLStr).booleanValue() == false) { // Don‘t use current context classloader when locating any // LogFactory or Log classes, just use the class that loaded // this abstract class. When this class is deployed in a shared // classpath of a container, it means webapps cannot deploy their // own logging implementations. It also means that it is up to the // implementation whether to load library-specific config files // from the TCCL or not. baseClassLoader = thisClassLoader; } } } // 這裏真正開始決定使用哪個factory // 首先,嘗試查找vm系統屬性org.apache.commons.logging.LogFactory,其是否指定factory // Determine which concrete LogFactory subclass to use. // First, try a global system property if (isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] Looking for system property [" + FACTORY_PROPERTY + "] to define the LogFactory subclass to use..."); } try { String factoryClass = getSystemProperty(FACTORY_PROPERTY, null); if (factoryClass != null) { if (isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] Creating an instance of LogFactory class ‘" + factoryClass + "‘ as specified by system property " + FACTORY_PROPERTY); } factory = newFactory(factoryClass, baseClassLoader, contextClassLoader); } else { if (isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] No system property [" + FACTORY_PROPERTY + "] defined."); } } } catch (SecurityException e) { if (isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] A security exception occurred while trying to create an" + " instance of the custom factory class" + ": [" + trim(e.getMessage()) + "]. Trying alternative implementations..."); } // ignore } catch (RuntimeException e) { // This is not consistent with the behaviour when a bad LogFactory class is // specified in a services file. // // One possible exception that can occur here is a ClassCastException when // the specified class wasn‘t castable to this LogFactory type. if (isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] An exception occurred while trying to create an" + " instance of the custom factory class" + ": [" + trim(e.getMessage()) + "] as specified by a system property."); } throw e; } // 第二,嘗試使用java spi服務發現機制,載META-INF/services下尋找org.apache.commons.logging.LogFactory實現 // Second, try to find a service by using the JDK1.3 class // discovery mechanism, which involves putting a file with the name // of an interface class in the META-INF/services directory, where the // contents of the file is a single line specifying a concrete class // that implements the desired interface. if (factory == null) { if (isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] Looking for a resource file of name [" + SERVICE_ID + "] to define the LogFactory subclass to use..."); } try { // META-INF/services/org.apache.commons.logging.LogFactory, SERVICE_ID final 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)) { if (isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] Creating an instance of LogFactory class " + factoryClassName + " as specified by file ‘" + SERVICE_ID + "‘ which was present in the path of the context classloader."); } factory = newFactory(factoryClassName, baseClassLoader, contextClassLoader); } } else { // is == null if (isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] No resource file with name ‘" + SERVICE_ID + "‘ found."); } } } catch (Exception ex) { // note: if the specified LogFactory class wasn‘t compatible with LogFactory // for some reason, a ClassCastException will be caught here, and attempts will // continue to find a compatible class. if (isDiagnosticsEnabled()) { logDiagnostic( "[LOOKUP] A security exception occurred while trying to create an" + " instance of the custom factory class" + ": [" + trim(ex.getMessage()) + "]. Trying alternative implementations..."); } // ignore } } // 第三,嘗試從classpath根目錄下的commons-logging.properties中查找org.apache.commons.logging.LogFactory屬性指定的factory // Third try looking into the properties file read earlier (if found) if (factory == null) { if (props != null) { if (isDiagnosticsEnabled()) { logDiagnostic( "[LOOKUP] Looking in properties file for entry with key ‘" + FACTORY_PROPERTY + "‘ to define the LogFactory subclass to use..."); } String factoryClass = props.getProperty(FACTORY_PROPERTY); if (factoryClass != null) { if (isDiagnosticsEnabled()) { logDiagnostic( "[LOOKUP] Properties file specifies LogFactory subclass ‘" + factoryClass + "‘"); } factory = newFactory(factoryClass, baseClassLoader, contextClassLoader); // TODO: think about whether we need to handle exceptions from newFactory } else { if (isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] Properties file has no entry specifying LogFactory subclass."); } } } else { if (isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] No properties file available to determine" + " LogFactory subclass from.."); } } } // 最後,使用後備factory實現,org.apache.commons.logging.impl.LogFactoryImpl // Fourth, try the fallback implementation class if (factory == null) { if (isDiagnosticsEnabled()) { logDiagnostic( "[LOOKUP] Loading the default LogFactory implementation ‘" + FACTORY_DEFAULT + "‘ via the same classloader that loaded this LogFactory" + " class (ie not looking in the context classloader)."); } // Note: unlike the above code which can try to load custom LogFactory // implementations via the TCCL, we don‘t try to load the default LogFactory // implementation via the context classloader because: // * that can cause problems (see comments in newFactory method) // * no-one should be customising the code of the default class // Yes, we do give up the ability for the child to ship a newer // version of the LogFactoryImpl class and have it used dynamically // by an old LogFactory class in the parent, but that isn‘t // necessarily a good idea anyway. factory = newFactory(FACTORY_DEFAULT, thisClassLoader, contextClassLoader); } if (factory != null) { /** * Always cache using context class loader. */ 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; } 可以看出,抽象類LogFactory加載具體實現的步驟如下: 1. 從vm系統屬性org.apache.commons.logging.LogFactory 2. 使用SPI服務發現機制,發現org.apache.commons.logging.LogFactory的實現 3. 查找classpath根目錄commons-logging.properties的org.apache.commons.logging.LogFactory屬性是否指定factory實現 4. 使用默認factory實現,org.apache.commons.logging.impl.LogFactoryImpl LogFactory的getLog()方法返回類型是org.apache.commons.logging.Log接口,提供了從trace到fatal方法。可以確定,如果日誌實現提供者只要實現該接口,並且使用繼承自org.apache.commons.logging.LogFactory的子類創建Log,必然可以構建一個松耦合的日誌系統。
四、log4j+commons-logging解耦 使用commons-logging解耦log4j,可按照下列步驟: 1. 將log4j和commons-logging依賴放入classpath: <!--commons-logging--> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> <!-- log4j --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> 2. 配置log4j.xml或者log4j.properties,放入類路徑根目錄; 3. 使用commons-logging得LogFactory獲取日誌實例 log4j提供了LogManager.getLogger(String) 方法創建日誌實例,但是為了日誌實現的解耦,必須使用commons-logging提供的抽象工廠LogFactory創建日誌實例: Log logger = LogFactory.getLog(String) 接下來就可以使用logger輸出日誌信息了。 log4j如何被commons-logging加載? 根據上述commons-logging解耦的原理,如果log4j是通過繼承抽象工廠org.apache.commons.logging.LogFactory來創建實現org.apache.commons.logging.Log接口的 日誌實例的,那麽就可以通過3種方法讓commons-logging加載log4j,分別是: 1. 從vm系統屬性org.apache.commons.logging.LogFactory 2. 使用SPI服務發現機制,發現org.apache.commons.logging.LogFactory的實現 3. 查找classpath根目錄commons-logging.properties的org.apache.commons.logging.LogFactory屬性是否指定factory實現 但是,查看log4j的jar包,發現在META-INF下面並沒有名為services得文件夾,更別說org.apache.commons.logging.LogFactory文件了: 而且,Logger類也沒有實現commons-logging提供的Log接口: log4j中也沒有找到繼承org.apache.commons.logging.LogFactory的類。 那麽,log4j是如何被commons-logging加載的呢? 按照上述LogFactory尋找factory實現的流程,LogFactory在找不到org.apache.commons.logging.LogFactory實現時,會使用默認實現org.apache.commons.logging.impl.LogFactoryImpl。 通過分析LogFactoryImpl的getInstance()方法,其調用以下方法獲得logger實例: protected Log newInstance(String name) throws LogConfigurationException { Log instance; try { if (logConstructor == null) { instance = discoverLogImplementation(name); } else { Object params[] = { name }; instance = (Log) logConstructor.newInstance(params); } if (logMethod != null) { Object params[] = { this }; logMethod.invoke(instance, params); } return instance; } catch (LogConfigurationException lce) { // this type of exception means there was a problem in discovery // and we‘ve already output diagnostics about the issue, etc.; // just pass it on throw lce; } catch (InvocationTargetException e) { // A problem occurred invoking the Constructor or Method // previously discovered Throwable c = e.getTargetException(); throw new LogConfigurationException(c == null ? e : c); } catch (Throwable t) { handleThrowable(t); // may re-throw t // A problem occurred invoking the Constructor or Method // previously discovered throw new LogConfigurationException(t); } } discoverLogImplementation方法如下: 1. 該方法首先查找用戶是否指定了日誌實現 2. classesToDiscover定義如下: private static final String[] classesToDiscover = { LOGGING_IMPL_LOG4J_LOGGER, // org.apache.commons.logging.impl.Log4JLogger "org.apache.commons.logging.impl.Jdk14Logger", "org.apache.commons.logging.impl.Jdk13LumberjackLogger", "org.apache.commons.logging.impl.SimpleLog" }; 用戶沒有指定日誌實現的情況下,按照classesToDiscover數組元素的順序,依次創建對應Log實例,直到返回成功創建的Log實例 private Log discoverLogImplementation(String logCategory) throws LogConfigurationException { if (isDiagnosticsEnabled()) { logDiagnostic("Discovering a Log implementation..."); } initConfiguration(); Log result = null; // See if the user specified the Log implementation to use // 查找用戶是否指定了日誌實現? String specifiedLogClassName = findUserSpecifiedLogClassName(); if (specifiedLogClassName != null) { if (isDiagnosticsEnabled()) { logDiagnostic("Attempting to load user-specified log class ‘" + specifiedLogClassName + "‘..."); } result = createLogFromClass(specifiedLogClassName, logCategory, true); if (result == null) { StringBuffer messageBuffer = new StringBuffer("User-specified log class ‘"); messageBuffer.append(specifiedLogClassName); messageBuffer.append("‘ cannot be found or is not useable."); // Mistyping or misspelling names is a common fault. // Construct a good error message, if we can informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LOG4J_LOGGER); informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_JDK14_LOGGER); informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LUMBERJACK_LOGGER); informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_SIMPLE_LOGGER); throw new LogConfigurationException(messageBuffer.toString()); } return result; } // No user specified log; try to discover what‘s on the classpath // // Note that we deliberately loop here over classesToDiscover and // expect method createLogFromClass to loop over the possible source // classloaders. The effect is: // for each discoverable log adapter // for each possible classloader // see if it works // // It appears reasonable at first glance to do the opposite: // for each possible classloader // for each discoverable log adapter // see if it works // // The latter certainly has advantages for user-installable logging // libraries such as log4j; in a webapp for example this code should // first check whether the user has provided any of the possible // logging libraries before looking in the parent classloader. // Unfortunately, however, Jdk14Logger will always work in jvm>=1.4, // and SimpleLog will always work in any JVM. So the loop would never // ever look for logging libraries in the parent classpath. Yet many // users would expect that putting log4j there would cause it to be // detected (and this is the historical JCL behaviour). So we go with // the first approach. A user that has bundled a specific logging lib // in a webapp should use a commons-logging.properties file or a // service file in META-INF to force use of that logging lib anyway, // rather than relying on discovery. if (isDiagnosticsEnabled()) { logDiagnostic( "No user-specified Log implementation; performing discovery" + " using the standard supported logging implementations..."); } // 用戶沒有指定日誌實現的情況下,按照classesToDiscover數組元素的順序,依次創建對應Log實例,直到返回成功創建的Log實例 for(int i=0; i<classesToDiscover.length && result == null; ++i) { result = createLogFromClass(classesToDiscover[i], logCategory, true); } if (result == null) { throw new LogConfigurationException ("No suitable Log implementation"); } return result; } findUserSpecifiedLogClassName方法如下: 1. 該方法首先試圖獲取commons-logging.properties 中的org.apache.commons.logging.Log指定的日誌實現 2. 試圖獲取commons-logging.properties 中的org.apache.commons.logging.log指定的日誌實現 3. 試圖獲取vm系統屬性org.apache.commons.logging.Log指定的日誌實現 4. 試圖獲取vm系統屬性org.apache.commons.logging.log指定的日誌實現 5. 都沒有返回null private String findUserSpecifiedLogClassName() { if (isDiagnosticsEnabled()) { logDiagnostic("Trying to get log class from attribute ‘" + LOG_PROPERTY + "‘"); } // 試圖獲取commons-logging.properties 中的org.apache.commons.logging.Log指定的日誌實現 String specifiedClass = (String) getAttribute(LOG_PROPERTY); if (specifiedClass == null) { // @deprecated if (isDiagnosticsEnabled()) { logDiagnostic("Trying to get log class from attribute ‘" + LOG_PROPERTY_OLD + "‘"); } // 試圖獲取commons-logging.properties 中的org.apache.commons.logging.log指定的日誌實現 specifiedClass = (String) getAttribute(LOG_PROPERTY_OLD); } if (specifiedClass == null) { if (isDiagnosticsEnabled()) { logDiagnostic("Trying to get log class from system property ‘" + LOG_PROPERTY + "‘"); } try { // 試圖獲取vm系統屬性org.apache.commons.logging.Log指定的日誌實現 specifiedClass = getSystemProperty(LOG_PROPERTY, null); } catch (SecurityException e) { if (isDiagnosticsEnabled()) { logDiagnostic("No access allowed to system property ‘" + LOG_PROPERTY + "‘ - " + e.getMessage()); } } } if (specifiedClass == null) { // @deprecated if (isDiagnosticsEnabled()) { logDiagnostic("Trying to get log class from system property ‘" + LOG_PROPERTY_OLD + "‘"); } try { // 試圖獲取vm系統屬性org.apache.commons.logging.log指定的日誌實現 specifiedClass = getSystemProperty(LOG_PROPERTY_OLD, null); } catch (SecurityException e) { if (isDiagnosticsEnabled()) { logDiagnostic("No access allowed to system property ‘" + LOG_PROPERTY_OLD + "‘ - " + e.getMessage()); } } } // Remove any whitespace; it‘s never valid in a classname so its // presence just means a user mistake. As we know what they meant, // we may as well strip the spaces. if (specifiedClass != null) { specifiedClass = specifiedClass.trim(); } return specifiedClass; } 如此一來,如果像上述一樣沒有指定任何org.apache.commons.logging.Log的實現,那麽commons-logging首先使用的是 org.apache.commons.logging.impl.Log4JLogger作為Log實現,可以看到Log4jLogger是實現了Log接口的: public class Log4JLogger implements Log, Serializable; Log4jLogger則是通過組合log4j提供的org.apache.log4j.Logger,來提供trace到fatal功能。 在log4j的jar包沒有導入到classpath之前,這個類是無法通過編譯的。 到現在應該知道了commons-logging是如何與log4j實現解耦的了,不是通過commons-logging提供的抽象工廠,而是通過 org.apache.commons.logging.impl.LogFactoryImpl加載具體的日誌實現。不得不說,在我看來這種設計極其不好,完全可以用抽象工廠提供的日誌實現加載!
五、log4j2+commons-logging解耦 log4j2是Apache推出的log4j的升級版本,在性能等各方面都做了提升。 要想使用commons-logging解耦log4j2,可按照下列步驟: 1. 導入log4j2, commons-logging, log4j-jcl依賴 其中,log4j-jcl是用來作為log4j和commons-logging的橋接的 <!--log4j2--> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.7</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.7</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-jcl</artifactId> <version>2.7</version> </dependency> 2. 配置好log4j2依賴的配置 log4j2支持了更多的配置方式,包括xml和json格式;若不指定配置文件位置,將其放置在classpath根路徑 3. 創建日誌實例使用 log4j2也提供了自己的日誌實例創建的方法: logger = LogManager.getLogger(Log4jTwoTest.class); 為了實現解耦,需要使用commons-logging提供的抽象共產LogFactory.getLog(String)方法獲取日誌實例即可; 解耦原理: log4j-jcl提供了解耦的實現,觀察其jar包: 該文件內容為:org.apache.logging.log4j.jcl.LogFactoryImpl 根據commons-logging抽象日誌工廠的第二步驟spi服務發現規則,抽象工廠使用org.apache.logging.log4j.jcl.LogFactoryImpl作為實際產生日誌實現的工廠。 分析LogFactoryImpl的代碼: private final LoggerAdapter<Log> adapter = new LogAdapter(); private final ConcurrentMap<String, Object> attributes = new ConcurrentHashMap<>(); @Override public Log getInstance(final String name) throws LogConfigurationException { return adapter.getLogger(name); } Log實例是由LogAdapter類的getLogger(String)方法創建的,其繼承了抽象適配器AbstractLoggerAdapter<Log>的getLogger方法: public L getLogger(String name) { LoggerContext context = this.getContext(); ConcurrentMap loggers = this.getLoggersInContext(context); Object logger = loggers.get(name); if(logger != null) { return logger; } else { loggers.putIfAbsent(name, this.newLogger(name, context)); return loggers.get(name); } } 最終調用LogAdapter的newLogger()方法產生日誌實例: @Override protected Log newLogger(final String name, final LoggerContext context) { return new Log4jLog(context.getLogger(name)); } Log4jLog組合了log4j2中的ExtendedLogger提供日誌打印功能。 可以看出,log4j2和commons-logging的解耦,完全使用了commons-logging提供的spi服務發現機制。 參考: SimpleLog類介紹: http://commons.apache.org/proper/commons-logging/apidocs/org/apache/commons/logging/impl/SimpleLog.html commons.logging1.1.1源代碼研究(2)-- 基本使用及配置文件 http://blog.csdn.net/gtuu0123/article/details/4509884 使用commons.logging中的SimpleLog顯示調試和日誌信息 http://blog.csdn.net/gxmark/article/details/7338253 Apache Commons Logging 是如何決定使用哪個日誌實現類的 http://blog.csdn.net/sunnydogzhou/article/details/5642319
commons-logging日誌實現解耦