无论在何时部署插件,都只需要修改日志配置文件和日志过滤条件,或者修改其输出,而不需要修改任何代码。更好的一点是,如果日志被禁用,那么所有的语句都不会影响性能,因为性能是 Log4j 设计的主要考虑因素之一。因此您可以在任何必要的地方采用这种记录器的方法。
如何实现
对于 com.tools.logging 的使用,我们就谈这么多;现在让我们来看一下其实现。
首先来看一下类 PluginLogManager。每个插件都有一个日志管理器。该管理器包含一个 hierarchy 对象,以及定制 appenders 所需的数据,如清单 2 所示。该对象并非直接源自于 Hierarchy 对象,因此不便将它暴露给最终用户。它在实现方面提供了更多的自由。构造函数使用默认的 DEBUG 级别创建一个 hierarchy 对象,然后使用提供的属性对其进行配置。它还可以简单地使用 xml 属性;只有对于对 Xerces 插件添加从属性并使用 DOMConfigurator 而不是 PropertyConfigurator 才是必要的。这部分内容留给读者作为练习。
清单 2. PluginLogManager 构造函数
public PluginLogManager(Plugin plugin,Properties properties) {
this.log = plugin.getLog();
this.stateLocation = plugin.getStateLocation();
this.hierarchy = new Hierarchy(new RootCategory(Level.DEBUG));
this.hierarchy.addHierarchyEventListener(new PluginEventListener());
new PropertyConfigurator().doConfigure(properties,this.hierarchy);
LoggingPlugin.getDefault().addLogManager(this);
}
注意 PluginLogManager
内部类是如何实现 org.apache.log4j.spi.HierarchyEventListener
的。这是向定制的 appender 传递必要信息的一种解决方案。在已经对 appender 进行实例化和完整配置并准备添加它时,会调用 addAppenderEvent()
方法,如清单 3 所示:
private class PluginEventListener implements HierarchyEventListener {
public void addAppenderEvent(Category cat, Appender appender) {
if (appender instanceof PluginLogAppender) {
((PluginLogAppender)appender).setLog(log);
}
if (appender instanceof PluginFileAppender) {
((PluginFileAppender)appender).setStateLocation(stateLocation);
}
}
public void removeAppenderEvent(Category cat, Appender appender) {
}
}
为了更好地理解 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
方法。
public void append(LoggingEvent event) {
if (this.layout == null) {
this.errorHandler.error("Missing layout for appender "
this.name,null,ErrorCode.MISSING_LAYOUT);
return;
}
String text = this.layout.format(event);
Throwable thrown = null;
if (this.layout.ignoresThrowable()) {
ThrowableInformation info = event.getThrowableInformation();
if (info != null)
thrown = info.getThrowable();
}
Level level = event.getLevel();
int severity = Status.OK;
if (level.toInt() >= Level.ERROR_INT)
severity = Status.ERROR;
else
if (level.toInt() >= Level.WARN_INT)
severity = Status.WARNING;
else
if (level.toInt() >= Level.DEBUG_INT)
severity = Status.INFO;
this.pluginLog.log(new Status(severity,
this.pluginLog.getBundle().getSymbolicName(),
level.toInt(),text,thrown));
}
LoggingPlugin
类维护了 PluginLogManagers 的一个列表。这是必需的,这样,在插件停止时,就可以关闭该插件的所有层次结构,并正确删除 appender 和记录器,如清单 5 所示。