45fan.com - 路饭网

搜索: 您的位置主页 > 电脑频道 > 电脑教程 > 阅读资讯:怎么样为Eclipse插件添加日志框架?

怎么样为Eclipse插件添加日志框架?

2016-09-02 10:18:42 来源:www.45fan.com 【

怎么样为Eclipse插件添加日志框架?

作者:ManoelM… 文章来源:IBMdeveloperWorks

Eclipse 中缺少像 J2SDK Logging Utilities 或 Apache 的 Log4j 这样功能丰富的可配置日志工具。在本文中,我们将学习如何为 Eclipse 插件配置并使用日志框架,该框架本身也是一个基于 Apache Log4j 的插件。为了便于您使用和扩展,本文还提供了完整的源代码。

为什么要采用日志?
良好的开发人员都知道精心设计、测试和调试的重要性。虽然 Eclipse 可以帮助开发人员实现这些任务,但是它怎样处理日志呢?很多开发人员相信对于良好的软件开发实践来说,日志是不可或缺的一部分。如果您曾经修正过他人部署过的程序,您无疑也会同意这一点。幸运的是,日志对于性能的影响很小,大部分情况下甚至根本不会对性能产生任何影响,而且由于日志工具非常简单易用,因此学习曲线也非常平滑。因此,对于现有的优秀工具,我们没有理由不在应用程序中添加日志功能。

可以使用的工具
如果您正在编写一个 Eclipse 插件,那么您可以使用 org.eclipse.core.runtime.ILog 所提供的服务,它可以通过 Plug 类的 getLog() 方法进行访问。只需要使用正确的信息创建一个 org.eclipse.core.runtime.Status 的实例,并调用 ILoglog() 方法即可。

这个日志对象可以接收多个日志监听器实例。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 的配置



private static final String LOG_PROPERTIES_FILE = "logger.properties";

public void start(BundleContext context) throws Exception {
  super.start(context);
  configure();
}

private void configure() {
  try {
   URL url = getBundle().getEntry("/" + LOG_PROPERTIES_FILE);
   InputStream propertiesInputStream = url.openStream();
   if (propertiesInputStream != null) {
     Properties props = new Properties();
     props.load(propertiesInputStream);
     propertiesInputStream.close();
     this.logManager = new PluginLogManager(this, props);
     this.logManager.hookPlugin(
     TestPlugin.getDefault().getBundle().getSymbolicName(),
     TestPlugin.getDefault().getLog()); 
   }
  } 
  catch (Exception e) {
   String message = "Error while initializing log properties." +
            e.getMessage();
   IStatus status = new Status(IStatus.ERROR,
   getDefault().getBundle().getSymbolicName(),
   IStatus.ERROR, message, e);
   getLog().log(status);
   throw new RuntimeException(
      "Error while initializing log properties.",e);
  }     
}

无论在何时部署插件,都只需要修改日志配置文件和日志过滤条件,或者修改其输出,而不需要修改任何代码。更好的一点是,如果日志被禁用,那么所有的语句都不会影响性能,因为性能是 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 所示:

清单 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 配置顺序图
怎么样为Eclipse插件添加日志框架?

对于这个 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));
}

LoggingPlugin 类维护了 PluginLogManagers 的一个列表。这是必需的,这样,在插件停止时,就可以关闭该插件的所有层次结构,并正确删除 appender 和记录器,如清单 5 所示。

清单 5. LoggingPlugin 类处理日志管理器



private ArrayList logManagers = new ArrayList(); 

public void stop(BundleContext context) throws Exception {
  synchronized (this.logManagers) {
   Iterator it = this.logManagers.iterator();
   while (it.hasNext()) {
     PluginLogManager logManager = (PluginLogManager) it.next();
     logManager.internalShutdown(); 
   }
   this.logManagers.clear(); 
  }
  super.stop(context);
}

