OK,现在我们来研究下common-logging的源码。这篇博客有参照上善若水的博客,感谢他的无私分享。

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



  • common-logging的包结构

本来我要下载这个包的源码然后在本地跑一下的,结果一看jar包就这么几个类,所以呢?这里也就不直接跑源码和调试源码了。common-logging的包结构如下:



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

  • LogFactory获取相对应的Log实现类源码

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

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

这里贴出LogFactory获取相对应的Log实现类的核心代码:

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



  • 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实现类



其实,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的配置文件放到共享目录下,该配置文件才能被正常识别。在WebSphere还可以通过设置类的加载顺序为PARENT_LAST的方法来解决。而在Jboss中则只能将自己的配置加到其conf下的Log4J配置文件中,因为Jboss默认导入Log4J包。





  • Commons Logging的具体实现:





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





  • 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”信息



  • 查找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的属性集合中。



  • 通过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实例存入缓存中。

  • 调用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的情况,代码比较严谨。这点值得我们学习。



common-logging源码解析的更多相关文章

  1. Common.Logging源码解析二

    Common.Logging源码解析一分析了LogManager主入口的整个逻辑,其中第二步生成日志实例工厂类接口分析的很模糊,本随笔将会详细讲解整个日志实例工厂类接口的生成过程! (1).关于如何生 ...

  2. Common.Logging源码解析一

    Common.Logging是Apache下的一个开源日志接口组件,主要用于切换不同的日志库,因为当前流行的日志库有很多向log4j.log4net(log4j的.net版本)等等,所以为了能灵活的切 ...

  3. [源码解析] 并行分布式任务队列 Celery 之 多进程模型

    [源码解析] 并行分布式任务队列 Celery 之 多进程模型 目录 [源码解析] 并行分布式任务队列 Celery 之 多进程模型 0x00 摘要 0x01 Consumer 组件 Pool boo ...

  4. [源码解析] 深度学习分布式训练框架 horovod (14) --- 弹性训练发现节点 & State

    [源码解析] 深度学习分布式训练框架 horovod (14) --- 弹性训练发现节点 & State 目录 [源码解析] 深度学习分布式训练框架 horovod (14) --- 弹性训练 ...

  5. [源码解析] PyTorch 分布式(11) ----- DistributedDataParallel 之 构建Reducer

    [源码解析] PyTorch 分布式(11) ----- DistributedDataParallel 之 构建Reducer 目录 [源码解析] PyTorch 分布式(11) ----- Dis ...

  6. [源码解析] PyTorch 分布式之弹性训练(5)---Rendezvous 引擎

    [源码解析] PyTorch 分布式之弹性训练(5)---Rendezvous 引擎 目录 [源码解析] PyTorch 分布式之弹性训练(5)---Rendezvous 引擎 0x00 摘要 0x0 ...

  7. [源码解析] TensorFlow 分布式环境(8) --- 通信机制

    [源码解析] TensorFlow 分布式环境(8) --- 通信机制 目录 [源码解析] TensorFlow 分布式环境(8) --- 通信机制 1. 机制 1.1 消息标识符 1.1.1 定义 ...

  8. Spring-cloud & Netflix 源码解析:Eureka 服务注册发现接口 ****

    http://www.idouba.net/spring-cloud-source-eureka-client-api/?utm_source=tuicool&utm_medium=refer ...

  9. solr&lucene3.6.0源码解析(四)

    本文要描述的是solr的查询插件,该查询插件目的用于生成Lucene的查询Query,类似于查询条件表达式,与solr查询插件相关UML类图如下: 如果我们强行将上面的类图纳入某种设计模式语言的话,本 ...

  10. Flume-ng源码解析之Source组件

    如果你还没看过Flume-ng源码解析系列中的启动流程.Channel组件和Sink组件,可以点击下面链接: Flume-ng源码解析之启动流程 Flume-ng源码解析之Channel组件 Flum ...

随机推荐

  1. flask入门

    一.Flask介绍(轻量级的框架,非常快速的就能把程序搭建起来) Flask是一个基于Python开发并且依赖jinja2模板和Werkzeug WSGI服务的一个微型框架,对于Werkzeug本质是 ...

  2. Git的使用-如何将本地项目上传到Github

    默认你的电脑上已经安装了git. 第一步:我们需要先创建一个本地的版本库(其实也就是一个文件夹). 你可以直接右击新建文件夹,也可以右击打开Git bash命令行窗口通过命令来创建. 现在我通过命令行 ...

  3. CubeMX使用及感受

    简介 CubeMX这几年刚流行起来,是一个STM32代码的初始化配置工具,里面封装了硬件层.中间层,以及示例代码. cube使用 该软件的安装需要较高版本jdk支持,固件库安装时需要注意和主程序的版本 ...

  4. Visual Studio总是在重新生成项目?

    你是否曾经有过这种感觉:即使代码没有改变,Visual Studio也总是在重新生成项目? 我们可以生成一个项目,然后不做任何处理后再次生成,我们就可以看见--VS正在开始生成项目,而我的项目代码并没 ...

  5. Linux centos7系统下svn的安装与配置

    一.安装svn # yum -y install svn 二.查看svn版本信息 # svnserve --version 三.搭建svn版本库(假设项目名称为project) 1.首先创建版本库目录 ...

  6. 任务调度框架Quartz原理简介

    [TOC] 第一章 Quartz 1.1 Quartz概念 Quartz是OpenSymphony开源组织的一个Java开源项目, 在2009被Terracotta收购.Quartz官网 1.2 Qu ...

  7. 阻止a标签的默认事件及延伸

    先贴一段代码 <html lang="en"> <head> <meta charset="UTF-8"> <meta ...

  8. AtCoder Regular Contest 069 D

    D - Menagerie Time limit : 2sec / Memory limit : 256MB Score : 500 points Problem Statement Snuke, w ...

  9. Java面向对象接口的应用实例练习

    interface USB { public void open(); public void close(); } class Upan implements USB { public void o ...

  10. Java面向对象抽象类案例分析

    /** 雇员示例: 需求:公司中程序员有姓名,工号,薪水,工作内容 项目经理除了有姓名,工号,薪水还有奖金,工作内容 对给出需求进行数据建模 分析: 在这个问题领域中,先找出涉及的对象 通过名词提炼法 ...