Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dependency injection and named loggers

I'm using Ninject to resolve the current class name for the logger instance like this:

kernel.Bind<ILogger>().To<NLogLogger>()
  .WithConstructorArgument("currentClassName", x => x.Request.ParentContext.Request.Service.FullName);

The constructor of a NLog Implementation could look like this:

public NLogLogger(string currentClassName)
{
  _logger = LogManager.GetLogger(currentClassName);
}

This approach should work with other IOC containers as well, I guess.


One can also use Common.Logging facade or the Simple Logging Facade.

Both of these employ a service locator style pattern to retrieve an ILogger.

Frankly, logging is one of those dependencies I see little to no value in automatically injecting.

Most of my classes that require logging services look like this:

public class MyClassThatLogs {
    private readonly ILogger log = Slf.LoggerService.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.FullName);

}

By utilizing the Simple Logging Facade I have switched a project from log4net to NLog, and I have added logging from a third party library that used log4net in addition to my application's logging using NLog. That is to say, the facade has served us well.

One caveat that is difficult to avoid is the loss of features specific to one logging framework or another, perhaps the most frequent example of which is custom logging levels.


This is for the benefit of anyone that is trying to figure out how to inject a logger dependency when the logger that you want to inject is provided a logging platform such as log4net or NLog. My problem was that I could not understand how I could make a class (e.g. MyClass) dependent on an ILogger-type interface when I knew that the resolution of the specific ILogger would depend on knowing the type of the class that is dependent on ILogger (e.g. MyClass). How does the DI/IoC platform/container get the right ILogger?

Well, I have looked at the source for Castle and NInject and have seen how they work. I have also looked AutoFac and StructureMap.

Castle and NInject both provide an implementation of logging. Both support log4net and NLog. Castle also supports System.Diagnostics. In both cases, when the platform resolves the dependencies for a given object (e.g. when the platform is creating MyClass and MyClass depends on ILogger) it delegates the creation of the dependency (ILogger) to the ILogger "provider" (resolver might be a more common term). The implementation of the ILogger provider is then responsible for actually instantiating an instance of ILogger and handing it back out, to then be injected into the dependent class (e.g. MyClass). In both cases the provider/resolver knows the type of the dependent class (e.g. MyClass). So, when MyClass has been created and its dependencies are being resolved, the ILogger "resolver" knows that the class is MyClass. In the case of using the Castle or NInject provided logging solutions, that means that the logging solution (implemented as a wrapper over log4net or NLog) gets the type (MyClass), so it can delegate down to log4net.LogManager.GetLogger() or NLog.LogManager.GetLogger(). (Not 100% sure of syntax for log4net and NLog, but you get the idea).

While AutoFac and StructureMap do not provide a logging facility (at least that I could tell by looking), they do seem to provide the ability to implement custom resolvers. So, if you wanted to write your own logging abstraction layer, you could also write a corresponding custom resolver. That way, when the container wants to resolve ILogger, your resolver would be used to get the correct ILogger AND it would have access to the current context (i.e. what object's dependencies are currently being satisfied - what object is dependent on ILogger). Get the type of the object, and you are ready to delegate the creation of the ILogger to the currently configured logging platform (that you have probably abstracted behind an interface and for which you have written a resolver).

So, a couple of key points which I suspected were required but that I did not fully grasp before are:

  1. Ultimately the DI container must be aware, somehow, of what logging platform to use. Typically this is done by specifying that "ILogger" is to be resolved by a "resolver" that is specific to a logging platform (hence, Castle has log4net, NLog, and System.Diagnostics "resolvers" (among others)). The specification of which resolver to use can be done via config file or programmatically.

  2. The resolver needs to know the context for which the dependency (ILogger) is being resolved. That is, if MyClass has been created and it is dependent on ILogger, then when the resolver is trying to create the correct ILogger, it (the resolver) must know the current type (MyClass). That way, the resolver can use the underlying logging implementation (log4net, NLog, etc) to get the correct logger.

These points might be obvious to those DI/IoC users out there, but I am just now coming into it, so it has taken me a while to get my head around it.

One thing that I have not figured out yet is how or if something like this is possible with MEF. Can I have an object dependent on an interface and then have my code execute after MEF has created the object and while the interface/dependency is being resolved? So, assume I have a class like this:

public class MyClass
{
  [Import(ILogger)]
  public ILogger logger;

  public MyClass()
  {
  }

  public void DoSomething()
  {
    logger.Info("Hello World!");
  }
}

When MEF is resolving the imports for MyClass, can I have some of my own code (via an attribute, via an extra interface on the implementation of ILogger, elsewhere???) execute and resolve the ILogger import based on the fact that it is MyClass that is currently in context and give back a (potentially) different ILogger instance than would be retrieved for YourClass? Do I implement some sort of MEF Provider?

At this point, I still don't know about MEF.


I see you figured out your own answer :) But, for folks in the future that have this question about how to NOT tie yourself to a particular logging framework, this library: Common.Logging helps with exactly that scenario.