分享
 
 
 

即时日志记录:使用 Jabber 增强 log4j 的功能

王朝java/jsp·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

学习如何用自己的 appender 来扩展 log4j 框架

级别:高级

Ruth Zamorano (ruth.zamorano@orange-soft.com),软件架构师,Orange Soft

Rafael Luque (rafael.luque@orange-soft.com), CTO,Orange Soft

2003年 9 月

日志记录不仅是开发和测试周期中的一个重要元素——提供关键调试信息,而且对于系统已部署到生产环境之后调试错误也是很有用的——提供修复错误所需的准确上下文信息。在本文中,Orange Soft 公司(这是一家专业从事面向对象技术、服务器端Java 平台和 Web 可访问性的西班牙公司)的共同创办人 Ruth Zamorano 和 Rafael Luque 阐述了如何利用 log4j 的扩展能力,使得分布式 Java 应用程序能够通过即时消息传送(instant messaging,IM)来监视。

不管您编写多少设计良好的测试用例,即使是最小的应用程序也会在部署到生产环境之后隐藏着一个或多个错误。虽然测试驱动的开发和 QA 手段可以提高代码质量 并增强对应用程序的信心,但是当某个系统失败时,开发人员和系统管理员需要了解系统的相关执行上下文信息。有了适当的信息,他们就能确定问题的本质并快速解决问题,从而节省时间和金钱。

监视分布式应用程序要求能够对远程资源进行日志记录——通常是一台中央日志服务器或者系统管理员的计算机。log4j 环境提供一组适用于远程日志记录的 appender,比如 SocketAppender、JMSAppender 和 SMTPAppender。在本文中,我们将向您展示一种新的远程类(remote-class)appender:IMAppender。

让我们首先简要回顾一下 log4j ,然后再深入研究 appender。自然地,理解 appender 的最好方式就是试着编写一个 appender,因此我们将在最后一节实现一个例子 IM(即时消息传送)appender,以说明 AppenderSkeleton 类的工作原理。

读者应该熟悉 log4j 框架。关于 log4j 的更多信息,请参见本文后面的 参考资料

log4j 概述log4j 框架是用 Java 语言编写的事实上的标准日志记录框架。作为 Jakarta 项目的一部分,它在 Apache 软件许可证(Apache Software License)下分发,Apache 软件许可证是由开放源代码促进会(Open Source Initiative ,OSI)认证的一种流行的开放源代码许可证。log4j 环境是完全可配置的,或者通过编程方式完成,或者通过属性中的配置文件或者 XML 格式的配置文件完成。此外,它还允许开发人员无需修改源代码就可以选择性地筛选出日志记录请求。

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

logger(日志记录器):控制要启用或禁用哪些日志记录语句。可以对日志记录器指定如下级别:ALL、DEBUG、INFO、WARN、ERROR, FATA或OFF。

layout(布局):根据用户的愿望格式化日志记录请求。

appender:向目的地发送格式化的输出。

理解 appender

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

ConsoleAppender

FileAppender

SMTPAppender

JDBCAppender

JMSAppender

NTEventLogAppender

SyslogAppender

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

log4j 最主要的特性之一就是它的灵活性。遗憾的是,没有多少现存文档说明了如何编写自己的 appender。学习编写 appender 的方式之一就是分析可用的源代码,然后尝试推断 appender 是如何工作的——本文将帮助 您完成这个任务。

揭开面纱

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

图 1. AppenderSkeleton 的 UML 类图

下面让我们研究一下 AppenderSkeleton 类所实现的 Appender 接口的方法。如清单1所示,Appender 接口中的几乎所有方法都是 setter 方法和 getter 方法:

清单1. 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 管理格式输出——也就是说,它返回 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。

如清单2所示的 doAppend() 方法遵循“四人组模板方法(Gang of Four Template Method )”设计模式(参见 参考资料)。这个方法提供了一个算法框架,它把某些步骤推迟到子类中来实现。

清单2:doAppend() 方法的实际源代码

public synchronized void doAppend (LoggingEvent event) {

if (closed) { // step 1

LogLog.error("Attempted to append to closed appender [" + name + "].");

return;

}

if ( !isAsSevereAsThreshold (event.level) ) { // step 2

return;

}

Filter f = this.headFilter; // step 3

FILTER_LOOP:

while ( f != null) {

switch ( f .decide(event) ) {

case Filter.DENY: return;

case Filter.ACCEPT: break FILTER_LOOP;

case Filter.NEUTRAL: f = f.next;

}

}

this.append(event); // step 4

}

