开发者

Is using an event for logging a good idea?

I'm working on program that is able to load various (self-made) plugins.

These Plugins need to have a way to send notifications to the host program.

Currently I have an Interface as a base for the plugins. The Interface defines an event that is fired whenever I need to log something

The Interface looks like this:

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();
}

In the plugin I use the following code to fire the event

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

My main program adds an event handler

plugin.WriteLog += new LogEventHandler(plugin_WriteLog);

Do you know other (potentially bett开发者_运维百科er) ways to implement logging?


You might consider usign built-in classes in the namespace System.Diagnostics (Debug, Debugger, Trace, EventLog).

That way you could redirect standard debug and trace output to VS console (when debugging) or to a file (when running a release build).


Hm, I am not sure if there are any good resources regarding logging standards and practices, but my experience has lead me to believe:

  • Logging is not a business function or feature, it is a diagnostic tool. Therefore, we should not expose or define logging features as part of our business contracts (ie interfaces).

  • From our framework's perspective, we cannot enforce logging in the components we run, and from our component's perspective, we cannot enforce reporting to be enabled, so defining it as part of our interface is somewhat moot.

  • Again, logging is not something we can enforce, but if we wish to encourage logging, our framework should expose a logging api.

Consider authoring our own ILog interface, such as

// 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);
}

This thin abstraction affords fine control of implementation while minimizing impact to existing consumers. This is advantageous in that the consumer (our plug-in) does not care how logging is implemented, only that there is something to log to.

  • Again, logging is not something we can enforce, but if a plug-in wishes to consume a logging service, it should be able to obtain it from a readily-available source.

One solution may be to expose a public static singleton, such as

// 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;
    }
}

A consumer would use it as such

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

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

The primary advantage to this approach is that it is easy to use and readily accessbile. A disadvantage is that our plug-in is now tightly-coupled and dependent on this static class, and for purists this may be an issue. Given logging is a passive activity this may not matter.

To sate the purists however, we could also inject an instance of ILog to our plugin. Such as,

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


Yes - I would see using events in this way as a good idea - the alternative (using your favourite logging framework) would tie someone into a logging framework when they may in fact already use their own.

Using built in classes in the System.Diagnostics name space (Debug, Trace etc...) would be a lighter-weight alternative, however these log destinations are less flexible than other more full-featured logging frameworks (such as log4net), and also require that a flag be defined (Trace or Debug respectively) at compile time - whether these methods would be suitable or not depends on how you anticipate these messages being used.

The only refinement I would suggest would be to separate your events into the different types of event / message being raised by the plugin rather than just having one generic event:

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

(Also, you may find it helpful to have your plugin base defined as an abstract base class rather than an interface - that way you can provide base implementations, however that is a different discussion)


Don't see a problem with using an event for this. Another solution would be to maybe implement an interface on the Pluginhost which holds methods for interacting with it from the plugin, for instance:

public class MyApplication: IPluginHost
{

}

where IPluginHost has the method

void writeLog( .... );

Then in the Plugin-interface add

IPluginHost HostApplication { get; }

and assign the Host when the plugin is loaded.

The plugin will then be able to do

HostApplication.WriteLog( ....)

This way you'll be able to create an API of some sorts to allow the plugins to perform various actions on the host.

(Took this out of my head as I was writing, so the code might not work as is, but hopefully you get my point :))


If you're developing a plugin system, you may be interested in using MEF. Using MEF or an IoC framework, you can define a logging service interface and inject the logging service object into your plugin classes when they are instantiated. Using MEF tags, it looks something like this:

//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;
}

Now when you instantiate the plugin, you use MEF to resolve its imports and the field will automatically be set to the shared instance of the AppLoggingService you've provided. It's kind of like a singleton, only there is no static dependency. Because the dependency is being injected by the framework, this leaves open the possibility for injection of test dependencies for your unit tests, as you probably want to disable logging to disk for your tests, for instance.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