深入源码之Commons Logging

自从七月份去走川藏后,已经好几个月没有更新博客了。其实八月底从拉萨回来后一直在Spring的代码,也想写几篇关于Spring源码的解读,可惜Spring实在是太复杂了,花了我一个多月的时间,框架大体流程是有头绪了,但是具体实现和各个模块的具体细节还都不是很清楚,迟迟不敢动笔。其实原本我不想回到Logging这一块,我知道光看完Log4J的代码还不够,也感觉Log在系统中其实占据了蛮重要的位置(虽然很多人都没有意识到),不过一般Log框架使用简单,遇到问题也比较少,即使看完源码对实际工作也帮助不大。可惜我前段时间答应要在公司讲一个关于Logging相关的Session了,木有办法,只能重拾Logging的内容。也趁这个机会把Java的Logging相关框架做一个全面的了解,包括Commons Logging、SLF4J、JDK Logging、LogBack。首先从Commons Logging和SLF4J的比较开始。

先来随便扯点吧,貌似所有这些流行的Logging框架都和Log4J多少有点关系(不太确定Commons Logging有多大关系,不过至少也都是Apache下的项目吧)。JDK Logging据说当初是想用Log4J的,但是当时两家好像谈判谈崩了,然后JDK自己实现了一个,貌似结构和Log4J差不多,只是实现的比较烂,基本上也只能在做测试的时候用,而SLF4J和LogBack都是出自Log4J的创始人Ceki Gülcü之手。这家伙也算是闲的蛋疼,光整Logging这些框架貌似就花了不少时间吧。

言归正传,在Logging系统中,目前框架都是基于相同的设计,即从一个LogFactory中取得一个命名的Log(Logger)实例,然后使用这个Log(Logger)实例打印debug、info、warn、error等不同级别的日志。作为两个门面日志系统,Commons Logging和SLF4J也同样采用这样的设计。所谓门面日志系统,是指它们本身并不实现具体的日志打印逻辑,它们只是作为一个代理系统,接收应用程序的日志打印请求,然后根据当前环境和配置,选取一个具体的日志实现系统,将真正的打印逻辑交给具体的日志实现系统,从而实现应用程序日志系统的“可插拔”,即可以通过配置或更换jar包来方便的更换底层日志实现系统,而不需要改变任何代码。个人感觉SLF4J的实现更加灵活,并且它还提供了Maker和MDC的接口。这个将在接下的小节中具体介绍。

Commons Logging的设计比较简单,它定义了一个Log接口,所有它支持的日志系统都有相应的Log实现类,如Log4JLogger、Jdk14Logger、Jdk13LumberjackLogger、SimpleLog、NoOpLog、AvalonLogger、LogKitLogger等类,在LogFactory中定义了一定的规则,从而根据当前的环境和配置取得特定的Log子类实例。

Commons Logging中默认实现的LogFactory(LogFactoryImpl类)查找具体Log实现类的逻辑如下:

1.    查找在commons-logging.properties文件中是否定存在以org.apache.commons.logging.Log或org.apache.commons.logging.log(旧版本,不建议使用)为key定义的Log实现类,如果是,则使用该类。

2.    否则,查找在系统属性中(-D方式启动参数)是否存在以org.apache.commons.logging.Log或org.apache.commons.logging.log(旧版本,不建议使用)为key定义的Log实现类,如果是,则使用该类。

3.    否则,如果在classpath中存在Log4J的jar包,则使用Log4JLogger类。

4.    否则,如果当前使用的JDK版本或等于1.4,则使用Jdk14Logger类。

5.    否则,如果存在Lumberjack版本的Logging系统,则使用Jdk13LumberjackLogger类。

6.    否则,如果可以正常初始化Commons Logging自身实现的SimpleLog实例,则使用该类

7.    最后,以上步骤都失败,则抛出LogConfigurationException。

其实,Commons Logging还支持用户自定义的LogFactory实现类。对LogFactory类的查找逻辑为:

1.    查看系统属性中是否存在以org.apache.commons.logging.LogFactory为key的LogFactory实现类,若有,则使用该类实例化一个LogFactory实例。

