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的实例对象。


最常用的方法是getLogClass clazz

 

可以看到方法内代码只有一行: return (getFactory().getInstance(clazz));

 

getInstance是个抽象方法,后面讲解。

 

这里解析下getFactory方法

 

  1. 获得当前线程的classloader,命名为contextClassLoader
  2. 根据contextClassLoader在缓存中获得logFactory,这里的缓存使用的是org.apache.commons.logging.impl.WeakHashTablekeyclassloadervaluelogFactory,如果在缓存中有logFactory则直接返回,没有进入下面的流程
  3. 读取配置文件commons-logging.properties
  4. 如果读到了配置文件,判断其中use_tccl属性,然后设定baseClassLoader是使用contextClassLoader还是使用加载本Class文件的那个classloader(名字为thisClassLoader
  5. 下面生成logFactory,这里会有使用四种方式,依次来尝试生成。
  6. 将生成的logFactory缓存起来
  7. 返回logFactory

第5条种四种方式是:

  • 通过系统属性中寻找org.apache.commons.logging.LogFactoryvalue值,根据值为类名生成logFactory
  • 通过资源META-INF/services/org.apache.commons.logging.LogFactory,获得的值为类名生成logFactor
  • 通过刚才读取的配置文件commons-logging.properties,从中获取以org.apache.commons.logging.LogFactorykeyvalue值,根据值为类名生成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参数),接着实例化。

 

如果这里affectStatecreateLogFromClass最后一个参数)为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.4logging,如果再没有,就使用Lumberjack,它是为jdk1.21.3实现了Java日志 API,再没有,那就使用自身提供的一个logSimpleLog)。

 

可以说commons-logging提供了一套简单的日志API给用户,但对于输出格式设定等问题,没有提供必要的API给用户,这点只能靠使用Log4j等更加强大的日志框架来完成,这也可能是为什么采用log4j作为最优先实现的原因吧。

 

项目中使用commons-logging的原因,可能是为了不与log4j死死的绑定在一起,使用commons-logging作为一个中间层,达到解耦的目的。

 

所以说commons-logging 的最大贡献就是为日志输出提供一个统一的接口,并且会在运行时,很方便的自动选择好日志实现系统。

 

 

顺便摘抄一段关于日志级别的信息:

 

确保日志信息在内容上和反应问题的严重程度上的恰当,是非常重要的。

  1. fatal非常严重的错误,导致系统中止。期望这类信息能立即显示在状态控制台上。
  2. error其它运行期错误或不是预期的条件。期望这类信息能立即显示在状态控制台上。
  3.  warn使用了不赞成使用的API、非常拙劣使用API, '几乎就是'错误其它运行时不合需要和不合预期的状态但还没必要称为 "错误"。期望这类信息能立即显示在状态控制台上。
  4. info运行时产生的有意义的事件。期望这类信息能立即显示在状态控制台上。
  5. debug系统流程中的细节信息。期望这类信息仅被写入log文件中。
  6. trace更加细节的信息。期望这类信息仅被写入log文件中。

通常情况下,记录器的级别不应低于info。

也就是说,通常情况下debug的信息不应被写入log文件中。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值