首页 > 代码库 > JAVA 日志库3

JAVA 日志库3

    Commons Logging和SLF4J都是基于相同的设计,即从一个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的实现可以分成以下几个步骤:

1.    LogFactory类初始化

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

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

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

d.    初始化factories实例(Hashtable),用于缓存LogFactorycontext-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,则在类的加载过程中使用初始化cachethisClassLoader字段,而不用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.Logorg.apache.commons.logging.log,旧版本)类,若不存在,查找系统属性中配置的org.apache.commons.logging.Logorg.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实例中相应的方法

在使用Commons Logging时,经常在服务器部署中会遇到ClassLoader的问题,这也是经常被很多人所诟病的地方,特别是在和Log4J一起使用的时候。常见的如,由于Common Logging使用非常广泛,因而很多Web容器(WebSphere)在内也会使用它作为日志处理系统而将其jar包引入到容器本身中,此时LogFactory是使用Web容器本身的ClassLoader装载的,即使Log4J中使用了ContextClassLoader来查找配置文件,此时的Thread依然在容器中,因而它使用的ClassLoader还是容器本身的ClassLoader实例,此时需要把Log4J的配置文件放到共享目录下,该配置文件才能被正常识别(以我的理解,容器在启动的时候,它根本无法获得Web应用程序中的jar包,所以也需要将Log4J的jar包放到共享目录中才可以,不过我木有用过WebSphere,也没法测试,所以只能猜测~)。
正是为解决上述ClassLoader的问题,log4j作者又开发了SLF4J,并且在使用Commons Logging时,我们经常会看到以下方法的写法:

if (logger.isDebugEnabled()) {

    logger.info("Loading XML bean definitions from " + encodedResource.getResource());

}

存在isDebugEnabled()的判断逻辑是为了在避免多余的字符串拼接,即如果不存在isDebugEnabled()判断,即使当前日志级别为ERROR时,在遇到logger.info()调用时,它还会先拼接日志消息的字符串,然后进入该方法内,才发现这个日志语句不用打印。而这种多余的拼接不仅浪费了多余的CPU操作,而且会增加GC的负担。SLF4J则提供以下的方式来解决这个问题:

logger.info("Loading XML bean definitions from {}", encodedResource.getResource());

SLF4J的设计如下:
技术分享

由于采用了静态绑定的方式,而不是像Commons Logging中的动态绑定,SLF4JLoggerFactory的实现要比Commons LoggingLogFactory的实现要简单的多。即LoggerFactory调用getILoggerFactory()方法,该方法会初始化LoggerFactory,即通过在bind()方法中加载classpath中的StaticLoggerBinder类,并根据加载结果设置当前LoggerFactory的初始化状态,从而在getILoggerFactory()方法中通过当前LoggerFactory的状态判断返回的ILoggerFactory实例。简单的示意图如下:

技术分享

SLF4J支持上层是SLF4J框架,底层还是通过Commons Logging的动态查找机制,只要将slf4j-jcl-<version>.jar包加入classpath中即可(当然slf4j-api-<version>.jar也要存在)。
 
另外SLF4J还支持上层是Commons Logging,而底层交给SLF4J提供的静态绑定机制查找真正的日志实现框架,只需要将jcl-over-slf4j-<version>.jar包加入到classpath中,此时不需要引入commons-logging-<version>.jar包。它的实现只是重写了Commons Logging框架,并在LogFactory中只使用SLF4JLog或SLF4JLocationAwareLog类。
 
不过需要注意,slf4j-jcl-<version>.jar包和jcl-over-slf4j-<version>.jar两个包不能同时出现在classpath中,不然会引起循环调用而导致栈溢出的问题,因而slf4j-jcl-<version>.jar在初始化时就会检测这个限制,并抛出异常。
 
最后SLF4J还支持Log4J作为上层,而底层交给SLF4J静态绑定要真正实现日志打印的框架,可以将log4j-over-slf4j-<version>.jar包加入到classpath中。其实现也类似jcl-over-slf4j-<version>.jar的实现,重写大部分的Log4J的内部逻辑,而在Logger类实现中,将真正的日志打印逻辑代理给SLF4J的LoggerFactory。


来源: http://www.blogjava.net/DLevin/archive/2012/11/08/390991.html
来源: http://www.blogjava.net/DLevin/archive/2012/11/04/390755.html


来自为知笔记(Wiz)


JAVA 日志库3