2.    否则,尝试使用service provider的方式查找LogFactory实现类,即查看classpath或jar包中是否存在META-INF/services/org.apache.commons.logging.LogFactory文件,如果存在,则使用该文件内定义的LogFactory类实例化一个LogFactory实例。

3.    否则,查找commons-logging.properties文件是否存在,并且其中存在以org.apache.commons.logging.LogFactory为key的LogFactory实现类,若有,则使用该类实例化一个LogFactory实例。

4.    否则,使用默认的LogFactoryImpl实现类实例化一个LogFactory实例。

Commons Logging的类设计图如下:

在使用Commons Logging时,经常在服务器部署中会遇到ClassLoader的问题,这也是经常被很多人所诟病的地方,特别是在和Log4J一起使用的时候。常见的如,由于Common Logging使用非常广泛,因而很多Web容器(WebSphere)在内也会使用它作为日志处理系统而将其jar包引入到容器本身中,此时LogFactory是使用Web容器本身的ClassLoader装载的,即使Log4J中使用了ContextClassLoader来查找配置文件,此时的Thread依然在容器中,因而它使用的ClassLoader还是容器本身的ClassLoader实例,此时需要把Log4J的配置文件放到共享目录下,该配置文件才能被正常识别(以我的理解,容器在启动的时候,它根本无法获得Web应用程序中的jar包,所以也需要将Log4J的jar包放到共享目录中才可以,不过我木有用过WebSphere,也没法测试,所以只能猜测~)。在WebSphere还可以通过设置类的加载顺序为PARENT_LAST的方法来解决。而在Jboss中则只能将自己的配置加到其conf下的Log4J配置文件中,因为Jboss默认导入Log4J包。具体可以参考我转载的一篇文章,Log4j/common log和各种服务器集成的问题(木有经验,只能用别人的文章了。。。还很可惜的木有机会测试。。。):http://www.blogjava.net/DLevin/archive/2012/11/02/390639.html,另外还找到一篇更加详细的描述Commons Logging中存在的ClassLoader问题的文章:http://articles.qos.ch/classloader.html

 

Commons Logging的具体实现:

在使用Commons Logging时,一般是通过LogFactory获取Log实例,然后调用Log接口中相应的方法。因而Commons Logging的实现可以分成以下几个步骤:

1.    LogFactory类初始化

a.    缓存加载LogFactory的ClassLoader(thisClassLoader字段),出于性能考虑。因为getClassLoader()方法可能会使用AccessController(虽然目前并没有使用),因而缓存起来以提升性能。

b.    初始化诊断流。读取系统属性org.apache.commons.logging.diagnostics.dest,若该属性的值为STDOUT、STDERR、文件名。则初始化诊断流字段(diagnosticStream),并初始化诊断消息的前缀(diagnosticPrefix),其格式为:”[LogFactory from <ClassLoaderName@HashCode>] “, 该前缀用于处理在同一个应用程序中可能会有多个ClassLoader加载LogFactory实例的问题。

c.    如果配置了诊断流,则打印当前环境信息:java.ext.dir、java.class.path、ClassLoader以及ClassLoader层级关系信息。

d.    初始化factories实例(Hashtable),用于缓存LogFactory(context-classloader –-> LogFactory instance)。如果系统属性org.apache.commons.logging.LogFactory.HashtableImpl存在,则使用该属性定义的Class作为factories Hashtable的实现类,否则,使用Common Logging实现的WeakHashtable。若初始化没有成功,则使用Hashtable类本身。使用WeakHashtable是为了处理在webapp中,当webapp被卸载是引起的内存泄露问题,即当webapp被卸载时,其ClassLoader的引用还存在,该ClassLoader不会被回收而引起内存泄露。因而当不支持WeakHashtable时,需要卸载webapp时,调用LogFactory.relase()方法。

e.    最后,如果需要打印诊断信息,则打印“BOOTSTRAP COMPLETED”信息

2.    查找LogFactory类实现,并实例化。