如清单2所示,该算法:

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

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

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

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

我们已经介绍了 AppenderSkeleton 从 Appender 继承来的方法和属性。下面让我们看看“为什么”AppenderSkeleton 要实现 OptionHandler 接口。OptionHandler 仅包含一个方法:activateOptions()。这个方法在对属性调用 setter 方法之后由一个配置器类调用。有些属性彼此依赖,因此它们在全部加载完成之前是无法激活的,比如在 activateOptions() 方法中就是这样。这个方法是开发人员在 appender 变为激活和就绪之前用来执行任何必要任务的机制。

除了上面提到的所有方法,让我们再回头观察一下图1。注意 AppenderSkeleton 提供了一个新的抽象方法(append() 方法)和一个新的 JavaBean 属性(threshold)。threshold 属性由 appender 用来过滤日志记录请求,只有超过阈值的请求才会得到处理。我们在谈到 doAppend() 方法之前就提到了 append() 方法。它是自定义 appender 必须实现的一个抽象方法,因为框架在 doAppend() 方法内调用 append() 方法。append()方法是框架的钩子(hook)之一。

现在我们已经看到了 AppenderSkeleton 类中的所有可用方法,下面让我们看看幕后发生的事情。图2演示了 log4j 中的一个 appender 对象的 生命周期。

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

现在我们已经回顾了与建立自己的 appender 相关的概念,下面让我们考虑一个包括真实例子appender 的完整案例研究。

编写自定义 appender 的诀窍

扩展 AppenderSkeleton 抽象类。

指定您的 appender 是否需要 layout。

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

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

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

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

编写基于 IM 的 appender

本文给出的代码说明了如何扩展 log4j 框架以集成 IM 特性。它被设计来使得 log4j 相容的应用程序能够把输出记录到 IM 网络上。IM appender 实际上充当一个自定义的 客户机。然而,它不是把 System.out、文件或者 TCP 套接字当作底层输出设备,而是把 IM 网络当作底层输出设备。

为了提供 IM 支持,我们不需要在开发特定解决方案时完全重新开始。相反,我们将利用一个我们认为是该类别中最好的工具:Jabber。Jabber 是一种用于即时消息传送和展示的基于 XML 的开放协议,它由 Jabber 社区开发,非 营利性的 Jabber 软件基金会(Jabber Software Foundation)对它提供技术支持。

我们之所以选择 Jabber 而没有选择其他 IM 系统,是因为 Jabber 提供了广泛的好处,包括它的:

开放性质: 不像其他的专有系统,Jabber 的规范和源代码是可以免费获得的,从而允许任何人无成本地创建 Jabber 实现。

简单性: Jabber 使用基于 XML 的简单协议作为它的标准数据格式,并且遵循大多数人都理解的客户机/服务器 架构。

与其他 IM 系统的互操作性:Jabber 传输模块(transport module)使得 Jabber 用户访问诸如 AIM、Yahoo!Messager 和 ICQ 等其他即时消息传送系统成为可能。

资源敏感:Jabber 对多客户机访问提供明确的支持。同一个用户可以通过不同的客户机(或者说资源)同时地连接到Jabber 服务器,消息将被恰当地路由到可用的最佳资源。

为什么要把日志记录到 IM 网络?

日志记录是开发人员必须养成的良好编码习惯,就像编写单元测试、处理异常或者编写 Javadoc 注释一样。插入到代码中明确位置的日志记录语句起着审核工具的功能,提供了关于应用程序内部状态的有用信息。与主流意见相反,我们认为在许多情况下,将日志语句保留在生产代码中是方便的。如果 您担心计算成本,就必须考虑从应用程序中删除日志记录功能所带来的少量性能提升是否值得。此外,log4j 的灵活性允许您声明式地控制日志记录行为。您可以建立严格的日志记录策略来降低日志的累赘性并改进性能。

图3显示了 IMAppender 的一个使用场景:一个配置为使用 IMAppender 的 log4j 应用程序记录 它的被包装为 IM 消息的调试数据。即时消息通过 Jabber 公司网络被路由到系统管理员的Jabber 地址(注意,公开可用的 Jabber 服务器对生产应用可能不足够可靠)。因而,无论何时系统管理员需要检查应用程序的状态,他们只需加载最喜欢的 Jabber 客户机,然后连接到Jabber 服务器。如图3所示,管理员可以通过不同的设备来访问。他可以使用办公室的 PC 来登录服务器,或者当他离开办公桌时,可以使用运行在手持设备上的 Jabber 客户机来检查消息。

