Apache Commons-Logging 源码分析
Commons-Logging包结构:
org.apache.commons.logging.Log
一个接口,定义了五个输出级别(从低到高依次严重)
trace (the least serious)
debug
info
warn
error
fatal (the most serious)
PS:以下XXX代表某一个日志级别
每个级别都有一个isXXXEnabled 方法 可以判断是否启用了这个级别的输出
同时还有两个输出方法:
XXX(Object message)
XXX(Object message, Throwable t)
org.apache.commons.logging.LogSource
已经被弃用的类,提供了获得log实例对象的方法,LogFactory替代了这个类的作用。
不过从这个类中还是可以看到是如何获得log实现类的。
首先判断 org.apache.log4j.Logger 是否在classpath中,也就是说默认采用log4j作为实现。
其次判断
java.util.logging.Logger 与 org.apache.commons.logging.impl.Jdk14Logger是否存在,将使用jdk1.4提供的log作为底层实现。
否则将使用org.apache.commons.logging.impl.NoOpLog作为实现,将不提供log输出。
org.apache.commons.logging.LogFactory
抽象类,log工厂类,产生log的实例对象。
最常用的方法是getLog(Class
clazz)
可以看到方法内代码只有一行: return (getFactory().getInstance(clazz));
getInstance是个抽象方法,后面讲解。
这里解析下getFactory方法
[*]获得当前线程的classloader,命名为contextClassLoader
[*]根据contextClassLoader在缓存中获得logFactory,这里的缓存使用的是org.apache.commons.logging.impl.WeakHashTable,key是classloader,value是logFactory,如果在缓存中有logFactory则直接返回,没有进入下面的流程
[*]读取配置文件commons-logging.properties
[*]如果读到了配置文件,判断其中use_tccl属性,然后设定baseClassLoader是使用contextClassLoader还是使用加载本Class文件的那个classloader(名字为thisClassLoader)
[*]下面生成logFactory,这里会有使用四种方式,依次来尝试生成。
[*]将生成的logFactory缓存起来
[*]返回logFactory
第5条种四种方式是:
[*]通过系统属性中寻找org.apache.commons.logging.LogFactory的value值,根据值为类名生成logFactory
[*]通过资源META-INF/services/org.apache.commons.logging.LogFactory,获得的值为类名生成logFactor
[*]通过刚才读取的配置文件commons-logging.properties,从中获取以org.apache.commons.logging.LogFactory为key的value值,根据值为类名生成logFactory
[*]如果上面都不成功的话,会使用默认的实现类org.apache.commons.logging.impl.LogFactoryImpl来生成logFactory
下面来看下默认实现类有什么内容,其中的getInstance是如果运作的。
org.apache.commons.logging.impl.LogFactoryImpl
主要查看getInstance方法
源码如下:
Log instance = (Log) instances.get(name);
if (instance == null) {
instance = newInstance(name);
instances.put(name, instance);
}
return (instance);
可以看到逻辑很简单,在一个缓存中查找是否有这个log,没有就生成一个,并放到缓存中,这里的name就是传入的className。
下面主要解析下newInstance方法:
首先会判断logConstructor是否为空,第一次必然为空,然后就调用discoverLogImplementation
在其方法中,会先查看是否有用户指定的实现类。如果没有则按照下面的顺序,在classpath中去寻找,找到就后进行实例化并返回该实例。
顺序如下:
org.apache.commons.logging.impl.Log4JLogger
org.apache.commons.logging.impl.Jdk14Logger
org.apache.commons.logging.impl.Jdk13LumberjackLogger
org.apache.commons.logging.impl.SimpleLog
生成log实例会调用方法:createLogFromClass
这里会先找到这个实现类的class文件
URL url;
String resourceName = logAdapterClassName.replace('.', '/') + ".class";
if (currentCL != null) {
url = currentCL.getResource(resourceName );
} else {
url = ClassLoader.getSystemResource(resourceName + ".class");
}
其实这里的1069行代码,ME觉得应该是个bug,明明resourceName已经加上了.class后缀,这里竟然要再加一遍,估计这个分支很少会跑进去,所以这个错误没有发现吧…
然后找个加载这个class文件,然后获取构造函数(带有一个String参数),接着实例化。
如果这里affectState(createLogFromClass最后一个参数)为true的话,会将刚才找到的构造函数,log类名等信息记录下来,方面后面使用。
最后返回log实例。
下面是关于log接口的几个实现类:
org.apache.commons.logging.impl.Log4JLogger
org.apache.commons.logging.impl.Jdk14Logger
org.apache.commons.logging.impl.Jdk13LumberjackLogger
org.apache.commons.logging.impl.SimpleLog
可以看到,前三个都是相当于适配器的功能,也就是说里面实际实现,是委托给其他log框架去完成的,例如log4j,jdk1.4自带的logging等等,最后一个才是该项目提供的log接口的一个实现类。
举个例子:
public void debug(Object message, Throwable
t) 这个方法
Log4JLogger中实现:
public void debug(Object message, Throwable t) {
getLogger().log(FQCN, Priority.DEBUG, message, t );
}
SimpleLog中实现:
public final void debug(Object message, Throwable t) {
if (isLevelEnabled(SimpleLog.LOG_LEVEL_DEBUG)) {
log(SimpleLog.LOG_LEVEL_DEBUG, message, t);
}
}
这里的log方法中,可以看到使用了java.io.StringWriter进行输出的。
总结下就是,commons-logging项目采用了一种动态搜索的机制,在实际运行时,动态的选择log 的实现方式。如果没有log4j,就使用jdk1.4的logging,如果再没有,就使用Lumberjack,它是为jdk1.2与1.3实现了Java日志 API,再没有,那就使用自身提供的一个log(SimpleLog)。
可以说commons-logging提供了一套简单的日志API给用户,但对于输出格式设定等问题,没有提供必要的API给用户,这点只能靠使用Log4j等更加强大的日志框架来完成,这也可能是为什么采用log4j作为最优先实现的原因吧。
项目中使用commons-logging的原因,可能是为了不与log4j死死的绑定在一起,使用commons-logging作为一个中间层,达到解耦的目的。
所以说commons-logging 的最大贡献就是为日志输出提供一个统一的接口,并且会在运行时,很方便的自动选择好日志实现系统。
顺便摘抄一段关于日志级别的信息:
确保日志信息在内容上和反应问题的严重程度上的恰当,是非常重要的。
[*]
fatal非常严重的错误,导致系统中止。期望这类信息能立即显示在状态控制台上。
[*]
error其它运行期错误或不是预期的条件。期望这类信息能立即显示在状态控制台上。
[*]
warn使用了不赞成使用的API、非常拙劣使用API, '几乎就是'错误, 其它运行时不合需要和不合预期的状态但还没必要称为 "错误"。期望这类信息能立即显示在状态控制台上。
[*]
info运行时产生的有意义的事件。期望这类信息能立即显示在状态控制台上。
[*]
debug系统流程中的细节信息。期望这类信息仅被写入log文件中。
[*]
trace更加细节的信息。期望这类信息仅被写入log文件中。
通常情况下,记录器的级别不应低于info。
也就是说,通常情况下debug的信息不应被写入log文件中。
页:
[1]