当调用LogFactory.getLog()方法时,它首先会创建LogFactory实例(getFactory()),然后创建相应的Log实例。getFactory()方法不支持线程同步,因而多个线程可能会创建多个相同的LogFactory实例,由于创建多个LogFactory实例对系统并没有影响,因而可以不用实现同步机制。

a.    获取context-classloader实例。

b.    从factories Hashtable(缓存)中获取LogFactory实例。

c.    读取commons-logging.properties配置文件(如果存在的话,如果存在多个,则可以定义priority属性值,取所有commons-logging.properties文件中priority数值最大的文件),如果设置use_tccl属性为false,则在类的加载过程中使用初始化cache的thisClassLoader字段,而不用context ClassLoader。

d.    查找系统属性中是否存在org.apache.commons.logging.LogFactory值,若有,则使用该值作为LogFactory的实现类,并实例化该LogFactory实例。

e.    使用service provider方法查找LogFactory的实现类,并实例化。对应Service ID是:META-INF/services/org.apache.commons.logging.LogFactory

f.     查找commons-logging.properties文件中是否定义了LogFactory的实现类:org.apache.commons.logging.LogFactory,是则用该类实例化一个出LogFactory

g.    否则,使用默认的LogFactory实现:LogFactoryImpl类。

h.    缓存新创建的LogFactory实例,并将commons-logging.properties配置文件中所有的键值对加到LogFactory的属性集合中。

3.    通过LogFactory实例查找Log实例(LogFactoryImpl实现)

使用LogFactory实例调用getInstance()方法取得Log实例。

a.    如果缓存(instances字段,Hashtable)存在,则使用缓存中的值。

b.    查找用户自定义的Log实例,即从先从commons-logging.properties配置文件中配置的org.apache.commons.logging.Log(org.apache.commons.logging.log,旧版本)类,若不存在,查找系统属性中配置的org.apache.commons.logging.Log(org.apache.commons.logging.log,旧版本)类。如果找到,实例化Log实例

c.    遍历classesToDiscover数组,尝试创建该数组中定义的Log实例,并缓存Log类的Constructor实例,在下次创建Log实例是就不需要重新计算。在创建Log实例时,如果use_tccl属性设为false,则使用当前ClassLoader(加载当前LogFactory类的ClassLoader),否则尽量使用Context ClassLoader,一般来说Context ClassLoader和当前ClassLoader相同或者是当前ClassLoader的下层ClassLoader,然而在很多自定义ClassLoader系统中并没有设置正确的Context ClassLoader导致当前ClassLoader成了Context ClassLoader的下层,LogFactoryImpl默认处理这种情况,即使用当前ClassLoader。用户可以通过设置org.apache.commons.logging.Log.allowFlawedContext配置作为这个特性的开关。

d.    如果Log类定义setLogFactory()方法,则调用该方法,将当前LogFactory实例传入。

e.    将新创建的Log实例存入缓存中。

4.    调用Log实例中相应的方法

Log接口比较简单,并且Log4J、JDK相应的实现类也都直接代理给各自框架,因而实现比较简单,不在详述。关于SimpleLog,可以类似的参考http://www.blogjava.net/DLevin/archive/2012/06/12/380647.html。 不过还是有必要对Jdk14Logger的实现吐槽一下,每次调用log方法时都会创建新的Throwable实例,然后去计算ClassName和Method,这会引起严重的性能问题,因为创建一个Throwable实例,意味着需要停止当前的运行,dump出一个调用栈快照。它为什么不像Jdk13LumberjackLogger的实现一样,把这两个字段缓存起来呢??

时间: 2024-09-03 16:36:13

深入源码之Commons Logging的相关文章

深入源码之JDK Logging

JDK从1.4开始提供Logging实现,据说当初JDK打算采用Log4J的,后来因为某些原因谈判没谈拢,然后就自己开发了一套,不知道是为了报复而故意不沿用Log4J的命名方式和抽象方式,还是开发这个模块的人水平不够,或没用心,亦或是我用Commons Logging和Log4J习惯了,看JDK的Logging实现怎么看怎么不爽~~~吐个槽额~~~~ JDK Logging将日志打印抽象成以下几个类之间的交互: 1.    Level,定义日志的级别,类似Log4J中的Level类. JDK L

