繁体   English   中英

使用一个事件来记录一个好主意?

[英]Is using an event for logging a good idea?

我正在研究能够加载各种(自制)插件的程序。

这些插件需要有一种方法来向主机程序发送通知。

目前我有一个接口作为插件的基础。 接口定义了每当我需要记录某些内容时触发的事件

界面如下所示:

public delegate void LogEventHandler(object sender, LogEventArgs e);
public interface IWatcherPluginBase
{
    event LogEventHandler WriteLog;

    string Name { get; }
    string Description { get; }
    string Contact { get; }
    Version Version { get; }
    PluginState Status { get; }
    string StatusDescription { get; }
    void Start();
    void Stop();
}

在插件中,我使用以下代码来触发事件

if (WriteLog != null)
    WriteLog(this, new LogEventArgs("Started", MessageLevel.Info));

我的主程序添加一个事件处理程序

plugin.WriteLog += new LogEventHandler(plugin_WriteLog);

您知道其他(可能更好)的方法来实现日志记录吗?

您可以考虑在命名空间System.DiagnosticsDebugDebuggerTraceEventLog )中使用内置类。

这样,您可以将标准调试和跟踪输出重定向到VS控制台(调试时)或文件(运行发布版本时)。

是的 - 我会看到以这种方式使用事件作为一个好主意 - 替代方案(使用您最喜欢的日志框架)会将某人绑定到日志框架中,而实际上他们可能已经使用了自己的日志框架。

System.Diagnostics名称空间( DebugTrace等...)中使用内置类将是一个更轻量级的替代方案,但是这些日志目标不如其他功能更全面的日志框架(如log4net)灵活性更低,并且还要求在编译时定义一个标志(分别为Trace或Debug) - 这些方法是否合适取决于您预期这些消息的使用方式。

我建议的唯一改进是将事件分成插件引发的不同类型的事件/消息,而不是只有一个通用事件:

public interface IWatcherPluginBase
{
    event EventHandler<GenericMessageEventArgs> GenericMessage;
    event EventHandler<PluginStartingEventArgs> PluginStarting;
    event EventHandler<PluginStoppingEventArgs> PluginStopping;
    // etc...

(另外,您可能会发现将插件库定义为抽象基类而不是接口是有帮助的 - 这样您就可以提供基本实现,但这是一个不同的讨论)

没有看到使用事件的问题。 另一个解决方案是在Pluginhost上实现一个接口,该接口包含从插件中与它交互的方法,例如:

public class MyApplication: IPluginHost
{

}

其中IPluginHost有方法

void writeLog( .... );

然后在Plugin-interface中添加

IPluginHost HostApplication { get; }

并在加载插件时分配主机。

然后插件就可以了

HostApplication.WriteLog( ....)

这样,您就可以创建某种类型的API,以允许插件在主机上执行各种操作。

(在我写作的时候把这个从头脑中拿出来,所以代码可能无法正常工作,但希望你能得到我的观点:))

嗯,我不确定在采伐标准和做法方面是否有任何好的资源,但我的经验让我相信:

  • 记录不是业务功能或功能,它是一种诊断工具。 因此,我们不应将日志记录功能公开或定义为业务合同(即接口)的一部分。

  • 从我们的框架的角度来看,我们无法强制执行我们运行的组件的日志记录,并且从我们的组件的角度来看,我们无法强制执行报告,因此将其定义为我们的界面的一部分有点没有实际意义。

  • 同样,日志记录不是我们可以强制执行的,但如果我们希望鼓励日志记录,我们的框架应该公开日志记录API。

考虑编写我们自己的ILog接口,例如

// most of us will recognize this as a thinly veiled 
// log4net subset, extend or reduce to address our
// framework's requirements
public interface ILog
{
    bool IsDebugEnabled { get; }
    void Debug(object message);
    void Debug(object message, Exception exception);
    void DebugFormat(string format, params object[] args);
}

这种精简的抽象提供了对实施的精细控制,同时最小化对现有消费者的影响。 这是有利的,因为消费者(我们的插件)不关心如何实现日志记录,只关心有事情要记录。

  • 同样,日志记录不是我们可以强制执行的,但是如果插件希望使用日志记录服务,它应该能够从容易获得的源获取它。

一种解决方案可能是暴露公共静态单例,例如

// again, thinly veiled wrapper to log4net. so long as we are able to
// implement these methods, however, we do not care who the actual
// provider is
public static class LogProvider
{
    public static ILog GetLogger<T>()
    {
        return GetLoggerByType(typeof(T));
    }
    public static ILog GetLoggerByName(string name)
    {
        global::log4net.ILog log4netLogger = 
            global::log4net.LogManager.GetLogger(name);

        // Log4NetLog is an implementation of our ILog
        // that accepts a lognet:ILog and delegates to it
        ILog logger = new Log4NetLog(log4netLogger);
        return logger;
    }
    public static ILog GetLoggerByType(Type type)
    {
        global::log4net.ILog log4netLogger = 
            global::log4net.LogManager.GetLogger(type);
        ILog logger = new Log4NetLog(log4netLogger);
        return logger;
    }
}

消费者会这样使用它

public class AwesomeLoggingPlugin : IWatcherPluginBase
{
    private static readonly ILog _log = 
        LogProvider.GetLogger<AwesomeLoggingPlugin> ();

    public AwesomeLoggingPlugin ()
    {
        _log.Debug ("Instantiated.");
    }
}

这种方法的主要优点是易于使用且易于访问。 缺点是我们的插件现在紧密耦合并依赖于这个静态类,对于纯粹主义者来说,这可能是一个问题。 鉴于日志记录是被动活动,这可能无关紧要。

然而,为了满足纯粹主义者,我们还可以向我们的插件注入一个ILog实例。 如,

public class AnotherAwesomeLoggingPlugin : IWatcherPluginBase
{
    private readonly ILog _log = null;
    public AnotherAwesomeLoggingPlugin (ILog log)
    {
        _log = log;
        _log.Debug ("Instantiated.");
    }
}

如果您正在开发插件系统,您可能会对使用MEF感兴趣。 使用MEF或IoC框架,您可以定义日志服务接口,并在实例化插件服务对象时将其注入插件类。 使用MEF标签,它看起来像这样:

//This would be defined in a Framework assembly that Plugins use
public interface ILoggingService
{
    //Various logging methods go here, including any overloads, like Debug, Trace, etc.
}

//This would be defined in one of your App assemblies that Plugins don't directly reference
[Export(typeof(ILoggingService))]
internal class AppLoggingService : ILoggingService
{
    //Implementation of logging for your app
}

public class MyPlugin : IWatcherPluginBase
{
    [Import] private ILoggingService _loggingService;
}

现在,当您实例化插件时,使用MEF解析其导入,该字段将自动设置为您提供的AppLoggingService的共享实例。 它有点像单身人士,只有没有静态依赖。 由于依赖项是由框架注入的,因此可以为您的单元测试注入测试依赖项,因为您可能希望禁用日志记录到磁盘以进行测试。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM