2
votes

This is going to be a long winded question, but please bear with me. I am trying to create a Logging component for our applications, which can be extended to plug-in new logging frameworks (NLog, Log4Net ec). Now I know that we already have Common.Logging facade written by Ben Foster, but I wanted to try something more customized. One of the major requirements in our case, is that we need a custom mechanism of logging individual parameters/values to database using a logging framework (NLog in this case). So I have a simple logging interface:

/// <summary>
/// Interface for implementing a Logger Provider.
/// </summary>
public interface ILogger
{
    /// <summary>
    /// Logs an Info level diagnostics message.
    /// </summary>
    /// <param name="logInfo">The Log Info object.</param>
    void Info(LogInfo logInfo);

    /// <summary>
    /// Logs a Warn level diagnostics message.
    /// </summary>
    /// <param name="logInfo">The Log Info object.</param>
    void Warn(LogInfo logInfo);

    /// <summary>
    /// Logs a Debug level diagnostics message.
    /// </summary>
    /// <param name="logInfo">The Log Info object.</param>
    void Debug(LogInfo logInfo);

    /// <summary>
    /// Logs an Error level diagnostics message.
    /// </summary>
    /// <param name="logInfo">The Log Info object.</param>
    void Error(LogInfo logInfo);

    /// <summary>
    /// Logs an Fatal level diagnostics message.
    /// </summary>
    /// <param name="logInfo">The Log Info object.</param>
    void Fatal(LogInfo logInfo);
}

The LogInfo object contains all the necessary logging information. I have an abstract class that implements the interface:

public abstract class NLogLoggerBase : ILogger
{
     ....
}

Which is sublcassed into:

public class NLogDatabaseLogger : NLogLoggerBase
{
   public NLogDatabaseLogger(ILogUtility logUtility)
        : base(logUtility)
    {
        //Make sure the custom target is registered for use BEFORE using it.
        ConfigurationItemFactory.Default.Targets.RegisterDefinition("DatabaseLog", typeof(DatabaseLogTarget));

        //initialize the _logger
        _logger = LogManager.GetLogger("DatabaseLogger");
    }
}

and

public class NLogFileLogger : NLogLoggerBase
{
   ....
}

Database logger uses a custom DatabaseTarget (which inherits from TargetWithLayout in NLog) to log into the database, and ditto for File Logger. The basic settings for NLog are wired in the App.Config:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog" />
  </configSections>
  <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        autoReload="true" throwExceptions="false" internalLogFile="C:\Temp\nlog.txt" internalLogLevel="Info"
        internalLogToConsole="false">
    <targets async="true">
      <target name="CustomDatabaseLog" xsi:type="DatabaseLog" />
      <target name="CustomFileLog" xsi:type="File" />
    </targets>

    <rules>
      <logger name="DatabaseLogger" minlevel="Info" writeTo="CustomDatabaseLog" />
      <logger name="FileLogger" minlevel="Info" writeTo="CustomFileLog" />
    </rules>
  </nlog>
</configuration>

So far so good. I have a couple of tests for each of the loggers, and they log fine to their respective targets.

Then I wrote a Logger factory, which will return the correct implementation of ILogger interface, based on the logger type:

/// <summary>
/// Provides an interface for a factory which returns a logger component.
/// </summary>
public interface ILoggerFactory
{
    /// <summary>
    /// Creates an instance of logger component based on dependency registration key.
    /// </summary>
    /// <param name="loggerName">The dependency registration key.</param>
    /// <returns>An Instance of logger component.</returns>
    ILogger CreateLogger(string loggerName);
}

and the implementation:

/// <summary>
/// Implementation of ILoggerFactory interface.
/// </summary>
public class NLogLoggerFactory : ILoggerFactory
{
    private readonly ILogger _nlogDatabaseLogger;
    private readonly ILogger _nlogFileLogger;

    public NLogLoggerFactory(ILogger nlogDatabaseLogger, ILogger nlogFileLogger)
    {
        if (nlogDatabaseLogger == null)
            throw new ArgumentNullException("nlogDatabaseLogger");

        if (nlogFileLogger == null)
            throw new ArgumentNullException("nlogFileLogger");

        _nlogDatabaseLogger = nlogDatabaseLogger;
        _nlogFileLogger = nlogFileLogger;
    }

    /// <summary>
    /// Creates an instance of logger component based on dependency registration key.
    /// </summary>
    /// <param name="loggerKey">The dependency registration key.</param>
    /// <returns>An Instance of logger component.</returns>
    public ILogger CreateLogger(string loggerKey)
    {
        if (loggerKey.Equals(DependencyRegistrationKeys.NLogDatabaseLoggerKey))
            return _nlogDatabaseLogger;

        if (loggerKey.Equals(DependencyRegistrationKeys.NLogFileLoggerKey))
            return _nlogFileLogger;

        throw new ArgumentException("Invalid loggerKey");
    }

So I go for wiring it all up with Unity. Here is quick test case I knocked up (I know it needs re-factoring, but it is for illustrative purposes):

    [Test]
    public void Test_if_logger_factory_returns_correct_implementation()
    {
        var container = new UnityContainer();
        container.RegisterType<ILogUtility, NLogUtility>();
        container.RegisterType<ILogger, NLogDatabaseLogger>(DependencyRegistrationKeys.NLogDatabaseLoggerKey);
        container.RegisterType<ILogger, NLogFileLogger>(DependencyRegistrationKeys.NLogFileLoggerKey);
        container.RegisterType<ILoggerFactory, NLogLoggerFactory>(
            new InjectionConstructor(
                new ResolvedParameter<ILogger>(DependencyRegistrationKeys.NLogDatabaseLoggerKey),
                new ResolvedParameter<ILogger>(DependencyRegistrationKeys.NLogFileLoggerKey)));

        var loggerFactory = container.Resolve<ILoggerFactory>();
        Assert.IsAssignableFrom<NLogLoggerFactory>(loggerFactory);

        var logger = loggerFactory.CreateLogger(DependencyRegistrationKeys.NLogDatabaseLoggerKey);
        Assert.IsNotNull(logger);
        Assert.IsAssignableFrom<NLogDatabaseLogger>(logger);
    }

All is fine until I hit the line:

var loggerFactory = container.Resolve<ILoggerFactory>();

This seems to hit the constructor of NLogDatabaseLogger as expected, and blows up on this line with an exception:

//initialize the _logger
_logger = LogManager.GetLogger("DatabaseLogger");

Exception:

Required parameter 'FileName' on 'File Target[CustomFileLog_wrapped]' was not specified.

My guess is that Unity is struggling with getting NLog, to get a named logger instance. Has anyone encountered this before? Is there any way around this? I have spent the whole morning banging my head about this, but no dice. Any help would be really appreciated!

1
Where is the code for LogManager? Is that from NLog? I don't think Unity is the problem here as LogManager.GetLogger looks like a static method call, NLogLoggerBase would be where the offending code is at I reckonJun Wei Lee
True, LogManager is a static class exposed by NLog. I ended up moving the FileTarget code to the app.config filetranceporter

1 Answers

3
votes

Check out this question and my answer.

How to Inject Log4Net ILog implementations using Unity 2.0

The question is about resolving named log4net loggers using unity, but I think that you should be able to apply the same process to NLog and unity.

The OP of that question wrote a nice blog post describing the exact procedure that he went through to get this to work. From his blog posting, these are the steps required (assumes some knowledge of unity):

Implementing this in Unity requires three steps.

  1. Creating a Build Tracking Extension
  2. Creating a Logger Creation Extension
  3. Wire the extensions into Unity

Good luck!