The solution is to create a simple logger façade to completely decouple Ninject from the rest of the app. The steps are:
1) Copy/paste Ninject's ILogger interface into the app's namespace (do not just inherit or you end up depending on Ninject's assembly because of the types exposed through Ninject's ILogger).
2) Create custom Logger, LoggerFactory and LoggerModule classes.
3) Pass LoggerModule to Ninject's StandardKernel
For completeness the code is:
Ninject's ILogger - copy/paste the ILogger interface, change it's namespace to MyAppNamespace.Logger and add the following methods:
void Debug(string message);
void Info(string message);
void Trace(string message);
void Warn(string message);
void Error(string message);
void Fatal(string message);
Logger.cs
namespace MyAppNamespace.Logger
{
using System;
class Logger : Ninject.Extensions.Logging.Log4net.Infrastructure.Log4NetLogger, ILogger
{
private const string DumpVerbatimFormat = "{0}";
public Logger(Type type)
: base(type)
{
}
public void Debug(string message)
{
base.Debug(DumpVerbatimFormat, message);
}
public void Info(string message)
{
base.Info(DumpVerbatimFormat, message);
}
public void Trace(string message)
{
base.Trace(DumpVerbatimFormat, message);
}
public void Warn(string message)
{
base.Warn(DumpVerbatimFormat, message);
}
public void Error(string message)
{
base.Error(DumpVerbatimFormat, message);
}
public void Fatal(string message)
{
base.Fatal(DumpVerbatimFormat, message);
}
}
}
LoggerFactory.cs
namespace MyAppNamespace.Logger
{
using System;
using System.Collections.Generic;
static class LoggerFactory
{
public static ILogger GetLogger(Ninject.Activation.IContext context)
{
return GetLogger(context.Request.Target == null ? typeof(ILogger) : context.Request.Target.Member.DeclaringType);
}
private static readonly Dictionary<Type, ILogger> TypeToLoggerMap = new Dictionary<Type, ILogger>();
private static ILogger GetLogger(Type type)
{
lock (TypeToLoggerMap)
{
if (TypeToLoggerMap.ContainsKey(type))
return TypeToLoggerMap[type];
ILogger logger = new Logger(type);
TypeToLoggerMap.Add(type, logger);
return logger;
}
}
}
}
LoggerModule.cs
namespace MyAppNamespace.Logger
{
public class LoggerModule : Ninject.Modules.NinjectModule
{
public override void Load()
{
log4net.Config.XmlConfigurator.Configure();
Bind<ILogger>().ToMethod(LoggerFactory.GetLogger);
}
}
}
Tuck this whole mess away into a separate class library making it the only piece dependent on Ninject's logging extension and the concrete logger. You can now use MyAppNamespace.ILogger throughout your app, like this:
LoggerTest.cs
namespace MyAppNamespace.Whatever
{
using Logger;
public class LoggerTest
{
public LoggerTest(ILogger log)
{
Log.Info("Logger starting up");
}
}
}
somewhere in Main.cs
using (IKernel kernel = new StandardKernel(new Logger.LoggerModule()))
{
kernel.Get<LoggerTest>();
}
Main ends up depending on Ninject but not the logging extension and whatever logger you use (the code works with Log4Net, you will need to tweak a bit for NLog). Other parts of the app depend on MyAppNamespace.ILogger. That's about it.
ILogger.Debug(string message)
method. When called with a single argument theILogger.Debug(string format, params object[] args)
ends up being called withargs = new object[]{}
instead ofnull
. The explicitLog.Debug("foo { bar", null)
call works fine, as does theLog.Debug("{0}", message)
but that is really not what I want. There really should be single argument methods likeDebug(string message)
(and friends) defined on ILogger. Still looking for a proper solution. – Collected