Java日志系统学习
作为一名Java开发者,日志是我们工作中经常接触到的一项技术。对于Web应用而言,日志的重要性不言而喻,是必不可少的一部分;日志提供了丰富的记录功能,例如程序运行时的错误信息,描述信息,状态信息和执行时间信息等。
在实际生产环境中,日志是查找问题的重要来源,良好的日志格式和记录可以帮助Developer快速定位到错误的根源,找到问题的原因;
尽管对于现在的应用程序来说,日志至关重要,但是在JDK最初的版本当中并不包含日志记录的API和实现,直到JDK1.4后才被加入;因此,开源社区在此期间提供了众多贡献,其中名声最大、运用最广泛的当log4j莫属,当然后续的logback、log4j2也在迅速的普及;下面,就让笔者来进行具体的介绍。
1 commons-logging
1.1 简介
Apache Commons Logging,又名JakartaCommons Logging (JCL),它是Apache提供的一个通用的日志接口,它的出现避免了和具体的日志方案直接耦合;在日常开发中,developer可以选择第三方日志组件进行搭配使用,例如log4j、logback等;
说的直白些,commons-logging提供了操作日志的接口,而具体实现交给log4j、logback这样的开源日志框架来完成;这样的方式,实现了程序的解耦,对于底层日志框架的改变,并不会影响到上层的业务代码。
1.2 commons-logging结构
Log:日志对象接口,封装了操作日志的方法,定义了日志操作的5个级别:trace < debug < info < warn < error
LogFactory:抽象类,日志工厂,获取日志类;
LogFactoryImpl:LogFactory的实现类,真正获取日志对象的地方;
Log4JLogger:对log4j的日志对象封装;
Jdk14Logger:对JDK1.4的日志对象封装;
Jdk13LumberjackLogger:对JDK1.3以及以前版本的日志对象封装;
SimpleLog:commons-logging自带日志对象;
1.3 使用
commons-logging的使用非常简单。首先,需要在pom.xml文件中添加依赖:
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.3</version>
</dependency>
声明测试代码:
public class commons_loggingDemo {
Log log= LogFactory.getLog(commons_loggingDemo.class);
@Test
public void test() throws IOException {
log.debug("Debug info.");
log.info("Info info");
log.warn("Warn info");
log.error("Error info");
log.fatal("Fatal info");
}
}
接下来,在classpath下定义配置文件:commons-logging.properties:
#指定日志对象:
org.apache.commons.logging.Log = org.apache.commons.logging.impl.Jdk14Logger
#指定日志工厂:
org.apache.commons.logging.LogFactory = org.apache.commons.logging.impl.LogFactoryImpl
在我们的项目中,如果只单纯的依赖了commons-logging,那么默认使用的日志对象就是Jdk14Logger,默认使用的日志工厂就是LogFactoryImpl
1.4 源码分析
public abstract class LogFactory {
public static final String HASHTABLE_IMPLEMENTATION_PROPERTY="org.apache.commons.logging.LogFactory.HashtableImpl";
private static final String WEAK_HASHTABLE_CLASSNAME = "org.apache.commons.logging.impl.WeakHashtable";
public static final String FACTORY_PROPERTIES = "commons-logging.properties";
public static final String FACTORY_PROPERTY = "org.apache.commons.logging.LogFactory";
public static final String FACTORY_DEFAULT = "org.apache.commons.logging.impl.LogFactoryImpl";
//LogFactory静态代码块:
static {
//获取LogFactory类加载器:AppClassLoader
thisClassLoader = getClassLoader(LogFactory.class);
String classLoaderName;
try {
ClassLoader classLoader = thisClassLoader;
if (thisClassLoader == null) {
classLoaderName = "BOOTLOADER";
} else {
//获取classLoader的名称:sun.misc.Launcher$AppClassLoader@150838093
classLoaderName = objectId(classLoader);
}
} catch (SecurityException e) {
classLoaderName = "UNKNOWN";
}
diagnosticPrefix = "[LogFactory from " + classLoaderName + "] ";
diagnosticsStream = initDiagnostics();
logClassLoaderEnvironment(LogFactory.class);
//创建存放日志的工厂缓存对象:实际为org.apache.commons.logging.impl.WeakHashtable
factories = createFactoryStore();
if (isDiagnosticsEnabled()) {
logDiagnostic("BOOTSTRAP COMPLETED");
}
}
//获取日志对象:
public static Log getLog(Class clazz) throws LogConfigurationException {
//得到LogFactoryImpl日志工厂后,实例化具体的日志对象:
return getFactory().getInstance(clazz);
}
//获取日志工厂
public static LogFactory getFactory() throws LogConfigurationException {
//获取当前线程的classCloader:
ClassLoader contextClassLoader = getContextClassLoaderInternal();
if (contextClassLoader == null) {
.....
}
//从缓存中获取LogFactory:此缓存就是刚才在静态代码块中创建的WeakHashtable
LogFactory factory = getCachedFactory(contextClassLoader);
//如果存在就返回:
if (factory != null) {
return factory;
}
if (isDiagnosticsEnabled()) {
......
}
//读取classpath下的commons-logging.properties文件:
Properties props = getConfigurationFile(contextClassLoader, FACTORY_PROPERTIES);
ClassLoader baseClassLoader = contextClassLoader;
if (props != null) {
//如果Properties对象不为空,从中获取 TCCL_KEY 的值:
String useTCCLStr = props.getProperty(TCCL_KEY);
if (useTCCLStr != null) {
if (Boolean.valueOf(useTCCLStr).booleanValue() == false) {
baseClassLoader = thisClassLoader;
}
}
}
.....
try {
/从系统属性中获取 FACTORY_PROPERTY 的值:
String factoryClass = getSystemProperty(FACTORY_PROPERTY, null);
if (factoryClass != null) {
//如果该值不为空,则实例化日志工厂对象:
factory = newFactory(factoryClass, baseClassLoader, contextClassLoader);
} else {
.....
}
} catch (SecurityException e) {
.....
}
if (factory == null) {
if (isDiagnosticsEnabled()) {
....
}
try {
//如果日志工厂对象还为null,则从 META-INF/services/org.apache.commons.logging.LogFactory 中获取:
final InputStream is = getResourceAsStream(contextClassLoader, SERVICE_ID);
if( is != null ) {
.....
}
} catch (Exception ex) {
......
}
}
if (factory == null) {
if (props != null) {
//如果此时日志工厂为null,并props有值,则获取 FACTORY_PROPERTY 为key的值:
String factoryClass = props.getProperty(FACTORY_PROPERTY);
if (factoryClass != null) {
if (isDiagnosticsEnabled()) {
logDiagnostic(
"[LOOKUP] Properties file specifies LogFactory subclass '" + factoryClass + "'");
}
//如果我们配置了,则实例化我们配置的日志工厂对象
factory = newFactory(factoryClass, baseClassLoader, contextClassLoader);
} else {
.....
}
} else {
......
}
}
if (factory == null) {
if (isDiagnosticsEnabled()) {
.....
}
//如果此时日志工厂依旧为null,则实例化默认工厂:org.apache.commons.logging.impl.LogFactoryImpl
factory = newFactory(FACTORY_DEFAULT, thisClassLoader, contextClassLoader);
}
if (factory != null) {
// 将日志工厂添加到缓存当中:
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;
}
//创建存放日志的工厂缓存对象:
private static final Hashtable createFactoryStore() {
Hashtable result = null;
String storeImplementationClass;
try {
//从系统属性中获取 HASHTABLE_IMPLEMENTATION_PROPERTY 为key的值:
storeImplementationClass = getSystemProperty(HASHTABLE_IMPLEMENTATION_PROPERTY, null);
} catch (SecurityException ex) {
storeImplementationClass = null;
}
//如果 storeImplementationClass 为null
if (storeImplementationClass == null) {
//将 WEAK_HASHTABLE_CLASSNAME 赋值给storeImplementationClass字符串
storeImplementationClass = WEAK_HASHTABLE_CLASSNAME;
}
try {
//反射实例化缓存对象:org.apache.commons.logging.impl.WeakHashtable
Class implementationClass = Class.forName(storeImplementationClass);
result = (Hashtable) implementationClass.newInstance();
} catch (Throwable t) {
.....
}
if (result == null) {
result = new Hashtable();
}
return result;
}
}
public class LogFactoryImpl extends LogFactory {
protected Hashtable instances = new Hashtable();
protected Constructor logConstructor = null;
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 {
//日志构造器对象为null:
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) {
....
}
}
//发现日志实现类:
private Log discoverLogImplementation(String logCategory) throws LogConfigurationException {
if (isDiagnosticsEnabled()) {
logDiagnostic("Discovering a Log implementation...");
}
initConfiguration();
Log result = null;
//从classpath中的commons-logging.properties文件中获取“org.apache.commons.logging.Log”的value:
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) {
.......
}
return result;
}
if (isDiagnosticsEnabled()) {
....
}
/*
*如果配置文件中不存在“org.apache.commons.logging.Log”的value:
*那么还有遍历classesToDiscover字符串数组,进行实例化:
*此数组中包括:log4j、Jdk14Logger、Jdk13LumberjackLogger、SimpleLog
*/
for(int i=0; i<classesToDiscover.length && result == null; ++i) {
result = createLogFromClass(classesToDiscover[i], logCategory, true);
}
//如果此时日志对象还未null,为报错;
if (result == null) {
throw new LogConfigurationException("No suitable Log implementation");
}
//返回日志对象:
return result;
}
}
以上就是commons-logging获取日志对象的全过程,具体文字总结如下:
获取当前线程的classLoader,根据classLoader从缓存中获取LogFactroy,使用的缓存是WeakHashTable对象;如果缓存中存在,则返回,没有则进入下面流程;
读取classpath下的commons-logging.properties文件,判断其中是否设置了use_tccl属性,如果不为空则判断,该属性的值是否为false,若为false,则将baseClassLoader替换为当前类的classLoader;
接着,继续获取LogFactory对象,此步骤分为4中方式:
(1)在系统属性中查找“org.apache.commons.logging.LogFactory”属性的值,根据值生成LogFactory对象;
(2)通过资源“META-INF/services/org.apache.commons.logging.LogFactory”文件,获取的值生成LogFactory对象;
(3)通过配置文件commons-logging.properties,获取以“org.apache.commons.logging.LogFactory”为key的值,根据值生成logFactory;
(4)如果以上均不成功,则创建系统默认的日志工厂:org.apache.commons.logging.impl.LogFactoryImpl
成功获取日志工厂后,根据类名获取日志对象;
主要逻辑在discoverLogImplementation方法中:
(1)检查commons-logging.properties文件中是否存在“org.apache.commons.logging.Log”属性,若存在则创建具体的日志对象;若不存在,进行下面逻辑;
(2)遍历classesToDiscover数组,该数组存有日志具体实现类的全限定类名:org.apache.commons.logging.impl.Log4JLogger、org.apache.commons.logging.impl.Jdk14Logger、org.apache.commons.logging.impl.Jdk13LumberjackLogger、org.apache.commons.logging.impl.SimpleLog;
(3)根据数组中存着的全限定类名,按照顺序依次加载Class文件,进行实例化操作,最后返回Log实例,默认为Jdk14Logger;
其中,获取日志工厂的过程,诟病最多。究其原因,主要是commons-logging在获取日志工厂的过程中使用了classLoader来寻找日志工厂实现,进而导致了其他组件,如若使用自己的classload,则不能获取具体的日志工厂对象,则导致启动失败,这样就是我们常说的--动态查找机制。