自定义log4j的Appender

编写自定义appender 的 步骤

1. 扩展 AppenderSkeleton 抽象类。如果是通过流方式实现读写数据的话,自定一定appender可以从WriterAppender继承,这样只需要把我们自己的 OutputStream连接到WriterAppender.qw上就可以了。更方便快捷。

2. 指定您的 appender 是否需要 layout。这个由requiresLayout()方法确定。

3. 如果某些属性必须同时激活,则应该在 activateOptions() 方法内完成。该方法上在Appender的构造函数之后被调用的。

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

5. 可选地指定要使用的默认 ErrorHandler 对象。系统默认为OnlyOnceErrorHandler,它发送出第一个错误的消息并忽略其余的所有错误,错误消息将输出到 System.err。

6. 编写 append() 方法的代码。这个方法负责附加日志记录事件,并在错误发生时负责调用错误处理程序。我们主要的日志记录等处理任务实际上是在该append()方法内完成的。

 

请看程序。
由测试程序:Log4jTest.java、
自定义的Appdender:UDPAppender
配置文件:log4j.properties
三个源文件组成。
测试程序:Log4jTest.java

写道
package zieckey.study.log4j;

import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

public class Log4jTest {

// 获取日志记录器
static Logger logger = Logger.getLogger(Log4jTest.class.getName());

Log4jTest() {
// 读取使用Java属性文件编写的配置文件
logger.debug("Read config file.");
PropertyConfigurator.configure("src/log4j.properties");
}

public static void printLog() {
logger.debug("Log4jTest-->>debug");
logger.info("Log4jTest-->>info");
logger.warn("Log4jTest-->>warn");
logger.error("Log4jTest-->>error");
}

public static void main(String[] args) {
Log4jTest.printLog();
new Log4jTest();
}
}

 自定义的Appdender:UDPAppender

写道
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;

import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.helpers.LogLog;
import org.apache.log4j.spi.LoggingEvent;

public class UDPAppender extends AppenderSkeleton {

static private int bufferSize = 8 * 1024;
private byte data[];
private String remoteHost = "localhost";
private int port = 5000;
private InetAddress address = null;
private DatagramSocket dataSocket = null;
private DatagramPacket dataPacket = null;

public UDPAppender() {
// LogLog.setInternalDebugging(true);
// LogLog.setQuietMode(false);
// LogLog.debug("default constructor.");
}

private void init() {
try {
dataSocket = new DatagramSocket(this.port + 1);
address = InetAddress.getByName(remoteHost);
} catch (SocketException e) {
LogLog.debug(e.getMessage());
} catch (UnknownHostException e) {
LogLog.debug(e.getMessage());
}
data = new byte[bufferSize];
if (this.layout == null) {
LogLog.debug("The layout is not loaded... we set it.");
String pattern = "%-4r %-5p %d{yyyy-MM-dd HH:mm:ss} %c %m%n";
this.setLayout(new org.apache.log4j.PatternLayout(pattern));
}
}

@Override
protected void append(LoggingEvent event) {
try {
String msg = "UDP Appender...send data: " + this.getLayout().format(event);
data = msg.getBytes();
dataPacket = new DatagramPacket(data, data.length, address, port);
dataSocket.send(dataPacket);
} catch (SocketException se) {
se.printStackTrace();
} catch (IOException ie) {
ie.printStackTrace();
}
}

/**
* Derived appenders should override this method if option structure requires it.
*/
public void activateOptions() {
init();
}

@Override
public void close() {
if (closed) return;
if (!dataSocket.isClosed()) {
dataSocket.close();
}
closed = true;
}

@Override
public boolean requiresLayout() {
return true;
}

/**
* The <b>RemoteHost</b> option takes a string value which should be the host name of the server where a
* {@link SocketNode} is running.
*/
public void setRemoteHost(String host) {
String val = host.trim();
remoteHost = val;
}

/**
* Returns value of the <b>RemoteHost</b> option.
*/
public String getRemoteHost() {
return remoteHost;
}

/**
* The <b>Port</b> option takes a positive integer representing the port where the server is waiting for
* connections.
*/
public void setPort(int port) {
this.port = port;
}

/**
* Returns value of the <b>Port</b> option.
*/
public int getPort() {
return port;
}
}

 配置文件:log4j.properties

