清单 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 所示:
清单 3. PluginEventListener 类
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 方法。
清单 4. PluginLogAppender 的 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));}
|