图 3. IMAppender 使用场景

但是为什么需要 IM appender 呢?因为向 IM 服务器发送消息将允许您通过自由选择的工具(比如Jabber客户机)来更容易地监视应用程序行为。

IMAppender提供了多个优点:

获得实时通知——我们称之为“即时日志记录”。

支持一对一(聊天)和一对多(小组聊天)模式。

Jabber 不只是用于台式计算机。用于诸如 PDA 和移动电话等无线设备的客户机也正在开发之中。

要进入或退出应用程序正在向其转发日志数据的聊天室很容易。而要订阅和取销订阅由 SMTPAppender 发送的电子邮件则很困难。

通过安全套接字层(SSL)上的隧道来保证安全很容易。当然,您可以加密电子邮件,但是 SSL上 的 Jabber 既方便又快捷。

进阶

IMAppender 模仿随 log4j 一起分发的 SMTPAppender 的日志记录策略。IMAppender 把日志记录事件存储在一个内部循环缓冲区(cyclic buffer)中,并且仅当所接收到的日志记录请求触发了某个用户指定的条件时,才把这些事件作为即时消息来发送。或者,用户也可以提供一个触发事件鉴别器类(triggering event evaluator class)。然而在默认情况下,消息传送是由指定为 ERROR 或者更高级别的事件所触发的。

每个消息中传送的日志记录事件的数量是由缓冲区的大小决定的。循环缓冲区仅保留最后的 bufferSize 个日志记录事件,当它装满时就会溢出并丢弃较旧的事件。

为了连接到 Jabber 服务器,IMAppender 需要依赖 Jive Software 公司的 Smack API。Smack 是一个开放源代码的高级库,它处理与 Jabber 服务器通信的协议细节。这样, 您无需任何特别的 Jabber 或者 XML 专业经验就能理解代码。

IMAppender 的属性总结在表 1中:

表 1. IMAppender 属性

属性

说明

类型

是否必需

host

服务器的主机名称

String

port

Jabber服务器的端口号

int

否,默认为 5222

username

应用程序的Jabber帐户用户名

String

password

应用程序的Jabber帐户密码

String

recipient

接收方的Jabber地址。Jabber地址也称为Jabber ID,它在一个@字符后面指定用户的Jabber 域,就像电子邮件地址一样

这个属性可以保存任何聊天地址或者聊天室地址。例如,您可以指定这样的聊天地址:sysadmin@company.com;或者 您可能希望向 sysadmin@company.com 小组聊天服务器上名为"java-apps"的某个聊天小组发送日志记录消息(例如,java-apps@conference.company.com)

String

chatroom

接受一个布尔值。如果为 true,recipient 值将被接受为小组聊天地址。如果要设置这个选项,还应该设置 nickname 选项。默认情况下,recipient 值被解释为一个聊天地址

boolean

否,默认为 false

nickname

仅当设置了chatroom 属性时才会考虑这个属性。否则,它将被忽略

用户可以选择 appender 使用的任意小组聊天昵称来加入小组聊天。昵称不一定要和 Jabber用户名有关

String

SSL

用于保护与 Jabber 服务器的连接

boolean

否,默认为false

bufferSize

可以保留在循环缓冲区中的日志记录事件的最大数量

int

否,默认为16

evaluatorClass

这个属性的值被当作一个类的完全限定名称的字符串表示形式,该类实现了 org.apache.log4j.spi. TriggeringEventEvaluator 接口(换句话说,也就是一个包含自定义触发逻辑的类,它覆盖了默认的触发逻辑)。如果没有指定这个选项,IMAppender 将使用 DefaultEvaluator 类的一个实例,这个类根据被指定为 ERROR 或更高级别的事件触发响应

String

否,默认为 DefaultEvaluator

现在让我们进一步观察代码。IMAppender 类遵循清单3所示的结构:

清单 3. IMAppender 类的总体结构

package com.orangesoft.logging;

import org.apache.log4j.AppenderSkeleton;

import org.apache.log4j.spi.LoggingEvent;

import org.apache.log4j.spi.TriggeringEventEvaluator;

public class IMAppender extends AppenderSkeleton {

private String host;

private int port = 5222;

private String username;

private String password;

private String recipient;

private boolean chatroom = false;

private String nickname;

private boolean SSL = false;

private int bufferSize = 16;

protected TriggeringEventEvaluator evaluator;

// Set/Get methods for properties

public void setHost(String host) {

this.host = host;

}

public String getHost() {

return this.host;

}

...other set/get methods...

// AppenderSkeleton callback methods

public boolean requiresLayout() { ... }

public void activateOptions() { ... }

public void append(LoggingEvent event) { ... }

public synchronized void close() { ... }

}

请注意关于我们的 appender 的如下几个方面:

IMAppender 类扩展 org.apache.log4j.AppenderSkeleton,这是所有自定义 appender 都必须要做的。IMAppender 从 AppenderSkeleton 继承诸如 appender 阈值和自定义过滤之类的公共功能。

我们的 appender 的第一部分很简单。每个 appender 都有字段和 set/get 方法。属性和方法签名遵守 JavaBeans 命名约定。因而,log4j 能够通过反射来分析 appender,透明地处理 appender 配置。为节省篇幅,上述代码片断仅显示了 setHost() 和 getHost() 方法。

为了完成我们的 appender,我们必须实现 log4j 框架调用来管理我们的 appender 的回调方法:requiresLayout()、activateOptions()、append() 和 close()。

log4j 框架调用 requiresLayout() 方法来判断自定义 appender 是否需要 layout。注意 ,有些appender 使用内置格式或者根本就不格式化事件,因此它们不需要 Layout 对象。IMAppender 需要 layout,因而该方法返回 true,如 清单4所示:

清单 4. requiresLayout() 方法

public boolean requiresLayout() {

return true;

}

注意,AppenderSkeleton 实现了 org.apache.log4j.spi.OptionHandler 接口(参见 图 1 )。AppenderSkeleton 把这个接口的单个方法 activateOptions() 实现为一个空方法。我们的 IMAppender 需要这个方法是由于其属性之间的相互依赖性。例如,与 Jabber 服务器的连接依赖 Host、Port 和 SSL 属性,因此 IMAppender 在这三个属性被初始化之前无法建立连接。log4j 框架调用 activateOptions() 方法来通知 appender 所有属性都已设置就绪。

IMAppender.activateOptions() 方法激活指定的属性(比如 Jabber 主机、端口、bufferSize,等等),所采取的方式是实例化依赖这些属性值的更高级对象,如清单5所示:

清单 5. 只有在调用 activateOptions() 方法之后,属性才会被激活且变得有效

protected org.apache.log4j.helpers.CyclicBuffer cb;

protected org.jivesoftware.smack.XMPPConnection con;

protected org.jivesoftware.smack.Chat chat;

protected org.jivesoftware.smack.GroupChat groupchat;

public void activateOptions() {

try {

cb = new CyclicBuffer(bufferSize);

if (SSL) {

con = new SSLXMPPConnection(host, port);

} else {

con = new XMPPConnection(host, port);

}

con.login(username, password);

if (chatroom) {

groupchat = con.createGroupChat(recipient);

groupchat.join(nickname != null ? nickname : username);

} else {

chat = con.createChat(recipient);

}

} catch (Exception e) {

errorHandler.error("Error while activating options for appender

named [" + name + "]",

e, ErrorCode.GENERIC_FAILURE);

}

}

activateOptions() 方法完成以下任务:

建立 bufferSize 个事件的最大循环缓冲区。我们使用了 org.apache.log4j.helpers.CyclicBuffer 的一个实例,org.apache.log4j.helpers.CyclicBuffer 是 log4j 附带的一个辅助类,它提供了缓冲区的逻辑。

Smack 的 XMPPConnection 类创建了一个到 XMPP (Jabber) 服务器的连接,这个服务器是通过 host 和 port 属性来指定的。为了创建一个 SSL 连接,我们要使用 SSLXMPPConnection 子类。

大多数服务器都要求您在执行其他任务之前首先登录,因此我们使用由 username 和 password 属性所定义的 Jabber 帐户来登录,同时调用 XMPPConnection.login() 方法。

在登录之后,我们创建一个 Chat 或者 GroupChat 对象,具体视 chatroom 值而定。

在 activateOptions() 方法返回之后,appender 就准备好处理日志记录请求了。如 清单6所示,由 AppenderSkeleton.doAppend() 调用的 append() 方法将执行大多数实际的日志附加工作。

清单 6. append() 执行实际的输出操作

public void append(LoggingEvent event) {

// check pre-conditions

if (!checkEntryConditions()) {

return;

}

cb.add(event);

if (evaluator.isTriggeringEvent(event)) {

sendBuffer();

}

}

protected boolean checkEntryConditions() {

if ((this.chat == null) && (this.groupchat == null)) {

errorHandler.error("Chat object not configured");

return false;

}

if (this.layout == null) {

errorHandler.error("No layout set for appender named [" + name + "]");

return false;

}

return true;

}

append() 方法中的第一个语句判断进行附加尝试是否有意义。checkEntryConditions() 方法检查是否有可用于附加到输出的 Chat 或者 GroupChat 对象,以及是否有用于格式化传入 event 对象的 Layout 对象。如果这些前提条件得不到满足,那么 append() 将输出一条警告消息并返回,从而不会继续进行输出操作。下一个语句把事件添加到循环缓冲区实例 cb。然后,if 语句把日志记录事件提交给 evaluator,这是一个 TriggeringEventEvaluator 实例。如果 evaluator 返回 true,这意味着该事件与触发条件匹配,sendBuffer() 就会被调用。

清单7显示了 sendBuffer() 方法的代码:

清单 7. sendBuffer()方法

protected void sendBuffer() {

try {

StringBuffer buf = new StringBuffer();

int len = cb.length();

for (int i = 0; i < len; i++) {

LoggingEvent event = cb.get();

buf.append(layout.format(event));

// if layout doesn't handle exceptions, the appender has

// to do it

if (layout.ignoresThrowable()) {

String[] s = event.getThrowableStrRep();

if (s != null) {

for (int j = 0; j < s.length; j++) {

buf.append(LINE_SEP);

buf.append(s[j]);

}

}

}

}

if (chatroom) {

groupchat.sendMessage(buf.toString());

} else {

chat.sendMessage(buf.toString());

}

} catch(Exception e) {

errorHandler.error("Could not send message

in IMAppender [" + name + "]",

e, ErrorCode.GENERIC_FAILURE);

}

}

sendBuffer() 方法把缓冲区的内容作为IM消息来发送。此方法逐项遍历保留在缓冲区中的事件,同时调用 layout 对象的 format() 方法来格式化每个事件。事件的字符串表示形式被附加到 StringBuffer 对象。最后,sendBuffer() 调用 chat 或者 groupchat 对象的 sendMessage() 方法,把消息发送出去。

请注意以下几点:

AppenderSkeleton.doAppend() 方法(它调用 append())是经过同步的,因此 sendBuffer() 已经拥有 appender 的监视器。这使得我们不必在 cb 上执行同步操作。

异常提供了极其有用的信息。由于这个原因,如果指定的 layout 忽略了包含在 LoggingEvent 对象中的可抛出对象,自定义 appender 的开发人员必须输出包括在事件中的异常信息。如果 layout 忽略了可抛出的对象,那么 layout 的 ignoresThrowable() 方法应该返回 true,并且 sendBuffer() 可以使用 LoggingEvent.getThrowableStrRep() 方法来检索包含在该事件中的可抛出信息的 String[] 表示形式。

下载源代码

执行这个示例所必需的全部JAR文件(instantlogging.jar、smack-1.1.0.jar 和 log4j-1.2.8.jar)包括在本文的源代码的 lib/ 目录下。相应的 zip 档案文件可从 参考资料 下载。

把全部内容组合起来

下面将通过展示 IMAppender 的实际工作效果来结束本文的讨论。我们将使用一个相当简单的名为 com.orangesoft.logging.example.EventCounter 的应用程序,如 清单8所示。这个示例应用程序在命令行接受两个参数。第一个参数是一个整数,对应于要产生的日志记录事件的数量。第二个参数必须是以属性的格式提供的一个 log4j 配置文件名。这个应用程序总是以 ERROR 事件结束,该事件将触发一次 IM 消息传送。

清单 8. EventCounter 示例应用程序

package com.orangesoft.logging.examples;

import org.apache.log4j.Logger;

import org.apache.log4j.PropertyConfigurator;

/**

* Generates the number of logging events indicated by the first

* argument value. The application ends with an ERROR level event

* to trigger the IMAppender action.

*/

public class EventCounter {

private static Logger logger = Logger.getLogger(EventCounter.class);

public static void main(String args[]) {

int numEvents = Integer.parseInt(args[0]);

String log4jConfigFile = args[1];

PropertyConfigurator.configure(log4jConfigFile);

for (int i = 1; i <= numEvents; i++) {

logger.info("Event #" + i);

}

logger.error("This error event triggers the delivery",

new Exception("This is a mock exception"));

}

}

我们可以使用类似清单9所示的配置文件:

清单 9. 示例 IMAppender 配置文件

log4j.rootLogger = ALL,im

log4j.appender.im = com.orangesoft.logging.IMAppender

