Change Dir

先知cd——热爱生活是一切艺术的开始

统计

留言簿(18)

积分与排名

“牛”们的博客

各个公司技术

我的链接

淘宝技术

阅读排行榜

评论排行榜

简单的log

在写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

posted on 2012-03-28 20:03 changedi 阅读(2340) 评论(1)  编辑  收藏

评论

# re: 简单的log 2012-03-30 07:50 tb

很不错的   回复  更多评论   


只有注册用户登录后才能发表评论。


网站导航: