Extending the ILog interface

Topics: Extensibility, Framework Services
Jun 16, 2011 at 4:27 PM

Would it be possible to add a second and/or third Error method to the existing ILog interface?

It seems to me that the "Error" method is the poor relation of the ILog interface. It can only report an error if the application throws an actual exception, whereas the "Info" and "Warn" methods can be used freely throughout the application.

Also, there is no way of adding details to the error log when an exception does occur.

To this end I suggest the following addtional Error method overloads. It shouldn't affect any existing code.

 public interface ILog
    {
        void Info(string format, params object[] args);

        void Warn(string format, params object[] args);

        void Error(Exception exception);
        void Error(string format, params object[] args); // ****New***
        void Error(Exception exception, string format, params object[] args); // ****New***

}
RodP
Coordinator
Jun 16, 2011 at 4:32 PM

I'll give it some thought. The interface was mostly designed for the needs of the framework, rather than a general logging abstraction. I have to be careful that Caliburn.Micro doesn't become Caliburn.Macro ;)

Jun 20, 2011 at 11:36 AM

I thought the same thing--that another overload of Error would be nice.  So I just did it.  CM is so nicely extensible (thanks Rob).  Extending the logging doesn't take messing with CM at all.  Here is what I did:

1. Created an extended logging interface:

namespace MyApp
{
    using Caliburn.Micro;

    public interface ILogEx : ILog
    {
        /// <summary>
        /// Logs the message as a error.
        /// </summary>
        /// <param name="format">A formatted message.</param>
        /// <param name="args">Parameters to be injected into the formatted message.</param>
        void Error(string format, params object[] args);
    }
}

2. Made our DebugLogger implementation support both interfaces:

namespace MyApp
{
    using System;
    using System.Diagnostics;
    using Caliburn.Micro;

    public class DebugLogger : ILog, ILogEx
    {

...

        public void Error(string format, params object[] args)
        {
            Debug.WriteLine(CreateLogMessage(format, args), "ERROR");
        }

...

    }
}

3. Get the logger via the extended interface in my view models

public class MyViewModel : Screen
{
    static readonly ILogEx Log = LogManager.GetLog(typeof(MyViewModel)) as ILogEx;

 ...

4. Just use it. :)

Log.Info("Info message");
Log.Warn("Warning message");
Log.Error(new Exception("Error from Exception"));
Log.Error("Error from string with {0}, {1}", "parameter 1", "parameter 2");

I did not, by the way, change anything in CM.  Nor did I change the registration of the logger in my bootstrapper either, which still looks like this:

LogManager.GetLog = type => new DebugLogger(type);

And I get ...

[2011-06-20T03:17:30.9158462-07:00 Caliburn.Micro.ActionMessage] Action: CancelButton availability update.
[2011-06-20T03:17:35.4528462-07:00 Caliburn.Micro.ActionMessage] Invoking Action: CancelButton.
[2011-06-20T03:17:35.4658462-07:00 MyApp.MyViewModel] Info message
[2011-06-20T03:17:35.4698462-07:00 MyApp.MyViewModel] Warning message
[2011-06-20T03:17:35.4718462-07:00 MyApp.MyViewModel] System.Exception: Error from Exception
[2011-06-20T03:17:35.4758462-07:00 MyApp.MyViewModel] Error from string with parameter 1, parameter 2

Jun 21, 2013 at 1:14 PM
Edited Jun 21, 2013 at 1:16 PM
I use another approach.
I created simple adapter that adapts slf4net.ILogger to Caliburn.Micro.ILog:
using System;
using Caliburn.Micro;
using slf4net;

internal class Log: ILog
{
    private ILogger _logger;

    internal Log(Type type)
    {
        _logger = LoggerFactory.GetLogger(type);
    }

    void ILog.Info(string format, params object[] args)
    {
        _logger.Info(format, args);
    }

    void ILog.Warn(string format, params object[] args)
    {
        _logger.Warn(format, args);
    }

    void ILog.Error(Exception exception)
    {
        _logger.Error(exception, string.Empty);
    }
}
and added this line in my bootstrapper:
LogManager.GetLog = type => new Log(type);
This redirects all logging operations from Caliburn.Micro to slf4net.
If I need some manual logging I don't use that adapter, I use slf4net directly instead:
private static ILogger _logger = LoggerFactory.GetLogger(typeof(ShellViewModel));
This gives me reach slf4net.ILogger interface while Caliburn.Micro uses ILog it needs.