java日志组件(2):common-logging

一.概述

Jakarta Commons Logging (JCL)提供的是一个日志(Log)接口(interface),同时兼顾轻量级和不依赖于具体的日志实现工具。它提供给中间件/日志工具开发者一个简单的日志操作抽象,允许程序开发人员使用不同的具体日志实现工具。用户被假定已熟悉某种日志实现工具的更高级别的细节。JCL提供的接口,对其它一些日志工具,包括Log4J, Avalon LogKit, and JDK 1.4等,进行了简单的包装,此接口更接近于Log4J和LogKit的实现。

JDK Logging据说当初是想用Log4J的,但是当时两家好像谈判谈崩了,然后JDK自己实现了一个,貌似结构和Log4J差不多,只是实现的比较烂,基本上也只能在做测试的时候用,而SLF4J和LogBack都是出自Log4J的创始人Ceki Gülcü之手。

二.原理

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

三.common-logging的包结构

这里写图片描述

四.底层实现流程

1.LogFactory获取相对应的Log实现类逻辑

JCL有两个基本的抽象类:Log(基本记录器)和LogFactory(负责创建Log实例)。当commons-logging.jar被加入到 CLASSPATH之后,它会合理地猜测你想用的日志工具,然后进行自我设置,用户根本不需要做任何设置。默认的LogFactory是按照下列的步骤去发现并决定那个日志工具将被使用的(按照顺序,寻找过程会在找到第一个工具时中止):
1. 寻找当前factory中名叫org.apache.commons.logging.Log配置属性的值
2. 寻找系统中属性中名叫org.apache.commons.logging.Log的值
3. 如果应用程序的classpath中有log4j,则使用相关的包装(wrapper)类(Log4JLogger)
4. 如果存在Lumberjack版本的Logging系统,则使用Jdk13LumberjackLogger类。
5. 如果应用程序运行在jdk1.4的系统中,使用相关的包装类(Jdk14Logger)
6. 使用简易日志包装类(SimpleLog)
7. 以上步骤都失败,则抛出LogConfigurationException。

org.apache.commons.logging.Log的具体实现:

org.apache.commons.logging.Log的具体实现有如下:
- org.apache.commons.logging.impl.Jdk14Logger 使用JDK1.4。
- org.apache.commons.logging.impl.Log4JLogger 使用Log4J。
- org.apache.commons.logging.impl.LogKitLogger 使用 avalon-Logkit。
- org.apache.commons.logging.impl.SimpleLog common-logging自带日志实现类。它实现了Log接口,把日志消息都输出到系统错误流System.err 中。
- org.apache.commons.logging.impl.NoOpLog common-logging自带日志实现类。它实现了Log接口。 其输出日志的方法中不进行任何操作。

获取一个log实例

public static Log LOG = LogFactory.getLog(CommonsLoggingTest.class);  

LogFactory抽象类:

public static Log getLog(Class clazz) throws LogConfigurationException {  
       return getFactory().getInstance(clazz);  
}

LogFactoryImpl实现类:

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 {  
           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;  
} 

如果在获取具体的实例的时候,common-logging的logConstructor属性不为空,则直接抛下反射初始化该实例就OK,如果该属性为空,那么就要按照顺序去找对应的日志实例。核心代码如下:

private Log discoverLogImplementation(String logCategory) throws LogConfigurationException {    
    //1,初始化配置  
    initConfiguration();  
    //2,寻找用户自定义的日志实例  
    Log result = null;  
    String specifiedLogClassName = findUserSpecifiedLogClassName();  
    //3,框架开始工作,按照顺序初始化log实例  
    if (specifiedLogClassName != null) {  
        // 初始化log实例  
        result = createLogFromClass(specifiedLogClassName,logCategory,true);  
        if (result == null) {  
            StringBuffer messageBuffer =  new StringBuffer("User-specified log class '");  
            messageBuffer.append(specifiedLogClassName);  
            messageBuffer.append("' cannot be found or is not useable.");   

            // 顺序连接报错字符串,抛出一个异常  
            informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LOG4J_LOGGER);  
            informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_JDK14_LOGGER);  
            informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LUMBERJACK_LOGGER);  
            informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_SIMPLE_LOGGER);  
            throw new LogConfigurationException(messageBuffer.toString());  
        }  

        return result;  
     }  
} 

2.自定义LogFactory实现类

(1)流程

其实,Commons Logging还支持用户自定义的LogFactory实现类。对LogFactory类的查找逻辑为:
① 查看系统属性中是否存在以org.apache.commons.logging.LogFactory为key的LogFactory实现类,若有,则使用该类实例化一个LogFactory实例。

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

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

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

Commons Logging的类设计图如下:
这里写图片描述

(2)ClassLoader的问题

在使用Commons Logging时,经常在服务器部署中会遇到ClassLoader的问题,这也是经常被很多人所诟病的地方,特别是在和Log4J一起使用的时候。常见的如,由于Common Logging使用非常广泛,因而很多Web容器(WebSphere)在内也会使用它作为日志处理系统而将其jar包引入到容器本身中,此时LogFactory是使用Web容器本身的ClassLoader装载的,即使Log4J中使用了ContextClassLoader来查找配置文件,此时的Thread依然在容器中,因而它使用的ClassLoader还是容器本身的ClassLoader实例,此时需要把Log4J的配置文件放到共享目录下,该配置文件才能被正常识别。在WebSphere还可以通过设置类的加载顺序为PARENT_LAST的方法来解决。而在Jboss中则只能将自己的配置加到其conf下的Log4J配置文件中,因为Jboss默认导入Log4J包。
原理参考:http://www.blogjava.net/DLevin/archive/2012/11/10/391122.html

3.Commons Logging的具体实现:

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

(1)LogFactory类初始化

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

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

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

④ 初始化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()方法。
⑤ 最后,如果需要打印诊断信息,则打印“BOOTSTRAP COMPLETED”信息。

(2)查找LogFactory类实现,并实例化

当调用LogFactory.getLog()方法时,它首先会创建LogFactory实例(getFactory()),然后创建相应的Log实例。getFactory()方法不支持线程同步,因而多个线程可能会创建多个相同的LogFactory实例,由于创建多个LogFactory实例对系统并没有影响,因而可以不用实现同步机制。
① 获取context-classloader实例。
②从factories Hashtable(缓存)中获取LogFactory实例。
③ 读取commons-logging.properties配置文件(如果存在的话,如果存在多个,则可以定义priority属性值,取所有commons-logging.properties文件中priority数值最大的文件),如果设置use_tccl属性为false,则在类的加载过程中使用初始化cache的thisClassLoader字段,而不用context ClassLoader。
④查找系统属性中是否存在org.apache.commons.logging.LogFactory值,若有,则使用该值作为LogFactory的实现类,并实例化该LogFactory实例。
⑤ 使用service provider方法查找LogFactory的实现类,并实例化。对应Service ID是:META-INF/services/org.apache.commons.logging.LogFactory
⑥ 查找commons-logging.properties文件中是否定义了LogFactory的实现类:org.apache.commons.logging.LogFactory,是则用该类实例化一个出LogFactory
⑦ 否则,使用默认的LogFactory实现:LogFactoryImpl类。
⑧缓存新创建的LogFactory实例,并将commons-logging.properties配置文件中所有的键值对加到LogFactory的属性集合中。

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

使用LogFactory实例调用getInstance()方法取得Log实例。
① 如果缓存(instances字段,Hashtable)存在,则使用缓存中的值。

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

③ 遍历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配置作为这个特性的开关。

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

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

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

Log实例中其实就是各个插拔日志的一个门面,关于门面可以去看我设计模式相关的文章。这里就以Log4JLogger为例,解释下该门面log。

下面先贴Log4JLogger核心源码:

public class Log4JLogger implements Log, Serializable {

    /** Serializable version identifier. */
    private static final long serialVersionUID = 5160705895411730424L;


    /** The fully qualified name of the Log4JLogger class. */
    private static final String FQCN = Log4JLogger.class.getName();

    /** Log to this logger */
    private transient volatile Logger logger = null;

    /** Logger name */
    private final String name;

    private static final Priority traceLevel;


    static {
        if (!Priority.class.isAssignableFrom(Level.class)) {
            // nope, this is log4j 1.3, so force an ExceptionInInitializerError
            throw new InstantiationError("Log4J 1.2 not available");
        }

        Priority _traceLevel;
        try {
            _traceLevel = (Priority) Level.class.getDeclaredField("TRACE").get(null);
        } catch(Exception ex) {
            // ok, trace not available
            _traceLevel = Level.DEBUG;
        }
        traceLevel = _traceLevel;
    }

    // ------------------------------------------------------------ Constructor

    public Log4JLogger() {
        name = null;
    }

    /**
     * Base constructor.
     */
    public Log4JLogger(String name) {
        this.name = name;
        this.logger = getLogger();
    }

    /**
     * For use with a log4j factory.
     */
    public Log4JLogger(Logger logger) {
        if (logger == null) {
            throw new IllegalArgumentException(
                "Warning - null logger in constructor; possible log4j misconfiguration.");
        }
        this.name = logger.getName();
        this.logger = logger;
    }

    /**
     * Logs a message with <code>org.apache.log4j.Priority.DEBUG</code>.
     *
     * @param message to log
     * @see org.apache.commons.logging.Log#debug(Object)
     */
    public void debug(Object message) {
        getLogger().log(FQCN, Level.DEBUG, message, null);
    }

    /**
     * Logs a message with <code>org.apache.log4j.Priority.DEBUG</code>.
     *
     * @param message to log
     * @param t log this cause
     * @see org.apache.commons.logging.Log#debug(Object, Throwable)
     */
    public void debug(Object message, Throwable t) {
        getLogger().log(FQCN, Level.DEBUG, message, t);
    }

    /**
     * Return the native Logger instance we are using.
     */
    public Logger getLogger() {
        Logger result = logger;
        if (result == null) {
            synchronized(this) {
                result = logger;
                if (result == null) {
                    logger = result = Logger.getLogger(name);
                }
            }
        }
        return result;
    }

    /**
     * Check whether the Log4j Logger used is enabled for <code>DEBUG</code> priority.
     */
    public boolean isDebugEnabled() {
        return getLogger().isDebugEnabled();
    }


}

关于上面源码的解释:

1,首先封装log4j的logger类作为属性,然后在封装一个name属性,该属性用来初始化logger时候的构造器参数,在初始化log4jLogger类的时候该name传入,然后用该name来初始化logger实例属性。

2,对外提供一套和log4j一致的API,然后方法中调用logger属性相关API方法就OK了。这里没有直接用属性.方法(),而是用了getLogger(),这样子可以防止logger属性是null的情况,代码比较严谨。这点值得我们学习。

五.使用JCL开发

因为Log4j的强大,同时开发者又不希望对Log4j的依赖性太强。所以目前比较流行的是Commons-logging和Log4j结合使用。

六.转载来源

http://blog.csdn.net/u011794238/article/details/50749260

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值