级别: 中级 |
Manoel Marques
(manoel@themsslink.com)
高级顾问, The Missing Link, Inc.
2004 年 10 月
Eclipse 中缺少像 J2SDK Logging Utilities 或 Apache 的 Log4j 这样功能丰富的可配置日志工具。在本文中,我们将学习如何为 Eclipse 插件配置并使用日志框架,该框架本身也是一个基于 Apache Log4j 的插件。为了便于您使用和扩展,本文还提供了完整的源代码。
为什么要采用日志?
良好的开发人员都知道精心设计、测试和调试的重要性。虽然 Eclipse 可以帮助开发人员实现这些任务,但是它怎样处理日志呢?很多开发人员相信对于良好的软件开发实践来说,日志是不可或缺的一部分。如果您曾经修正过他人部署过的程序,您无疑也会同意这一点。幸运的是,日志对于性能的影响很小,大部分情况下甚至根本不会对性能产生任何影响,而且由于日志工具非常简单易用,因此学习曲线也非常平滑。因此,对于现有的优秀工具,我们没有理由不在应用程序中添加日志功能。
可以使用的工具
如果您正在编写一个 Eclipse 插件,那么您可以使用 org.eclipse.core.runtime.ILog 所提供的服务,它可以通过 Plug 类的 getLog()
方法进行访问。只需要使用正确的信息创建一个 org.eclipse.core.runtime.Status 的实例,并调用 ILog 的 log()
方法即可。
这个日志对象可以接收多个日志监听器实例。Eclipse 添加了两个监听器:
- 一个监听器向 "Error Log(错误日志)" 视图中写入日志。
- 一个监听器向位于 “${workspace}/.metadata/.log" 的日志文件中写入日志。
您也可以创建自己的日志监听器,只需实现 org.eclipse.core.runtime.ILogListener 接口并使用 addLogListener()
方法将其添加到日志对象中即可。这样,每个日志事件都可以调用这个类的 logging()
方法。
虽然所有的内容都非常简单,但是这种方法存在一些问题。如果您希望修改一个已部署好的插件目标,那么应该如何处理?或者说要如何控制记录下来的日志信息的数量?还有,这种实现可能会对性能造成影响,因为它总是要向所有的监听器发送日志事件。这就是为什么我们通常只在极端的情况(例如错误条件)中才会看到要记录日志的原因。
另一方面,还有两个专门用于日志的杰出的工具。一个来自 Java 2 SDK 1.4 的 java.util.logging 包;另外一个来自 Apache,名为 Log4j。
这两个工具都采用了日志对象的层次结构的概念,都可以将日志事件发送到任意数目的处理程序(Handler,在 Log4j 中称为 Appender)中,它代表了发送给格式化程序(Formatter,在 Log4j 中称为 Layout)进行格式化的消息。这两个工具都可以通过属性文件进行配置。 Log4j 还可以使用 xml 文件进行配置。
记录器可以有一个名称并与某一级别相关联。记录器可以继承父母的设置(级别,处理程序)。名为“org”的记录器会自动成为另外一个名为“org.eclipse” 的记录器的父母;因此不管您在配置文件中怎样对“org”进行设置,这些设置都可以被“org.eclipse”记录器继承。
我更喜欢哪一个工具?这两个工具我都曾经用过,不过我比较喜欢 Log4j。只有在非常简单的程序中我才使用 java.util.logging,我并不想在这样的程序中添加 log4j.jar。关于这两个工具的详细介绍,请参阅 Java 文档和 Apache 的站点(请参阅参考资料中的链接)。
一种改进的日志
如果存在改进 Eclipse 日志体验的方法,那不是很棒吗?但这样做有两个问题:
- 缺少外部配置文件。
- 性能问题,同时还有缺乏对日志行为进行细粒度控制。
给出这个难题之后,我开始考虑将日志工具集成到 Eclipse 中的方法。我可以使用的第一个选择是 java.util.logging,原因非常简单:在 JSDK1.4 发行版中已经包含了这个包。
我想采用一个编辑器,通过配置文件对日志行为进行定制,从而允许将日志事件发送到任何可用的处理程序中。我计划另外创建两个处理程序:一个负责将日志事件发送到“Error Log”视图中,另外一个将日志写入插件所在的位置:“${workspace}/.metadata/.plugins/${plugin.name}"。
所有的内容都将包含在一个日志管理器插件(Plug-in Log Manager)中。您只能将其加入插件从属关系中,并从中获得日志对象。
然而,根据我的经验,我不推荐使用 java.util.logging 来实现这项功能。因为实现的代码将很长,而且只能保留一个 LogManager
实例;它使用系统类装载程序来达到这个目的。这样,所有的用户只有一个层次结构,您会失去隔离性。因此,如果很多应用程序都在使用这个记录器,那么它们将共享设置,一个应用程序的记录器实例可以继承其他应用程序记录器的设置。
既然如此,为什么我们不对 LogManager 进行扩充,并自己实现一个记录器呢?这种方法的问题是 LogManager 实例使用了系统类的装载程序从配置文件中对类进行实例化。这种插件的优点之一是通过使用不同的类装载程序提供隔离性。如果您的日志管理程序需要隔离性,那么由于架构的限制, java.util.logging 可能不适合您的要求。
另一方面,Log4j 已经证明是非常有用的。不管您相信与否,Log4j 的记录器的层次结构保留在一个称为 Hierarchy 的对象中。因此,您可以为每个插件都创建一个层次结构,这样问题就解决了。您还可以创建一个定制的 appender (处理程序)将事件发送给 "Error Log" 视图,再创建一个将事件发送到插件所在的位置。这样生活就变得美好起来了。
现在让我们回顾一下整个过程是如何实现的,我们从插件编辑器的角度入手,创建插件,并将 com.tools.logging 添加到从属类型列表中,然后创建一个 Log4j 配置文件。对 PluginLogManager 进行实例化,并使用配置文件对其进行配置。由于这个过程只需要做一次,因此您只需要在启动插件时执行这项操作即可。对于日志语句,只需像在 Log4j 中那样使用它即可。 清单 1 给出了一个例子:
清单 1. TestPlugin 插件类中 PluginLogManager 的配置
|
无论在何时部署插件,都只需要修改日志配置文件和日志过滤条件,或者修改其输出,而不需要修改任何代码。更好的一点是,如果日志被禁用,那么所有的语句都不会影响性能,因为性能是 Log4j 设计的主要考虑因素之一。因此您可以在任何必要的地方采用这种记录器的方法。
如何实现
对于 com.tools.logging 的使用,我们就谈这么多;现在让我们来看一下其实现。
首先来看一下类 PluginLogManager。每个插件都有一个日志管理器。该管理器包含一个 hierarchy 对象,以及定制 appenders 所需的数据,如清单 2 所示。该对象并非直接源自于 Hierarchy 对象,因此不便将它暴露给最终用户。它在实现方面提供了更多的自由。构造函数使用默认的 DEBUG 级别创建一个 hierarchy 对象,然后使用提供的属性对其进行配置。它还可以简单地使用 xml 属性;只有对于对 Xerces 插件添加从属性并使用 DOMConfigurator 而不是 PropertyConfigurator 才是必要的。这部分内容留给读者作为练习。
清单 2. PluginLogManager 构造函数
|
注意 PluginLogManager
内部类是如何实现 org.apache.log4j.spi.HierarchyEventListener
的。这是向定制的 appender 传递必要信息的一种解决方案。在已经对 appender 进行实例化和完整配置并准备添加它时,会调用 addAppenderEvent()
方法,如清单 3 所示:
|
为了更好地理解 appender 的生命周期以及一些决定,可以使用 UML 顺序图(UML Sequence Diagram)。图 1 显示了创建和配置 PluginFileAppender 实例的事件顺序。
Figure 1. PluginFileAppender 配置顺序图
对于这个 appender 来说,我们对 org.apache.log4j.RollingFileAppender
进行了扩展。这不但允许您自由对文件进行操作,而且还提供了很多有用特性,例如文件大小的上限;当达到文件上限时,日志自动重叠写入另一个文件。
通过选择对 RollingFileAppender 进行扩展,您还需要对其行为进行正确处理。当 Log4j 创建 appender 之后,就会调用“setter”方法从配置文件中对其属性进行初始化,然后调用 activateOptions()
方法让附加程序完成未完成的任何初始化操作。在进行这项操作时,RollingFileAppender
实例会调用 setFile()
,它将打开日志文件并准备好写入日志。只有此时 Log4j 才会通知 PluginEventListener
实例。
显然,在有机会设置插件位置前,您不能打开文件。因此当调用 activateOptions()
时,如果还没有位置信息,就会被标记为未决的;当最后设置位置信息时,会再次调用该方法,此时 appender 就准备好,可以使用了。
另外一个 appender PluginLogAppender
的生命周期相同,不过由于它并没有对现有的 appender 进行扩展,因此您不必担心初始化的问题。appender 在 addAppenderEvent
方法被调用之前不会启动。Log4j 文档对如何编写定制 appender 进行了详细的讨论。清单 4 给出了 append
方法。
|
LoggingPlugin
类维护了 PluginLogManagers 的一个列表。这是必需的,这样,在插件停止时,就可以关闭该插件的所有层次结构,并正确删除 appender 和记录器,如清单 5 所示。
|
插入 PluginLogManager
类的内容有很多。有时您所从属的插件,特别是那些从属于 workbench 的插件,可能引发异常。这些异常通常都会被 Eclipse 记录到日志中。允许将从属插件(dependent plug-in)插入日志框架中,这非常有用。在触发异常时,Eclipse 要记录的所有日志都会被放入日志框架,它与其他记录器共享配置文件。这种方法非常有用,因为这样可以将所有的内容都集中在一个位置上,并可以保留一个事实的历史样本,从而有助于修正应用程序的问题。
这可以通过实现 org.eclipse.core.runtime.ILogListener
并将其添加到从属插件的 ILog 实例中实现。基本上,您只需要将其与 Eclipse 的日志相关联。然后,这种实现就可以将所有的请求都重定向到一个使用您选择的名字(通常是一个插件标识符)创建的记录器中。然后您可以通过相同的配置文件对输出结果进行配置;只需指定记录器的名字、设置过滤条件、添加 appender 即可。该类如清单 6 所示:
|
整个框架是在一个插件项目 com.tools.logging 中实现的。为了显示它是如何工作的,我创建了两个插件:
- HelloPlugin 是从一个项目模板中构建出来的,它显示一个消息对话框,其中显示 "Hello, Eclipse world"。
- TestPluginLog 作为一个与 HelloPlugin 的一个从属插件添加的,因此它可以被勾挂在相同的日志级别中。它有一个方法
dummyCall()
,可以使用 Eclipse API 添加一条假消息,然后它会被重定向到 HelloPlugin 的日志中。
其他插件的从属类型都已经设置好了,例如 org.eclipse.ui 或 org.eclipse.core.runtime。
为了显示 logger.properties 配置文件的强大功能,在创建该文件时我非常小心。正如您在清单 7 中看到的一样,我们定义了两个 appender: appender A1 是一个 PluginFileAppender
类,它被分配给根记录器。其他记录器都是从这个根记录器继承而来,都将使用这个 appender。因此,所有的日志,包括来自 TestPluginLog 插件的日志,都被写入一个位于插件所在位置的文件中。
|
另外一个 appender 是 A2,它是一个 PluginLogAppender
类,只能将它添加到记录器 "helloplugin" 中,因此 TestPluginLog 没有使用它。否则,在 "Error View" 窗口中 "TestPluginLog" 就会有两项:一个来自于 Eclipse,另外一个来自于 com.tools.logging。您可以自己做个实验,然后就会明白我的意思了。只需将 A2 添加到 log4j.rootCategory 中并删除 log4j.logger.helloplugin 所在的那个行即可。
清单 8 显示了在点击 "sample menu" 并显示消息框之后 ${workspace}/.metadata/.plugins/HelloPlugin/helloplugin.log 的内容。注意 TestPluginLog Eclipse 日志是如何写入最后一行中的。通过将您自己的日志和 Eclipse 插件日志写入一个输出文件中,可以保留日志事件的序列。
清单 8. helloplugin.log
|
结束语
本文介绍了两种改进 Eclipse 日志功能的方法。一种方法是在插件中使用 com.tools.logging,这样就可以使用 Log4j 中所有有用的特性;如果您愿意的话,它依将是 Eclipse 日志框架的一部分。另外一种方法与一个插件相关,该插件并不了解 Log4j,但即时只使用 Eclipse 日志 API,也可以对其日志输出进行配置。
实际上,您并不需要使用 com.tools.logging。现在,您可以展开示例代码,并将其作为一个单独的 jar 文件加入您自己的插件中。当然,不要忘记了 Log4j 的 jar 文件。
插件是使用新的 OSGI 创建的。所有的代码都是使用 Eclipse 3.0 Release Candidate 1、Sun Java 2 SDK 1.4.2 和 Log4j 1.2.8 进行开发的,并在这些环境中进行了测试。在可以下载的代码中,不包括 log4j-1.2.8.jar 文件。如果您要下载这些代码,应该从 Apache 的 Log4j 中获得这个 jar 文件,并在 com.tools.logging 项目和 com.tools.logging_1.0.0 插件目录中包含该文件。
- 您可以参阅本文在 developerWorks 全球站点上的 英文原文。
- 在 eclipse.org 站点上可以找到插件的文档、源代码和最新的 Eclipse 版本。
- 在 Sun Java 2 SDK 1.4.2 站点上可以找到有关 java.util.logging 的 Java 运行时文档。
- 可以在 Apache 的 Log4j Logging Services 站点上找到您所需要的关于 Log4j 的所有资料。
- 在 developerWorks 的开源项目专区中,可以找到更多 针对 Eclipse 用户的文章。请同时参阅 alphaWorks 中最新的 Eclipse 技术下载。
- 在 Developer Bookstore 的 Open source 区中购买 有关 open source 主题的打折书籍。在这儿您可以找到几本 有关 Eclipse 的书籍。
- 利用 developerWorks 工具包订阅的最新 IBM 工具和中间件来开发并测试您的应用程序:您可以获得 IBM 的 WebSphere® 软件,比如基于 Eclipse 的 WebSphere Studio Application Developer for Linux 和 WebSphere Studio Application Developer for Windows,以及 DB2®、Lotus®、Rational® 和 Tivoli®,还有一个使用这些软件的 12 个月的许可证,所有花费都低得超乎您的想象。
关于作者 Manoel Marques 是一位软件开发人员和技术顾问,他在这些领域已经工作了 15 年;在此期间,他在巴西和美国从事了很多项目和研究工作。他毕业于巴西里约热内卢 Pontificia Universidade Catolica 的计算机科学系,并获得了科学硕士学位。您可以通过 manoel@themsslink.com 与 Manoel 联系。 |
到页首 | |
描述 | 文件类型 | 文件大小 | 下载方式 |
loggingplugins.zip | zip | 16 KB | FTP |
loggingsourcecode.zip | zip | 31 KB | FTP |