浅谈Log4j的扩展 (一)

来源:互联网 发布:结构设计软件体系 编辑:程序博客网 时间:2024/06/11 07:23

转自:http://blog.sina.com.cn/s/blog_5f53615f0100sfo1.html

 

浅谈Log4j的扩展 (一)    
#1楼

摘要:Log4j是Apache的一个开放源代码项目,通过使用Log4j,我们可以控制日志信息输出地;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。那么是不是这样,我们就可以完全使用log4j,而不需要扩展定制了呢?

作者:武玉厚

查看本文第二部分

1       基本介绍Log4j
  Log4j是Apache的一个开放源代码项目,通过使用Log4j,我们可以控制日志信息输出地;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
  log4j的好处在于:
1) 通过修改配置文件,就可以决定log信息的目的地——控制台、文件、GUI组件、甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等
2) 通过修改配置文件,可以定义每一条日志信息的级别,从而控制是否输出。在系统开发阶段可以打印详细的log信息以跟踪系统运行情况,而在系统稳定后可以关闭log输出,从而在能跟踪系统运行情况的同时,又减少了垃圾代码(System.out.println(......)等)。
3) 使用log4j,需要整个系统有一个统一的log机制,有利于系统的规划。
  那么是不是这样,我们就可以完全使用log4j,而不需要扩展定制了呢?当然不是这样,因为每个项目的需求不一样,而且log4j本身也提供了灵活的扩展机制。下面我们说说log4j常用的扩展方式。
2       扩展点Log4j
2.1   自己的日志系统
  每一个项目都想有自己的一套日志系统,而不受其他项目、jar包的影响。所以日志系统需要独立存在,且有相应的自己单独的配置文件而不受影响。我们先来看看common-logging和log4j的默认工作流程:
common-logging
当我们用Log log = LogFactory.getLog(“loggerName”);取得log时,让我们看看他是如何工作的?
1)        首先在classpath下寻找自己的配置文件commons-logging.properties,如果找到,则使用其中定义的Log实现类
2)        如果找不到commons-logging.properties文件,则在查找是否已定义系统环境变量org.apache.commons.logging.Log,找到则使用其定义的Log实现类
3)        查看classpath中是否有Log4j的包,如果发现,则自动使用Log4j作为日志实现类
4)        使用JDK自身的日志实现类(JDK1.4以后才有日志实现类)
5)        使用commons-logging自己提供的一个简单的日志实现类SimpleLog
 
commons-logging总是能找到一个日志实现类,并且尽可能找到一个"最合适"的日志实现类.
ü 可以不需要配置文件
ü 自动判断有没有Log4j包,有则自动使用之
ü 最悲观的情况下也总能保证提供一个日志实现(SimpleLog)
log4j
当我们用Logger.getLogger(loggerName);取得log时,让我们看看他是如何工作的?
1)        查找是否已定义系统环境变量log4j.configuration,找到则使用其定义的Log配置文件;否则搜索log4j.xml,如果存在,则执行4)
2)        如果不存在,搜索log4j.properties
3)        如果不存在,则使用程序默认的日志仓库
4)        如果存在,注册配置日志仓库
5)        去日志仓库中查询,如果不存在,则new一个
 
从上面我们可以看到无论是common-logging还是log4j,以及其他的日志开源系统都会提供一套默认的遍历规则,去搜索他的log记录实例。一般情况下,我们都会使用它的默认规则,但是这样的话,我们就会受制于它,比如如果有人在默认规则中改变了某一个条件。
如果我们不想使用它的默认规则,该怎么办?很简单,在log4j中是通过日志仓库来维护一套独立日志系统,只要我们直接使用它的日志仓库,写一个我们自己的日志工厂就可以了。具体如下:

  1. Public final class CustomLogFactory {
  2. //日志仓库实例
  3. Static LoggerRepository h = new Hierarchy(new RootLogger(Level.DEBUG));
  4.  
  5. Static {
  6. //配置文件定制日志仓库属性,这里面你可以做更多的文章
  7. //来定义自己的配置文件位置等等
  8. new PropertyConfigurator().configure(h, urlConfigFile);
  9. }
  10.  
  11. public static Logger getLogger(String loggerName) {
  12. return h.getLogger(loggerName);
  13. }
  14. }

这样你的日志系统完全独立了。是不是很容易?
2.2   自己的日志记录器
Log4j的logger本身提供八种级别的日志记录,如果你想让你的logger没有级别概念,或者让日志信息支持国际化,那该怎么办?
Log4j用于第三方扩展的记录日志方法API
public boolean isEnabledFor(Priority level);
public void log(String callerFQCN, Priority level, Object message, Throwable t);
扩展示例
1)        没有级别概念

  1. Public final class CustomLogger {
  2. //log4j日志记录器
  3. Logger log4j;
  4.  
  5. //自己的FQCN
  6. Private static final String FQCN = CustomLogger.class.getName();
  7.  
  8. //构造方法
  9. Private CustomLogger(String loggerName) {
  10. log4j = CustomLogFactory.getLogger(loggerName);
  11. }
  12.  
  13. //工厂方法
  14. Public static CustomLogger getLogger(String loggerName) {
  15. Return new CustomLogger(loggerName);
  16. }
  17.  
  18. Public Boolean isEntryEnabled() {
  19. Return log4j. isEnabledFor(Level.INFO);
  20. }
  21. //程序入口日志
  22. //你完全可以在message上做些手脚,加上一些自己的特殊的日志信息
  23. //当然你也可以后面介绍的Appender的时候加
  24. Public void entry(String message, Throwable t) {
  25. Log4j.log(FQCN , Level.INFO, message, t);
  26. }
  27.  
  28. Public Boolean isExitEnabled() {
  29. Return log4j. isEnabledFor(Level.INFO);
  30. }
  31. //程序结束日志
  32. Public void exit(String message, Throwable t) {
  33. Log4j.log(FQCN , Level.INFO, message, t);
  34. }
  35. }

2)        支持国际化
主要是在message取得上做点事情。就像上面那个Public void entry(String message, Throwable t);如果你把API改为Public void entry(String messageID, Throwable t);实现改为:


  1. Public void entry(String messageID, Throwable t) {
  2. Log4j.log(FQCN , Level.INFO, getMessage(messageID), t);
  3. }
  4. //取得相应的国际化信息
  5. Private String getMessage(String messageID) {
  6. Return InternalResource.getLogMessage(messageID, Locale.getDefault());
  7. }

 2.3   自己的日志输出地
Log4j本身提供了大量的默认Appender实现,已经能很好的解决大部分应用,但是有时候我们还是有一些自己的需要,比如只有在输出到控制台Appender时加一个运行时才能确定的固定前缀。我们可以这么写:

  1. public final class CustomConsoleAppender extends org.apache.log4j.ConsoleAppender {
  2.  
  3. @Override
  4. protected void subAppend(LoggingEvent event) {
  5. StringBuffer msgBuf = new StringBuffer();
  6.  
  7. //固定前缀
  8. msgBuf.append('[');
  9. msgBuf.append(Manager.current ().getName());
  10. msgBuf.append(']');
  11.  
  12. //根据配置文件中的格式化信息格式化event
  13. msgBuf.append(this.layout.format(event));
  14. this.qw.write(msgBuf.toString());
  15. ……
  16. }
  17. }

也就是我们想定制某一种日志输出地,直接继承相应的log4j中的类似的Appender,然后复写相应的方法即可。最后在配置文件中使用自己定义的Appender类就行了。
2.4   自己的日志过滤器
Log4j的日志输出控制级别比较中有一个缺陷就是只能大于某个级别时才能输出,没有小于某个级别时才输出的控制,当然一般不会用到,如果有类似这样的需求是不是就做不到了呢?不是的,这时候你可以使用日志过滤器来实现。比如有这样一个需求,输出到控制台的日志信息,当级别小于WARN时,用System.out;当大于等于WARN时,用System.err。
过滤器定义如下:

  1. public final class CustomWarnLevelFilter extends org.apache.log4j.spi.Filter {
  2. @Override
  3. public int decide(LoggingEvent event) {
  4. //大于等于WARN的日志不允许输出
  5. if(event.getLevel().toInt() >= Level. WARN.toInt()) {
  6. return DENY;
  7. } else {
  8. return ACCEPT;
  9. }
  10. }
  11. }

 