写道
log4j.rootLogger=DEBUG,CONSOLE,UDPAppender,LOGFILE
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%-4r %-5p %d{yyyy-MM-dd HH:mm:ss} %c %m%n
log4j.appender.UDPAppender=zieckey.study.log4j.UDPAppender
log4j.appender.UDPAppender.RemoteHost=localhost
log4j.appender.UDPAppender.Port=4561
log4j.appender.UDPAppender.layout=org.apache.log4j.PatternLayout
log4j.appender.UDPAppender.layout.ConversionPattern=%-4r %-5p %d{yyyy-MM-dd HH:mm:ss} %c %m%n
log4j.appender.LOGFILE=org.apache.log4j.RollingFileAppender
log4j.appender.LOGFILE.File=Log4jTest.log
log4j.appender.LOGFILE.MaxFileSize=20KB
log4j.appender.LOGFILE.MaxBackupIndex=1
log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout
log4j.appender.LOGFILE.layout.ConversionPattern=%-4r %-5p %d{yyyy-MM-dd HH:mm:ss} %c %m%n

 

注意配置文件应该放在src目录下。

appender 的生命周期

 

 

1. appender 实例不存在,或许框架还没有配置好。

2. 框架实例化了一个新的 appender。这发生在配置器类分析配置脚本中的一个 appender 声明的时候。配置器类调用 Class.newInstance(YourCustomAppender.class) ,这等价于动态调用 new YourCustomAppender() 。框架这样做是为了避免被硬编码为任何特定的 appender 名称;框架是通用的,适用于任何 appender。

e.g. log4j.appender.file=org.apache.log4j.RollingFileAppender

3. 框架判断 appender 是否需要 layout。如果该 appender 不需要 layout,配置器就不会尝试从配置脚本中加载 layout 信息。

在Appender接口中,有public boolean requiresLayout() 方法,如果return true;则要求layout,需在配置脚本中设置layout信息

e.g. log4j.appender.file.layout=org.apache.log4j.PatternLayout

4. Log4j 配置器调用 setter 方法。在所有属性都已设置好之后,框架就会调用这个方法。

此时对应RollingFileAppender的每个Field,需要有一个setter方法,在配置脚本中也要进行设置

e.g. log4j.appender.file.File=<project>.log

对应的在RollingFileAppender中,有

public void setFile(String file) {

    String val = file.trim();

    fileName = val;

}

其它的属性,如MaxFileSize ,也相同

5. 配置器调用 activateOptions() 方法。在所有属性都已设置好之后,框架就会调用这个方法。

OptionHandler 接口定义了activateOptions()方法,在全部属性设置好了之后,在该方法中进行必要的操作,如打开文件:

写道
if(fileName != null) {

try {

setFile(fileName, fileAppend, bufferedIO, bufferSize);

}

catch(java.io.IOException e) {

errorHandler.error("setFile("+fileName+","+fileAppend+") call failed.",

e, ErrorCode.FILE_OPEN_FAILURE);

}

} else {

//LogLog.error("File option not set for appender ["+name+"].");

LogLog.warn("File option not set for appender ["+name+"].");

LogLog.warn("Are you using FileAppender instead of ConsoleAppender?");

}

 

6. Appender 准备就绪。 此刻,框架可以调用 append() 方法来处理日志记录请求。这个方法由 AppenderSkeleton.doAppend() 方法调用。

该方法为Appender中最关键的方法,append()中可以定义日志的输出,最简单的:

protected void append(LoggingEvent event){

    //if layout is required

    System.out.println(this.getLayout().format(event));

}

7. 最后,关闭appender。 当框架即将要删除您的自定义 appender 实例时,它会调用您的 appender 的 close() 方法。 close() 是一个清理方法,意味着 您需要释放已分配的所有资源。它是一个必需的方法,并且不接受任何参数。它必须把 closed 字段设置为 true ,并在有人尝试使用关闭的 appender 时向框架发出警报。

public void close() {

        if (this.closed)

            return;

        this.closed = true;

    }

注意: 需要在主程序退出前,调用Logger.getRoot().removeAllAppender();这样才能调用Appender的close()方法.

 

编写自定义appender 的 步骤

1. 扩展 AppenderSkeleton 抽象类。

2. 指定您的 appender 是否需要 layout。

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

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

5. 可选地指定要使用的默认 ErrorHandler 对象。系统默认为OnlyOnceErrorHandler,它发送出第一个错误的消息并忽略其余的所有错误,错误消息将输出到 System.err。

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

 

log4j 环境包括三个主要组件:

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

所有的 appender 都必须扩展 org.apache.log4j.AppenderSkeleton 类,这是一个抽象类,它实现了 org.apache.log4j.Appenderorg.apache.log4j.spi.OptionHandler 接口。 AppenderSkeleton 类的 UML 类图看起来如图所示:

自定义log4j的Appender - is00hcw - is00hcw的博客

Appender 接口

package org.apache.log4j;public interface Appender {    void addFilter(Filter newFilter);                        void clearFilters() ;                                                   void close();                                                            void doAppend(LoggingEvent event);                ErrorHandler getErrorHandler();         Filter getFilter();             Layout getLayout();              String getName();           boolean requiresLayout();            void setErrorHandler(ErrorHandler errorHandler);            void setLayout(Layout layout);           void setName(String name);         }

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

  • name: Appender 是命名的实体,因此有一个针对其名称的 setter/getter。
  • layout: Appender 可以具有关联的 Layout,因此还有另一个针对 layout 的setter/getter 方法。注意我们说的是“可以”而不是“必须”。这是因为有些 appender 不需要 layout。lauout 管理格式输出――也就是说,它返回 LoggingEventString 表示形式。另一方面, JMSAppender 发送的事件是 串行化的,因此您不需要对它附加 layout。如果自定义的 appender 不需要 layout,那么 requiresLayout() 方法必须返回 false ,以避免 log4j 抱怨说丢失了 layout 信息。
  • errorHandler : 另一个 setter/getter 方法是为 ErrorHandler 而存在的。appender 可能把它们的错误处理委托给一个 ErrorHandler 对象――即 org.apache.log4j.spi 包中的一个接口。实现类有两个:OnlyOnceErrorHandlerFallbackErrorHandlerOnlyOnceErrorHandle 实现 log4j 的默认错误处理策略,它发送出第一个错误的消息并忽略其余的所有错误。错误消息将输出到 System.errFallbackErrorHandler 实现ErrorHandler 接口,以便能够指定一个辅助的 appender。如果主 appender 失败,辅助 appender 将接管工作。错误消息将输出到 System.err ,然后登录到新的辅助 appender。

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

一个 appender 可以包含多个过滤器。自定义过滤器必须扩展 org.apache.log4j.spi.Filter 抽象类。这个抽象类要求把过滤器组织为线性链。 对每个过滤器的 decide(LoggingEvent) 方法的调用要按照过滤器被添加到链中的顺序来进行。自定义过滤器基于三元逻辑。 decide() 方法必须返回 DENYNEUTRAL 或者 ACCEPT 这三个整型常量值之一。

除了 setter/getter 方法以及和过滤器相关的方法外,还有另外两个方法: close()doAppend()close() 方法释放 appender 中分配的任何资源,比如文件句柄、网络连接,等等。在编写自定义 appender 代码时,务必要实现这个方法,以便当您的 appender 关闭时,它的 closed 字段将被设置为 true

 

  • 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 时向框架发出警报。

你可能感兴趣的:(appender)