002 使用Appender扩展logger框架

  这个地方,在看公司的源代码的时候,写的知识点;

  现在再看,竟然不是太懂,重新写一份新的文档,外加示例说明。

一:说明

1.log4j 环境的三个主要组件:

  • logger(日志记录器)控制要启用或禁用哪些日志记录语句。可以对日志记录器指定如下级别: ALL 、DEBUG 、 INFO 、 WARN 、 ERROR , FATA或 OFF 。 
  • layout(布局):根据用户的愿望格式化日志记录请求。 
  • appender:向目的地发送格式化的输出。

2.理解 appender

  log4j 框架允许向任何日志记录器附加多个 appender。

  可以在任何时候对某个日子记录器添加(或删除)appender。附随 log4j 分发的 appender 有多个,包括:

  • ConsoleAppender
  • FileAppender
  • SMTPAppender
  • JDBCAppender
  • JMSAppender
  • NTEventLogAppender
  • SyslogAppender

  也可以创建自己的自定义 appender。

3.工作原理

   所有的 appender 都必须扩展 org.apache.log4j.AppenderSkeleton 类。

  这是一个抽象类,它实现了 org.apache.log4j.Appender 和 org.apache.log4j.spi.OptionHandler 接口。

  

  这是AppenderSkeleton的UML类图。

二:Appender接口

1.Appender接口:

 1 import org.apache.log4j.spi.ErrorHandler;
 2 import org.apache.log4j.spi.Filter;
 3 import org.apache.log4j.spi.LoggingEvent;
 4 
 5 public interface Appender {
 6     void addFilter(Filter var1);
 7 
 8     Filter getFilter();
 9 
10     void clearFilters();
11 
12     void close();
13 
14     void doAppend(LoggingEvent var1);
15 
16     String getName();
17 
18     void setErrorHandler(ErrorHandler var1);
19 
20     ErrorHandler getErrorHandler();
21 
22     void setLayout(Layout var1);
23 
24     Layout getLayout();
25 
26     void setName(String var1);
27 
28     boolean requiresLayout();
29 }

  

2.对上文的注解说明

  这些方法处理 appender 的如下属性:

  name:  Appender 是命名的实体,因此有一个针对其名称的 setter/getter。
  layout: Appender 可以具有关联的 Layout,因此还有另一个针对 layout 的setter/getter 方法。

    注意我们说的是“可以”而不是“必须”。这是因为有些 appender 不需要 layout。

    lauout 管理格式输出――也就是说,它返回LoggingEvent 的 String 表示形式。

    另一方面, JMSAppender 发送的事件是 串行化的,因此您不需要对它附加 layout。如果自定义的 appender 不需要 layout,那么 requiresLayout() 方法必须返回 false ,以避免 log4j 抱怨说丢失了 layout 信息。
  errorHandler : 另一个 setter/getter 方法是为 ErrorHandler 而存在的。

    appender 可能把它们的错误处理委托给一个 ErrorHandler 对象――即 org.apache.log4j.spi 包中的一个接口。

    实现类有两个: OnlyOnceErrorHandler 和 FallbackErrorHandler 。

     OnlyOnceErrorHandle 实现 log4j 的默认错误处理策略,它发送出第一个错误的消息并忽略其余的所有错误。错误消息将输出到 System.err 。

    FallbackErrorHandler 实现 ErrorHandler 接口,以便能够指定一个辅助的 appender。如果主 appender 失败,辅助 appender 将接管工作。错误消息将输出到 System.err ,然后登录到新的辅助 appender。

  还有管理过滤器的其他方法(比如 ddFilter() 、 clearFilters() 和 getFilter() 方法 )。尽管 log4j 具有过滤日志请求的多种内置方法(比如知识库范围级、日志记录器级和 appender 阈值级),但它使用自定义过滤器方法的能力也是非常强大的。

    一个 appender 可以包含多个过滤器。

    自定义过滤器必须扩展 org.apache.log4j.spi.Filter 抽象类。这个抽象类要求把过滤器组织为线性链。

    对每个过滤器的 decide(LoggingEvent) 方法的调用要按照过滤器被添加到链中的顺序来进行。

    自定义过滤器基于三元逻辑。 decide() 方法必须返回 DENY 、 NEUTRAL 或者 ACCEPT 这三个整型常量值之一。

  除了 setter/getter 方法以及和过滤器相关的方法外,还有另外两个方法: close() 和 doAppend() 。 close() 方法释放 appender 中分配的任何资源,比如文件句柄、网络连接,等等。

    在编写自定义 appender 代码时,务必要实现这个方法,以便当您的 appender 关闭时,它的 closed 字段将被设置为 true 。

