您的位置:首页 > 编程语言 > Java开发

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.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
,若该属性的值为
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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: