java日志组件(2):common-logging
2017-02-16 19:34
190 查看
一.概述
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.SimpleLogcommon-logging自带日志实现类。它实现了Log接口,把日志消息都输出到系统错误流System.err 中。
-
org.apache.commons.logging.impl.NoOpLogcommon-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,若该属性的值为
STDOUT、
STDERR、文件名。则初始化诊断流字段(
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.Log(
org.apache.commons.logging.log,旧版本)类,若不存在,查找系统属性中配置的
org.apache.commons.logging.Log(
org.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相关文章推荐
- java日志组件介绍(common-logging,log4j,slf4j,logback )
- java日志组件介绍(common-logging,log4j,slf4j,logback )
- java日志组件介绍(common-logging,log4j,slf4j,logback )
- java日志组件介绍(common-logging,log4j,slf4j,logback )
- java日志组件介绍(common-logging,log4j,slf4j,logback )
- java日志组件介绍(common-logging,log4j,slf4j,logback )
- java日志组件介绍(common-logging,log4j,slf4j,logback )
- java日志组件介绍(common-logging,log4j,slf4j,logback )
- java日志组件介绍(common-logging,log4j,slf4j,logback )+ java日志,需要知道的几件事(commons-logging,log4j,slf4j,logback
- java日志组件介绍(common-logging,log4j,slf4j,logback )
- java日志组件介绍(common-logging,log4j,slf4j,logback )
- java日志组件介绍(common-logging,log4j,slf4j,logback )
- java日志组件介绍(common-logging,log4j,slf4j,logback )
- java日志组件介绍(common-logging,log4j,slf4j,logback )
- java日志组件介绍(common-logging,log4j,slf4j,logback )
- java日志组件介绍(common-logging,log4j,slf4j,logback )
- java日志组件介绍(common-logging,log4j,slf4j,logback )
- java日志组件介绍(common-logging,log4j,slf4j,logback )
- java日志组件介绍(common-logging,log4j,slf4j,logback )
- java日志组件介绍(common-logging,log4j,slf4j,logback)