3.doAppend方法的源代码

 1 public synchronized void doAppend (LoggingEvent event) {
 2   if (closed) {
 3     // step 1
 4     LogLog.error("Attempted to append to closed appender [" + name + "].");
 5     return;
 6   } if ( !isAsSevereAsThreshold (event.level) ) {
 7     // step 2
 8     return;
 9   }
10   Filter f = this.headFilter;
11   // step 3
12   FILTER_LOOP:
13   while ( f != null) {
14     switch ( f .decide(event) ) {
15       case Filter.DENY: return;
16       case Filter.ACCEPT: break FILTER_LOOP;
17       case Filter.NEUTRAL: f = f.next;
18     }
19   }
20   this.append(event);
21   // step 4
22 }

   doAppend() 方法之前就提到了 append() 方法。

  它是自定义 appender 必须实现的一个抽象方法,因为框架在 doAppend() 方法内调用 append() 方法。 append() 方法是框架的钩子(hook)之一。

4.doAppender算法框架

  检查 appender 是否关闭。附加关闭的 appender 是一个编程错误。

  检查正在记录日志的事件是否处于 appender 的阈值之下。

  检查是否有过滤器附加到 appender,如果有,则拒绝请求。

  调用 appender 的 append() 方法。这个步骤被委托给每个子类。

三:OptionHandler

1.OptionHandler 接口说明

  OptionHandler 仅包含一个方法: activateOptions() 。

  这个方法在对属性调用 setter 方法之后由一个配置器类调用。

  有些属性彼此依赖,因此它们在全部加载完成之前是无法激活的,比如在 activateOptions() 方法中就是这样。

  这个方法是开发人员在 appender 变为激活和就绪之前用来执行任何必要任务的机制。

2.OptionHandler 接口

1 package org.apache.log4j.spi;
2 
3 public interface OptionHandler {
4     void activateOptions();
5 }

  

3.对上文的注解说明

  OptionHandler 仅包含一个方法: activateOptions() 。

  这个方法在对属性调用 setter 方法之后由一个配置器类调用。

  有些属性彼此依赖,因此它们在全部加载完成之前是无法激活的,比如在 activateOptions() 方法中就是这样。

  这个方法是开发人员在 appender 变为激活和就绪之前用来执行任何必要任务的机制。

四:理论总结

1.Appender生命周期

  • appender 实例不存在。或许框架还没有配置好。 
  • 框架实例化了一个新的 appender。这发生在配置器类分析配置脚本中的一个 appender 声明的时候。配置器类调用 Class.newInstance(YourCustomAppender.class) ,这等价于动态调用 new YourCustomAppender() 。框架这样做是为了避免被硬编码为任何特定的 appender 名称;框架是通用的,适用于任何 appender。 
  • 框架判断 appender 是否需要 layout。如果该 appender 不需要 layout,配置器就不会尝试从配置脚本中加载 layout 信息。 
  • Log4j 配置器调用 setter 方法。在所有属性都已设置好之后,框架就会调用这个方法。程序员可以在这里激活必须同时激活的属性。 
  • 配置器调用 activateOptions() 方法。在所有属性都已设置好之后,框架就会调用这个方法。程序员可以在这里激活必须同时激活的属性。 
  • Appender 准备就绪。 此刻,框架可以调用 append() 方法来处理日志记录请求。这个方法由 AppenderSkeleton.doAppend() 方法调用。 
  • 最后,关闭appender。 当框架即将要删除您的自定义 appender 实例时,它会调用您的 appender 的 close() 方法。 close() 是一个清理方法,意味着 您需要释放已分配的所有资源。它是一个必需的方法,并且不接受任何参数。它必须把 closed 字段设置为 true ,并在有人尝试使用关闭的 appender 时向框架发出警报。

2.生命周期图

  

3.书写Appender的步骤

  扩展 AppenderSkeleton 抽象类。 

  指定您的 appender 是否需要 layout。

  如果某些属性必须同时激活,则应该在 activateOptions() 方法内完成。 

  实现 close() 方法。它必须把 closed 字段的值设置为 true 。记得释放所有资源。 

  可选地指定要使用的默认 ErrorHandler 对象。 

  编写 append() 方法的代码。这个方法负责附加日志记录事件,并在错误发生时负责调用错误处理程序。

4.log4j执行顺序

  

五:小示例

1.程序结构

  感觉使用maven管理jar比较方便,这里就使用maven项目

  

2.pom

  一直在加包,导致现在也不清楚需要多少包,以后这里再研究。

 1 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 2   <modelVersion>4.0.0</modelVersion>
 3   <groupId>appender</groupId>
 4   <artifactId>jun.it</artifactId>
 5   <version>0.0.1-SNAPSHOT</version>
 6   <name>AppenderDemo</name>
 7   <dependencies>
 8       <!-- https://mvnrepository.com/artifact/log4j/log4j -->
 9     <dependency>
10         <groupId>log4j</groupId>
11         <artifactId>log4j</artifactId>
12         <version>1.2.17</version>
13     </dependency>
14     <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
15     <dependency>
16         <groupId>org.apache.logging.log4j</groupId>
17         <artifactId>log4j-core</artifactId>
18         <version>2.10.0</version>
19     </dependency>
20     <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api -->
21     <dependency>
22         <groupId>org.apache.logging.log4j</groupId>
23         <artifactId>log4j-api</artifactId>
24         <version>2.10.0</version>
25     </dependency>
26     <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j-impl -->
27     <dependency>
28         <groupId>org.apache.logging.log4j</groupId>
29         <artifactId>log4j-slf4j-impl</artifactId>
30         <version>2.10.0</version>
31     </dependency>
32     <!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
33     <dependency>
34         <groupId>commons-logging</groupId>
35         <artifactId>commons-logging</artifactId>
36         <version>1.2</version>
37     </dependency>
38 
39     
40         
41         
42   
43   </dependencies>
44 </project>

3.HelloAppender

 1 package com.jun.it;
 2 
 3 import org.apache.log4j.AppenderSkeleton;
 4 import org.apache.log4j.spi.LoggingEvent;
 5 
 6 public class HelloAppender extends AppenderSkeleton {
 7     // ==============参数==============
 8     private String account;
 9 
10     public String getAccount() {
11         return account;
12     }
13 
14     public void setAccount(String account) {
15         this.account = account;
16     }
17     // ================================
18     
19     public void close() {
20 
21     }
22 
23     public boolean requiresLayout() {
24         return false;
25     }
26 
27     @Override
28     protected void append(LoggingEvent event) {
29         System.out.println("Hello, " + account + " : " + event.getMessage());
30     }
31 
32 }

4.测试类

 1 package com.jun.it;
 2 
 3 import org.apache.commons.logging.Log;
 4 import org.apache.commons.logging.LogFactory;
 5 
 6 public class TestAppenderDemo {
 7 
 8     public static void main(String[] args) {
 9         Log log = LogFactory.getLog("hello");  
10         log.info("I am ready."); 
11 
12     }
13 
14 }

5.log4j.properties

1 log4j.rootLogger=INFO,hello
2 log4j.appender.hello=com.jun.it.HelloAppender
3 log4j.appender.hello.account=world
4 log4j.appender.hello.Encoding=UTF-8
5 log4j.appender.hello.Threshold=DEBUG
6 log4j.appender.hello.DatePattern=yyyy-MM-dd'.log'

6.效果

  

原文地址:https://www.cnblogs.com/juncaoit/p/7412690.html