void addLogManager(PluginLogManager logManager) {
  synchronized (this.logManagers) {
   if (logManager != null)
     this.logManagers.add(logManager); 
  }
}

void removeLogManager(PluginLogManager logManager) {
  synchronized (this.logManagers) {
   if (logManager != null)
     this.logManagers.remove(logManager); 
  }
}

插入 PluginLogManager 类的内容有很多。有时您所从属的插件,特别是那些从属于 workbench 的插件,可能引发异常。这些异常通常都会被 Eclipse 记录到日志中。允许将从属插件(dependent plug-in)插入日志框架中,这非常有用。在触发异常时,Eclipse 要记录的所有日志都会被放入日志框架,它与其他记录器共享配置文件。这种方法非常有用,因为这样可以将所有的内容都集中在一个位置上,并可以保留一个事实的历史样本,从而有助于修正应用程序的问题。

这可以通过实现 org.eclipse.core.runtime.ILogListener 并将其添加到从属插件的 ILog 实例中实现。基本上,您只需要将其与 Eclipse 的日志相关联。然后,这种实现就可以将所有的请求都重定向到一个使用您选择的名字(通常是一个插件标识符)创建的记录器中。然后您可以通过相同的配置文件对输出结果进行配置;只需指定记录器的名字、设置过滤条件、添加 appender 即可。该类如清单 6 所示:

清单 6. PluginLogListener 类



class PluginLogListener implements ILogListener {

  private ILog log;
  private Logger logger;

  PluginLogListener(ILog log,Logger logger) {
   this.log = log;
   this.logger = logger;
   log.addLogListener(this);
  }

  void dispose() {
   if (this.log != null) {
     this.log.removeLogListener(this);
     this.log = null;
     this.logger = null;
   } 
  }

  public void logging(IStatus status, String plugin) {
   if (null == this.logger || null == status) 
     return;

   int severity = status.getSeverity();
   Level level = Level.DEBUG; 
   if (severity == Status.ERROR)
     level = Level.ERROR;
   else
   if (severity == Status.WARNING)
     level = Level.WARN;
   else
   if (severity == Status.INFO)
     level = Level.INFO;
   else
   if (severity == Status.CANCEL)
     level = Level.FATAL;

   plugin = formatText(plugin);
   String statusPlugin = formatText(status.getPlugin());
   String statusMessage = formatText(status.getMessage());
   StringBuffer message = new StringBuffer();
   if (plugin != null) {
     message.append(plugin);
     message.append(" - ");
   }  
   if (statusPlugin != null && 
      (plugin == null || !statusPlugin.equals(plugin))) {
     message.append(statusPlugin);
     message.append(" - ");
   }
   message.append(status.getCode());
   if (statusMessage != null) {
     message.append(" - ");
     message.append(statusMessage);
   } 
   this.logger.log(level,message.toString(),status.getException());
  }
  
  static private String formatText(String text) {
   if (text != null) {
     text = text.trim();
     if (text.length() == 0) return null;
   } 
   return text;
  }
}

整个框架是在一个插件项目 com.tools.logging 中实现的。为了显示它是如何工作的,我创建了两个插件:

  1. HelloPlugin是从一个项目模板中构建出来的,它显示一个消息对话框,其中显示 "Hello, Eclipse world"。
  2. TestPluginLog 作为一个与 HelloPlugin 的一个从属插件添加的,因此它可以被勾挂在相同的日志级别中。它有一个方法 dummyCall() ,可以使用 Eclipse API 添加一条假消息,然后它会被重定向到 HelloPlugin 的日志中。

其他插件的从属类型都已经设置好了,例如 org.eclipse.ui 或 org.eclipse.core.runtime。

为了显示 logger.properties 配置文件的强大功能,在创建该文件时我非常小心。正如您在清单 7 中看到的一样,我们定义了两个 appender: appender A1 是一个 PluginFileAppender 类,它被分配给根记录器。其他记录器都是从这个根记录器继承而来,都将使用这个 appender。因此,所有的日志,包括来自 TestPluginLog 插件的日志,都被写入一个位于插件所在位置的文件中。

清单 7. HelloPlugin 项目中的 Logger.properties 文件



log4j.rootCategory=, A1

# A1 is set to be a PluginFileAppender

log4j.appender.A1=com.tools.logging.PluginFileAppender
log4j.appender.A1.File=helloplugin.log
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%p %t %c - %m%n

# A2 is set to be a PluginLogAppender

log4j.appender.A2=com.tools.logging.PluginLogAppender
log4j.appender.A2.layout=org.apache.log4j.PatternLayout
log4j.appender.A2.layout.ConversionPattern=%p %t %c - %m%n

# add appender A2 to helloplugin level only

log4j.logger.helloplugin=, A2

另外一个 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



INFO main helloplugin.actions.SampleAction - starting constructor.
INFO main helloplugin.actions.SampleAction - ending constructor.
WARN main helloplugin.actions.SampleAction - init
WARN main helloplugin.actions.SampleAction - run method
WARN main TestPluginLog - TestPluginLog - 0 - Logging using the Eclipse API.

结束语
本文介绍了两种改进 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 的书籍。

下载


怎么样为Eclipse插件添加日志框架?
Name 怎么样为Eclipse插件添加日志框架? Size 怎么样为Eclipse插件添加日志框架? Download method 怎么样为Eclipse插件添加日志框架?
loggingplugins.zip 怎么样为Eclipse插件添加日志框架? 16 KB 怎么样为Eclipse插件添加日志框架? FTP 怎么样为Eclipse插件添加日志框架?
怎么样为Eclipse插件添加日志框架? 怎么样为Eclipse插件添加日志框架? 怎么样为Eclipse插件添加日志框架? 怎么样为Eclipse插件添加日志框架? 怎么样为Eclipse插件添加日志框架? 怎么样为Eclipse插件添加日志框架? 怎么样为Eclipse插件添加日志框架? 怎么样为Eclipse插件添加日志框架? 怎么样为Eclipse插件添加日志框架? 怎么样为Eclipse插件添加日志框架? 怎么样为Eclipse插件添加日志框架? 怎么样为Eclipse插件添加日志框架?
loggingsourcecode.zip 怎么样为Eclipse插件添加日志框架? 31 KB 怎么样为Eclipse插件添加日志框架? FTP 怎么样为Eclipse插件添加日志框架?
怎么样为Eclipse插件添加日志框架? 怎么样为Eclipse插件添加日志框架? 怎么样为Eclipse插件添加日志框架? 怎么样为Eclipse插件添加日志框架? 怎么样为Eclipse插件添加日志框架? 怎么样为Eclipse插件添加日志框架? 怎么样为Eclipse插件添加日志框架? 怎么样为Eclipse插件添加日志框架? 怎么样为Eclipse插件添加日志框架? 怎么样为Eclipse插件添加日志框架? 怎么样为Eclipse插件添加日志框架? 怎么样为Eclipse插件添加日志框架?
怎么样为Eclipse插件添加日志框架?
怎么样为Eclipse插件添加日志框架?关于下载方法的信息
怎么样为Eclipse插件添加日志框架?
关于作者
怎么样为Eclipse插件添加日志框架?Manoel Marques 是一位软件开发人员和技术顾问,他在这些领域已经工作了 15 年;在此期间,他在巴西和美国从事了很多项目和研究工作。他毕业于巴西里约热内卢 Pontificia Universidade Catolica 的计算机科学系,并获得了科学硕士学位。您可以通过 manoel@themsslink.com与 Manoel 联系。
 

本文地址:http://www.45fan.com/dnjc/71230.html
Tags: 插件 Eclipse 日志
编辑:路饭网
关于我们 | 联系我们 | 友情链接 | 网站地图 | Sitemap | App | 返回顶部