log4j.appender.im.host = JABBER_SERVER (e.g. jabber.org)

log4j.appender.im.username = APP_JABBER_ACCOUNT_USERNAME

log4j.appender.im.password = APP_JABBER_ACCOUNT_PASSWORD

log4j.appender.im.recipient = YOUR_JABBER_ADDRESS (e.g. foobar@jabber.org)

log4j.appender.im.layout=org.apache.log4j.PatternLayout

log4j.appender.im.layout.ConversionPattern = %n%r [%-5p] %M:%L - %m

上面的配置文件脚本把 IMAppender 添加到根日志记录器(root logger),这样所接收到的每个日志记录请求都将被分派到我们的 appender。

在试验这个示例应用程序之前,请确保将 host、username、password 和 recipient 属性设置为 您所在环境中的适当值。下面的命令将运行 EventCounter 应用程序:

java com.orangesoft.logging.examples.EventCounter 100

eventcounter.properties

当运行时,EventCounter 将根据 eventcounter.properties 所设置的策略记录 100 个事件。然后一个 IM 消息将从接收方的屏幕上弹出来。图4、5、6 显示了不同平台上的 Jabber 客户机接收到的结果消息:

图 4. Windows (Rhymbox)上的 Jabber 客户机接收到的消息的屏幕快照

图 5. Linux (PSI)上的 Jabber 客户机接收到的消息的屏幕快照

图 6. Pocket PC (imov)上的 Jabber 客户机接收到的消息的屏幕快照

注意 EventCounter 产生了 100 个事件。然而,由于 IMAppender 缓冲区的默认大小为 16,接收方应该收到仅包含最后 16 个事件的 IM 消息。可以看到,包含在最后一个事件(消息和堆栈跟踪)中的异常信息已经被正确地传送了。

这个例子应用程序只展示了 IMAppender 的一个非常小的用途,因此继续探索它吧,您会找到很多乐趣的!

结束语

log4j 网络 appender,SocketAppender、 JMSAppender 和 SMTPAppender 已经提供了监视 Java 分布式应用程序的机制。然而,多个因素使得 IM 成为用于实时远程日志记录的合适技术。在本文中,我们介绍了通过自定义 appender 来扩展 log4j 的基础知识,并看到了一个基本 IMAppender 的逐步实现过程。许多开发人员和系统管理员都可以从 appender 的使用中获益。

参考资料

下载 IMAppender 类的 源代码 、例子应用程序和必需的库。 您可以根据需要随便使用和扩展这些源代码。

log4j项目首页 获得最新的 log4j 版本 ——包括完整的源代码、类文件和文档。欲了解更多信息,请在 log4j官方文档页 查看文章和演示材料。

如果您的开发工作需要超出本文档范围的内容,可以参考 Ceki Gülcü 编著的一本优秀参考书 log4j完全参考手册(QOS.ch, 2003),该书详细介绍了 log4j 的基本特性和高级特性。

developerWorks Web services 专区提供了 LogKit 作为“每周内容”(component of the week)” (2001年8月)。LogKit 是 Jakarta 的 Avalon项目 的日志记录组件。

Sun 已经完成了一个名为 JSR 47 的社区过程(community process),它定义了 Java 平台的日志记录 API。JSR 47 API和 log4j 在体系结构层次上相当类似,不过 log4j 具有许多 JSR 47 所没有的特性。

在“Merlin的魔力:异常和日志记录”(developerWorks,2001年12月)中,John Zukowski展示了新的 JDK 1.4 日志记录 API 是如何工作的。

Thomas E. Davis 撰写的 JavaWorld 文章 “Logging on to Internet Relay Chat (IRC)” 介绍了一种允许应用程序把输出写到 IRC 的简单工具。

Gerhard Poul 的文章“Jabber” (developerWorks,2002年5月)说明了 Jabber 如何适合于当今的电子商务 基础设施。

Smack 是 Jive Software 公司提供的一个开放源代码库,用于与 Jabber 服务器通信以执行即时消息传送和聊天。

Iain Shigeoka 的文章 用 Java 实现即时消息传送 (Manning, 2002) 深入分析了各种 Jabber 协议。

“模板方法(Template Method)设计模式”出自 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 所著的 设计模式 一书(Addison-Wesley出版, 1995年),这四位作者也被称为“四人组(Gang of Four,GoF)。

也可以在线阅读 模板方法设计模式 的相关报道。

developerWorks Java技术专区 可以找到数百篇关于 Java 编程的各个方面的文章。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有