commons-logging是Apache commons类库中的一员。commons-logging自带了日志实现类,但是功能比较简单,更多的是将其作为门面,底层实现依赖其它框架。commons-logging能够选择使用Log4j还是JDK Logging,但是它并不依赖Log4j或JDK Logging的API。commons-logging会自动检测项目classpath中包含的支持的框架,从而自动选择实现框架。使用commons-logging能否灵活的选择使用哪些日志框架,而且不需要修改源代码。
项目地址
https://github.com/apache/commons-logging
使用示例
Log log = LogFactory.getLog(LoadTestCase.class);
log.info("test");
Log接口部分代码
/**
* Logs a message with info log level.
*
* @param message log this message
*/
void info(Object message);
/**
* Logs an error with info log level.
*
* @param message log this message
* @param t log this cause
*/
void info(Object message, Throwable t);
从接口定义可以看出,Log的info方法并不能像slf4j一样支持占位符,所以在使用时需要手动拼接字符串,该接口的其它方法一样不支持占位符。
源码解析
从第一行开始分析
public static Log getLog(Class clazz) throws LogConfigurationException {
return getFactory().getInstance(clazz);
}
LogFactory是个抽象类,其中有两个比较重要的抽象方法交由子类去实现
public abstract Log getInstance(Class clazz)
throws LogConfigurationException;
public abstract Log getInstance(String name)
throws LogConfigurationException;
可以看到getLog方法也调用了其中的抽象方法,我们先来看看getFactory()方法
public static 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
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.
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).
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;
}
}
}
// 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 {
// 1. 先从系统变量中看看是否定义了factory的实现类,由变量 org.apache.commons.logging.LogFactory定义
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;
}
// 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 {
// 2.看看文件META-INF/services/org.apache.commons.logging.LogFactory中是否定义了factory的实现类
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;
try {
factoryClassName = rd.readLine();
} finally {
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
}
}
// 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...");
}
// 3.从配置文件commons-logging.properties中查找key为org.apache.commons.logging.LogFactory的value实例化factory
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..");
}
}
}
// 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.
// 4. 使用默认的实现类org.apache.commons.logging.impl.LogFactoryImpl
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;
}
从上面的代码分析可以知道commons-logging是按照如下顺序实例化Factory的
- 系统变量 org.apache.commons.logging.LogFactory定义
- 文件META-INF/services/org.apache.commons.logging.LogFactory定义
- 配置文件commons-logging.properties中查找key为org.apache.commons.logging.LogFactory定义
- 默认实现类org.apache.commons.logging.impl.LogFactoryImpl
假设使用了默认实现类,继续分析
先来看看默认实现类中对重要的两个抽象方法的实现
public Log getInstance(Class clazz) throws LogConfigurationException {
return getInstance(clazz.getName());
}
public Log getInstance(String name) throws LogConfigurationException {
Log instance = (Log) instances.get(name);
if (instance == null) {
instance = newInstance(name);
instances.put(name, instance);
}
return instance;
}
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);
}
}
默认情况下logConstructor为null,因此重点在于discoverLogImplementation方法
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;
}
if (isDiagnosticsEnabled()) {
logDiagnostic(
"No user-specified Log implementation; performing discovery" +
" using the standard supported logging implementations...");
}
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方法中可以寻找用户自定义的Log实现类,此处不贴代码了,只说明寻找的顺序
- commons-logging.properties中org.apache.commons.logging.Log的定义
- commons-logging.properties中org.apache.commons.logging.log的定义(此为旧版本中的定义方式)
- 系统变量中org.apache.commons.logging.Log的定义
- 系统变量中org.apache.commons.logging.log的定义(此为旧版本中的定义方式)
以上为用户自定义Log实现类部分,如果找不到用户自定义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"
};
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;
很清晰的看到commons-logging在寻找Log默认实现类时的寻找顺序
- Log4JLogger
- Jdk14Logger
- Jdk13LumberjackLogger
- SimpleLog
这几个Log实现类都是commons-logging自带的,它们都是org.apache.commons.logging.Log接口的实现类,而且都提供了一个只有一个String类型参数的构造函数,createLogFromClass方法中通过反射调用实现类的构造函数从而获得一个Log实例。
以Log4jLogger为例来说明commons-logging是如何委托给org.apache.log4j.Logger的
其实很简单,Log4jLogger本身持有了一个org.apache.log4j.Logger实例,它将所有的动作直接扔给这个org.apache.log4j.Logger实例去完成了。
自带Log实现类
commons-logging提供了两个有实际实现的Log实现类,其它的实现类则是委托给了具体的实现框架。这两个实现类比较简单,我们只是粗略看看,实际项目中不太可能用到它们。
SimpleLog
这是一个很简单的日志输出实现类,它只能控制台输出,提供了以下可定制的属性
- org.apache.commons.logging.simplelog.showlogname(默认不输出)
是否输出Log名,就是传递给Simple构造函数的字符串
- org.apache.commons.logging.simplelog.showShortLogname(默认输出)
是否输出短的Log名,一般传递给构造函数的字符串为全类名,例如com.google.test.Student,那么logname就是com.google.test.Student,而shortLogname就是Student,二者选一,不能同时输出
- org.apache.commons.logging.simplelog.showdatetime
是否输出时间(默认不输出)
- org.apache.commons.logging.simplelog.dateTimeFormat
输出的时间格式(默认为yyyy/MM/dd HH:mm:ss:SSS zzz)
这些变量的寻找顺序为
- 环境变量
- commons-logging.properties配置文件
NoOpLog
这个实现类什么都不做,所有的方法均为空
总结
commons-logging其主要作用是门面担当,更多的是依赖其它日志框架实现日志输出,当然commons-logging自身也提供了简单的实现类,但是功能单一,实际项目中基本上不会用到。建议大家在实际项目中还是选用更通用,更易用的slf4j-api作为门面。commons-logging相比slf4j-api的优势在于:它不依赖桥接器就能与其他日志框架配合使用。