在写j2ee程序时,我们的程序中会经常出现这样一句代码:log.error(“something bad happend”);每个人都知道,这是打印日志用的,那么这个log对象如何来的呢?我们也知道,是
private Log log = LogFactory.getLog(CLASS.class);
这样来的。配置log已经不是一件技术活了,我们总是使用JCL作为wraper,调用实际的底层的日志框架,而日志框架无非就是log4j,logback。这里简单的记录一下log在系统中的配置和使用。
commons-log是非常简单的一个开源项目,apache-commons下代码算是最少的了。功能最简单,包装一个统一的log接口,然后调用实际的log框架完成日志记录的工作。
生一个log对象,是通过logFactory完成的。
1: /**
2: * Convenience method to return a named logger, without the application
3: * having to care about factories.
4: *
5: * @param clazz Class from which a log name will be derived
6: *
7: * @exception LogConfigurationException if a suitable <code>Log</code>
8: * instance cannot be returned
9: */
10: public static Log getLog(Class clazz)
11: throws LogConfigurationException {
12:
13: return (getFactory().getInstance(clazz));
14:
15: }
其中的getFactory方法,首先会尝试利用当前的classLoader去一个hashtable里取一个factory出来,这个设计为了避免资源重复(按照官方说法,getLog是一个很重的操作),利用了cache的思想,缓存了构建过的factory。这个缓存的key就是classloader。写到这里,插一句,这个设计在我们为一个已有项目添加资源利用时是必备的设计之一——缓存重复利用的资源。
1: ClassLoader contextClassLoader = getContextClassLoaderInternal();
2:
3: if (contextClassLoader == null) {
4: // This is an odd enough situation to report about. This
5: // output will be a nuisance on JDK1.1, as the system
6: // classloader is null in that environment.
7: if (isDiagnosticsEnabled()) {
8: logDiagnostic("Context classloader is null.");
9: }
10: }
11:
12: // Return any previously registered factory for this class loader
13: LogFactory factory = getCachedFactory(contextClassLoader);
14: if (factory != null) {
15: return factory;
16: }
当然第一次加载时是缓存取不到的,那么logFactory会尝试进行一次commons-logging.properties文件的读取,来配置具体的log方式,没有配置的话,接着会尝试"META-INF/services/org.apache.commons.logging.LogFactory”来配置。最后实在没有找到任何信息时,就会用commons-logging自己的org.apache.commons.logging.impl.LogFactoryImpl来实例化了。加载方式比较简单,利用初始时得到的classloader加载org.apache.commons.logging.impl.LogFactoryImpl即可。然后把得到的factory缓存住就OK了。
显然的事情是,logFactory这种东西都缓存了,那么log这个对象就更要缓存了,LogFactoryImpl里有个hashtable用来存放已经注册了的Log对象。key的话是调用时传入的className。如果配置了Log4j的话,那么接下来log的所有行为就写到log4j配置的日志里了(其实不用配置,log4j是commons-logging的首选支持)。在没有任何配置的情况下,会支持{log4j,jdk14log,jdk13log和simpleLog}
以log4j为例说一下具体的写日志:在commons-logging的Log4JLogger拿到后,作为一个包装器,其所有的写日志方法都是调用的内部的一个logger对象的,这个logger来自Log4j的Logger,logger是通过LogManager的getLogger方法得到的。
log4j的一个xml配置解说:
1: <?xml version="1.0" encoding="GBK"?>
2: <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
3: <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
4: <appender name="appender1" class="org.apache.log4j.DailyRollingFileAppender">
5: <param name="file" value="/home/admin/yourapp/app.log" />
6: <param name="encoding" value="GBK" />
7: <param name="append" value="true" />
8: <param name="threshold" value="info"/>
9: <layout class="org.apache.log4j.PatternLayout">
10: <param name="ConversionPattern" value="%d{yy/MM/dd HH:mm:ss:SSS}][%C-%M] %m%n" />
11: </layout>
12: </appender>
13: <logger name="com.yourcom.yourpackage" additivity="false">
14: <level value="your_loggingLevel"/>
15: <appender-ref ref="appender1"/>
16: </logger>
17: <root>
18: <level value="your_loggingLevel" />
19: <appender-ref ref="appender1" />
20: </root>
其中appender定义了日志以什么样的pattern打印到什么目录file,日志打印的级别threshold以及编码方式encoding。logger配置定义了哪些类里的log代码会被打印,这里name指定了对应的包名。root定义了log4j中的hierarchy类中的root logger。最终logger的所有打日志方法都会调用下面这个方法,这个方法在logger的父类Category中:
1: /**
2: Call the appenders in the hierrachy starting at
3: <code>this</code>. If no appenders could be found, emit a
4: warning.
5:
6: <p>This method calls all the appenders inherited from the
7: hierarchy circumventing any evaluation of whether to log or not
8: to log the particular log request.
9:
10: @param event the event to log. */
11: public
12: void callAppenders(LoggingEvent event) {
13: int writes = 0;
14:
15: for(Category c = this; c != null; c=c.parent) {
16: // Protected against simultaneous call to addAppender, removeAppender,...
17: synchronized(c) {
18: if(c.aai != null) {
19: writes += c.aai.appendLoopOnAppenders(event);
20: }
21: if(!c.additive) {
22: break;
23: }
24: }
25: }
26:
27: if(writes == 0) {
28: repository.emitNoAppenderWarning(this);
29: }
30: }
一个LoggingEvent在打日志的时候会被实例化,并且赋予一些记录日志相关的信息。第19行的代码其实就是在调用配置文件中配置过的appender。具体的打印样式,会被Layout类处理。
最后关于性能的讨论,来源于官网的解释,logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i])); 这样一条语句带来了大量的string操作及参数构建。所以文档建议我们使用这样的写法:
1: if(logger.isDebugEnabled() {
2: logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
3: }
理由是这样避免了log参数的构建。当然,用commons-logging也可以的,其中的Log也封装了所有的Logger原有的方法。另外,layout花费了不少时间,目前的文档说,SimpleLayout已经优化到和System.out.print()一样快了。
总结一下,log4j是使用最普遍的日志应用了,依靠日志,我们记录了信息,同时可以帮助调试,是服务器端开发的一大利器。了解日志系统的原理也是必需的技能。log系统值得深入的地方还有很多。以后继续挖掘。
参考资料:
http://www.iteye.com/topic/378077
http://logging.apache.org/log4j/1.2/manual.html