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!
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.
Good luck!