配置文件中可以这样配置:

  1. <appender class="com.primeton.ext.common.log.EOSConsoleAppender" name="CONSOLE_OUT">
  2. <param name="Target" value="System.out"/>
  3. <layout class="org.apache.log4j.PatternLayout">
  4. <param name="ConversionPattern" value="[%d{yyyy-MM-dd HH:mm:ss,SSS}][%p][%C][Line:%L] %m%n"/>
  5. </layout>
  6. <filter class=" CustomWarnLevelFilter "/>
  7. </appender>
  8.  
  9. <appender class="com.primeton.ext.common.log.EOSConsoleAppender" name="CONSOLE_ERR">
  10. <param name="Target" value="System.err"/>
  11. <param name="Threshold" value="WARN"/>
  12. <layout class="org.apache.log4j.PatternLayout">
  13. <param name="ConversionPattern" value="[%d{yyyy-MM-dd HH:mm:ss,SSS}][%p][%C][Line:%L] %m%n"/>
  14. </layout>
  15. </appender>
  16.  
  17. <root>
  18. <level value="DEBUG"/>
  19. <appender-ref ref="CONSOLE_OUT"/>
  20. <appender-ref ref="CONSOLE_ERR"/>
  21. </root>

因为log4j设计的灵活性,所以当然也有其他的方式来达到这个目的。
 
上面简单介绍了对log4j常用的扩展方式,你还可以扩展像layout、errorhandler等等,这里不再赘述。
3       性能影响因素Log4j
Log4j一直被人所诟病的也就是它的性能问题了,所以在这里简要的说明一下使用log4j的注意事项:
1)        如果你的日志实例的名称不是经常变化,请将它定义为static变量。
2)        如果你的message构造很复杂,那么在构造之前,请先使用isXXXEnabled()判断。
3)        配置文件中格式控制如果不需要,尽量不要输出类名和行号。
4)        根据log4j的级别判断控制流程,在配置文件中尽可能配置的比较细致。
Logger.info(message)的比较流程:
                                      i.              比较日志仓库的threashold
                                    ii.              与自身的有效Level(没有则是父亲的)比较
                                  iii.              与Appender的threashold比较
                                   iv.              询问Appender的filter
                                     v.              Layout格式化
                                   vi.              记录日志
5)        如果Appender是文件类型,请不要把文件大小设的太小。至少设为10MB(<param name="MaxFileSize" value="10MB"/>)。
6)        如果不是即时调试程序,把你的级别设定为高级别,最好是threshold=INFO之上。
7)        如果不是想即时看到日志信息,你也可以把Appender的ImmediateFlush 设为false(<param name="ImmediateFlush" value="false"/>)。
 
4       附录
4.1   Log4j基本概念
Log4j体系结构图
日志仓库(或者容器)loggerRepository:跟一个配置文件相对应,顾名思义,里面存放着日志实例。
1)        属性:threashold(阈值)
2)        默认实现:org.apache.log4j.Hierarchy
3)        日志系统━日志仓库━配置文件
 
日志记录器:根日志和子日志(继承的概念,用“.”来区分,日志名称的重要性)
1)        的概念(用于第三方封装和继承)FQCN
2)        属性:level, appender, additivity(是否继承父日志的appender)
 
Level:日志级别
 
Appender:日志输出端
属性:threashold(阈值),filter,Layout,Filter,ErrorHandler
Layout:布局(输出格式)
Filter:过滤器
ErrorHandler:错误处理器

 

原创粉丝点击