0
votes

I am using the Castle logging facility with log4net in my application (MVC3 web app). However, rather than using the ILogger interface directly I have abstracted this by creating another interface (IAuditor) with a concrete implementation Log4NetAuditor (see code below).

You may ask why I've done this since the point of ILogger is to abstract the underlying logging implementation. I've done because of strict observation of the onion architecture principle of abstracting all infrastructure concerns.

Now, this works just fine except all loggers are named the same: MyProject.Infrastructure.Log4NetAuditor:

2011-01-24 13:26:11,746 [11] DEBUG MyProject.Infrastructure.Log4NetAuditor [] - Attempting login...
2011-01-24 13:26:11,845 [11] DEBUG MyProject.Infrastructure.Log4NetAuditor [] - Authentication result is Authenticated

The IAuditor is exposed as a property on an AuditableComponent which is an abstract class from which all components that require logging derive.

public abstract class AuditableComponent
{
    private IAuditor _auditor = NullAuditor.Instance;

    public IAuditor Auditor
    {
        get { return _auditor; }
        set { _auditor = value; }
    }

    //....
}

The Log4NetAuditor class looks like this:

public class Log4NetAuditor : IAuditor
{
    private ILogger _logger = NullLogger.Instance;

    public ILogger Logger
    {
        get { return _logger; }
        set { _logger = value; }
    }

    public void Trace(string message)
    {
        _logger.Debug(message);
    }
}

Now, it's clear why all log statements have the same logger name: ILogger is injected on the Log4NetAuditor class.

How, then, can I ensure the name of the logger corresponds to the name of the class that extends AuditableComponent?

1
And why aren't you using LoggingFacility? It provides taht functionality OOTBKrzysztof Kozmic
Hi Krzystof, I am using the LoggingFacility. However, I don't want any references to ILogger (i.e. Windsor) in my client app. That's where the IAuditor abstraction comes in.Øyvind
then have a look at how the facility works and you can replicate that. It's OSS mate - you can use and modify that code as you please :)Krzysztof Kozmic
That's a good suggestion and so obvious I didn't think of it ;-) I've created a factory that solves the problem for me, and retained the use of the facility in the background so that my Auditors can have any logging implementation plugged into it. Nice! Thanks Krzysztof!Øyvind

1 Answers

1
votes

I did the same thing as you but I also included a static LogManager class who's job it was to instantiate your logger. I made my constructors internal on the Logger class (your auditor class) so that they could not be created directly. This means that any external entity would need to get their reference through the LogManager class.

public static class LogManager
{
    public static readonly ILogger Log = CreateLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

    /// <summary>
    /// Creates the logger for the specified type.
    /// </summary>
    /// <param name="loggerType">Type of the logger.</param>
    /// <returns></returns>
    public static Logger CreateLogger(Type loggerType)
    {
        return new Logger(loggerType);
    }

    /// <summary>
    /// Creates the logger for the specified name.
    /// </summary>
    /// <param name="loggerName">Name of the logger.</param>
    /// <returns></returns>
    public static Logger CreateLogger(string loggerName)
    {
        return new Logger(loggerName);
    }
}

Now those types will be able to instantiate your Auditor with their own type being passed to the Create method.

UPDATE --

I used the Unity container to inject dependencies into this code as well. The relevant bits is the InjectionFactory. It allows me to specify a static method locally to inject a type based on some conditions.

DependencyInjection.Container.RegisterType<IFormatter>(
    new ContainerControlledLifetimeManager(),
    new InjectionFactory(c => CreateFormatter(filename, outputType)));

...

        private static IFormatter CreateFormatter(string filename, OutputType outputType)
        {
            TextWriter textWriter = (filename.Length > 0) ? new StreamWriter(filename) : new StreamWriter(Console.OpenStandardOutput());
            var formatter = (outputType == OutputType.Xml) ? (IFormatter)new XmlFormatter(textWriter) : new CsvFormatter(textWriter);

            return formatter;
        }

...
// Calling it
using (var formatter = DependencyInjection.Container.Resolve<IFormatter>())
{
    if (timeLimit <= 1000) retval = OutputResults(results, formatter, msxQuery);
    else TimedOutputResults(results, formatter, msxQuery, timeLimit, OutputResults);
}

Well, that does it for me at least!