SpringMVC4.0 + Tomcat7 + JDK7环境搭建 + (Spring4.0jar包+源码+logging+SpringIDE百度网盘下载)

今天一时兴起想用一下新版本的框架,就找了一个SpringMVC4.0的来,还是遇到一些问题,写下来帮助一下大家吧,程序员都知道配环境是最头痛的. 这个里面就是Spring4.0jar包+源码+logging+SpringIDE,如果能下载别忘了点个赞. 云盘链接 链接:http://pan.baidu.com/s/1c1XqZOs 密码:y26a 最最基本的目录结构如下: 先建立动态web工程,然后导入jar包,把SpringMVC几个必须的jar包,加上logging处理日志jar包复制到li

Nutch2.3.1源码开发环境搭建

源码下载 修改配置文件 编译项目 导入intellij idea 调整依赖顺序 运行测试 联系作者 源码下载 下载地址:http://nutch.apache.org/downloads.html 解压后得到目录apache-nutch-2.3.1,进入该目录. 修改配置文件 修改配置文件conf/nutch-site.xml <!-- Put site-specific property overrides in this file. --> <configuration> &l

深入Log4J源码之SimpleLog

一个月前因为某些事情被困宁波三天,在酒店里闲着无事,然后开始看Log4J的源码.原本之后的一个星期就应该开始写了,无奈又遇到一些事情,迟迟没有动笔.感觉工作后要做好一件额外的事情总是很难,每天下班后才能看代码.写文章,而如果中途遇到一些没有预料到的事情就很容易不了了之了,所以现在如果出现能静下心来看代码.写文章的时间,我都是特别珍惜.我一直不知道如何开场一篇文章,所以先用一些废话做引子-.:( 在软件开发过程中,出现bug总是在所难免:事实上,以我个人经验,即使在实际开发阶段,fix bug时间

springMvc源码学习之:spring源码总结

转载自:http://www.cnblogs.com/davidwang456/p/4213652.html   spring beans下面有如下源文件包: org.springframework.beans, 包含了操作java bean的接口和类.org.springframework.beans.annotation, 支持包,提供对java 5注解处理bean样式的支持.org.springframework.beans.factory, 实现spring轻量级IoC容器的核心包.or

RSA加密解密(附源码工程)

版权声明:本文为博主原创文章,转载注明出处http://blog.csdn.net/u013142781 目录(?)[+] 一.RSA加密介绍 RSA公钥加密算法是1977年由罗纳德·李维斯特(Ron Rivest).阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的.1987年首次公布,当时他们三人都在麻省理工学院工作.RSA就是他们三人姓氏开头字母拼在一起组成的. RSA是目前最有影响力的公钥加密算法,它能够抵抗到目前为止已知的绝大多数密码攻击,

WebWork2源码分析续一

web 至于Action是的创建则是由ActionProxy来完成的,来看一段简要的程序调用 ActionProxy proxy = ActionProxyFactory.getFactory().createActionProxy(namespace, actionName, extraContext); request.setAttribute(ServletActionContext.WEBWORK_VALUESTACK_KEY, proxy.getInvocation().getStac

Struts源码研究

logic:Iterator标签(以下简称"该标签")是Struts里非常常用的一个标签,其作用在于循环显示给定容器对象中的值 如此常用的标签,其源代码当然需要拿出来研究一下,以下列举几条研究成果: 1.该标签内部使用Collection来表示给定的容器,所有的给定容器对象(如ArrayList,Map等)都会被其转化成为Collection 2.该标签自己维护循环索引 3.该标签常见的几个属性如下: name.property.scope.id 4.结合以上标签,给出一段源代码来解释

源码编译安装MySQL5.6.10最佳实践

  1安装cmake MySQL从5.5版本开始,通过./configure进行编译配置方式已经被取消,取而代之的是cmake工具. 因此,我们首先要在系统中源码编译安装cmake工具. # wget http://www.cmake.org/files/v2.8/cmake-2.8.7.tar.gz # tar zxvf cmake-2.8.7.tar.gz # cd cmake-2.8.7 # ./configure # make # make install 1.1cmake命令语法 1.