无责任的烂笔头

Concentrate & enjoy!
posts - 3, comments - 15, trackbacks - 0, articles - 7
  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

Words on Java Logging

Posted on 2010-03-18 00:52 BZ 阅读(854) 评论(0)  编辑  收藏 所属分类: Misc


  •  关于Commons Logging

Commons Logging库包含了commons-logging-api.jarcommons-logging.jar. 查看这两个文件的内容, 你会发现它们的差别并不大: org.apache.commons.logging包下的内容是相同的,差异的地方仅在org.apache.commons.logging.impl. 这两个包内容的描述可以参考官方网站:

http://commons.apache.org/logging/guide.html#commons-logging.jar

 

简单地说, commons-logging-api.jar仅包含定义接口的类, commons-logging.jar是其的一种实现。由于commons-logging.jar实现类较少且简单, 为了方便部署, 于是将commons-logging-api.jar中对于接口的定义也一并打包在commons-logging.jar. 这样一来, 对于要采用commons-logging为实现类的应用程序,只要让commons-logging.jarclasspath中就好。类似地, 如果不计划使用commons-logging为实现(但采用commons-logging-api的另一种实现),则需要将commons-logging-api.jar放到classpath, commons-logging.jar则是不需要(也不能添加).

 

ps: commons-logging-api接口又被称为JCL(Jakarta Commons Logging). 实现JCL接口的库除了Commons Logging自己外, 另一个很有名的就是SLF4J(http://www.slf4j.org/).


  • SLF4J性能最优

SLF4J的官方网站指明SLF4J拥有绝佳的性能. http://www.slf4j.org/faq.html#logging_performance. SLF4J提供的API甚至达到其它API性能的30:

… the second form will outperform the first form by a factor of at least 30, in case of a disabled logging statement …

性能的提升关键在于减少将对象(特别是非常复杂的对象且其toString()方法实现得比较耗时)转换为String的开销. JCL定义的log方法(trace, debug, info, warnerror)只包含一个Object类型的参数. 这样, 执行这些方法之前就必须将所有要log的东西转化为一个Object类型的对象(通常是String), 例如:

logger.debug("The new entry is "+entry+".");

 

如果entry的转换比较复杂, 就会比较耗时. 特别是如果在上述例子中, debug levellog被禁用了, 这种转换的开销则是完全没有必要的(白白浪费CPU和内存). 当然, 加上对相应log level的启用/禁用的判断,然后再调用log方法, 也是可以避免这些开销的. 例如:

   if (logger.isDebugEnabled()) {

     logger.debug("The new entry is "+entry+".");

}

 

在这种情况下, 转换的开销就被避免了.在上述代码中, 对于log level的启用/禁用的判断实际上是执行了两次: 一次是在if语句中的显式调用, 另一次则是log方法内部在做真正的log动作之前进行了调用. 对于log level的启用/禁用的判断产生的开销相当小, 很多时候是可以忽略不计的. 但是, SLF4J比较计较, 它认为仍然有一次调用是没有必要的. SLF4J解决这个问题的方法也很简单: 利用与String.format()类似的方法, 需要的参数无需转换, 而是作为函数的参数传入, 函数在实现时, 先判断log level是否被启用. 如果没有, 直接返回. 否则, 转换, 然后log. 从这种实现思路可以知道, SLF4J需要定义另一套API, JCL所有log方法的基础上增加一个(或多个)用来表示传入对象的参数。例如:

public void debug(String format, Object arg) {

    if (logger.isLoggable(Level.FINE)) {  //判断log level的启用情况

      String msgStr = MessageFormatter.format(format, arg);  //format,转化

      log(SELF, Level.FINE, msgStr, null);  //log动作

    }

   }

 

 

这下, 调用这样的API自然就能在性能上胜人一筹了:

logger.debug("The entry is {}.", entry);  //直接调用, 无需判断log level是否启用. 简洁,高效

 

需要说明的是:

l   log方法的第一个参数(format), SLF4J自定义的, 不是String.format()所支持的那些. SLF4Jformat更简单.

l   SLF4J本身也是接口和实现分离的. 不是SLF4J的所有实现能具有性能上的优势, 比如SLF4Jsimple实现版本就完全没有性能上的优化, 所以选择SLF4J的实现前, 需要考察一下.

  • SpringSLF4J

Spring从一开始就依赖JCL(同时出于后向兼容的考虑, 在可以预见的未来, 也会持续依赖JCL), 而不是SLF4J. Spring的官方文档中可以看到这样的叙述:

If we could turn back the clock and start Spring now as a new project it would use a different logging dependency. The first choice would probably be the Simple Logging Facade for Java (SLF4J), which is also used by a lot of other tools that people use with Spring inside their applications.

 

可见, Spring的开发人员也意识到JCL的默认实现没有SLF4J优秀. 幸运的是,尽管SpringJCL默认实现的依赖如此严重, Spring中将其替换也不是不可能. 事实上, Spring官方就给出了一种方法, 这种方法本质上是将SLF4J作为JCL的一种实现:

l   需要保留commons-logging-api.jar. 很多Spring Module都依赖JCL. 同时排除commons-logging.jar, 不要使用这种实现.

l   添加jcl-over-slf4j.jar. 这作为JCLSLF4J之间的bridge, 实际上就是采用SLF4J来实现JCL.

l   添加slf4j-api.jar. 这是SLF4J的接口包.

l   添加SLF4J的一种实现(以及这种实现需要的其他包). 比如计划使用log4j, 则添加slf4j-log4j12.jar(以及log4j.jar).


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


网站导航: