Dependency injection and named loggers
Asked Answered
N

5

79

I am interested in learning more about how people inject logging with dependency injection platforms. Although the links below and my examples refer to log4net and Unity, I am not necessarily going to use either of those. For dependency injection/IOC, I will probably use MEF as that is the standard that the rest of the project (large) is settling on.

I am very new to dependency injection/ioc and am pretty new to C# and .NET (have written very little production code in C#/.NET after the past 10 years or so of VC6 and VB6). I have done a lot of investigation into the various logging solutions that are out there, so I think that I have a decent handle on their feature sets. I am just not familiar enough the with actual mechanics of getting one dependency injected (or, maybe more "correctly", getting an abstracted version of one dependency injected).

I have seen other posts related to logging and/or dependency injection like: dependency injection and logging interfaces

Logging best practices

What would a Log4Net Wrapper class look like?

again about log4net and Unity IOC config

My question does not have specifically to do with "How to I inject logging platform xxx using ioc tool yyy?" Rather, I am interested in how people have handled wrapping the logging platform (as is often, but not always recommended) and configuration (i.e. app.config). For example, using log4net as an example, I could configure (in app.config) a number of loggers and then get those loggers (without dependency injection) in the standard way of using code like this:

private static readonly ILog logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

Alternatively, if my logger is not named for a class, but rather, for a functional area, I could do this:

private static readonly ILog logger = LogManager.GetLogger("Login");
private static readonly ILog logger = LogManager.GetLogger("Query");
private static readonly ILog logger = LogManager.GetLogger("Report");

So, I guess that my "requirements" would be something like this:

  1. I would like to insulate my product's source from a direct dependency on a logging platform.

  2. I would like to be able to resolve a specific named logger instance (probably sharing the same instance among all requesters of the same named instance) either directly or indirectly by some kind of dependency injection, probably MEF.

  3. I don't know if I would call this a hard requirement, but I would like the ability to get a named logger (different than the class logger) on demand. For example, I might create a logger for my class based on the class name, but one method needs particulary heavy diagnostics that I would like to control separately. In other words, I might want a single class to "depend" on two separate logger instances.

Let's start with number 1. I have read a number of articles, primarily here on stackoverflow, about whether or not it is a good idea to wrap. See the "best practices" link above and go to jeffrey hantin's comment for one view about why it is bad to wrap log4net. If you did wrap (and if you could wrap effectively) would you wrap strictly for the purpose of injection/removal of direct depdency? Or would you also try to abstract away some or all of the log4net app.config information?

Let's say I want to use System.Diagnostics, I would probably want to implement an interface-based logger (maybe even using the "common" ILogger/ILog interface), probably based on TraceSource, so that I could inject it. Would you implement the interface, say over TraceSource, and just use the System.Diagnostics app.config information as is?

Something like this:

public class MyLogger : ILogger
{
  private TraceSource ts;
  public MyLogger(string name)
  {
    ts = new TraceSource(name);
  }

  public void ILogger.Log(string msg)
  {
    ts.TraceEvent(msg);
  }
}

And use it like this:

private static readonly ILogger logger = new MyLogger("stackoverflow");
logger.Info("Hello world!")

Moving on to number 2 ... How to resolve a particular named logger instance? Should I just leverage the app.config information of the logging platform that I choose (i.e. resolve the loggers based on the naming scheme in the app.config)? So, in the case of log4net, might I prefer to "inject" LogManager (note that I know this is not possible since it is a static object)? I could wrap LogManager (call it MyLogManager), give it an ILogManager interface, and then resolve MyLogManager.ILogManager interface. My other objects could have a depenency (Import in MEF parlance) on ILogManager (Export from the assembly where it is implemented). Now I could have objects like this:

public class MyClass
{
  private ILogger logger;
  public MyClass([Import(typeof(ILogManager))] logManager)
  {
    logger = logManager.GetLogger("MyClass");
  }
}

Any time ILogManager is called, it would directly delegate to log4net's LogManager. Alternatively, could the wrapped LogManager take the ILogger instances that it gets based on the app.config and add them to the(a ?) MEF container by name. Later, when a logger of the same name is requested, the wrapped LogManager is queried for that name. If the ILogger is there, it is resolved that way. If this is possible with MEF, is there any benefit do doing so?

In this case, really, only ILogManager is "injected" and it can hand out ILogger instances in the way that log4net normally does. How does this type of injection (essentially of a factory) compare to injecting the named logger instances? This does allow for more easy leveraging of log4net's (or other logging platform) app.config file.

I know that I can get named instances out of the MEF container like this:

var container = new CompositionContainer(<catalogs and other stuff>);
ILogger logger = container.GetExportedValue<ILogger>("ThisLogger");

But how do I get the named instances into the container? I know about the attribute based model where I could have different implementations of ILogger, each of which is named (via a MEF attribute), but that doesn't really help me. Is there a way to create something like an app.config (or a section therein) that would list the loggers (all of the same implementation) by name and that MEF could read? Could/should there be a central "manager" (like MyLogManager) that resolves named loggers via the underlying app.config and then inserts the resolved logger into the MEF container? This way it would be available to someone else that has access to the same MEF container (although without the MyLogManager's knowledge of how to use log4net's app.config information, it seems that the container would be unable to resolve any named loggers directly).

This has already gotten pretty long. I hope it that it is coherent. Please feel free to share any specific information about how you dependency injected a logging platform (we are most likely considering log4net, NLog, or something (hopefully thin) built on System.Diagnostics) into your application.

Did you inject the "manager" and have it return logger instances?

Did you add some of your own config information in your own config section or in your DI platform's config section to make it easier/possible to inject logger instances directly (i.e. make your dependencies be on ILogger rather than ILogManager).

What about having a static or global container that has either the ILogManager interface in it or the set of named ILogger instances in it. So, rather than injecting in the conventional sense (via constructor, property, or member data), the logging dependency is explicitly resolved on demand. Is this a good or bad way to dependency inject.

I am marking this as a community wiki since it doesn't seem like a question with a definite answer. If anyone feels otherwise, feel free to change it.

Thanks for any help!

Neurocoele answered 10/8, 2010 at 18:48 Comment(0)
N
15

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.

Neurocoele answered 10/8, 2010 at 18:48 Comment(2)
It appears like MEF and Unity (IoC from MS's Patterns & Practices group) are like comparing apples and oranges. Maybe a true IoC container is what is needed for this particular issue which would have the extensibility points to add custom dependency resolution. #293551Tipton
Wow... some people have really made simple things far more difficult than they needed to be. I wonder how useful all of this really is when you just want to write some text to a file...Suborbital
C
39

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.

Chock answered 10/8, 2010 at 18:48 Comment(3)
I used x.Request.ParentContext.Plan.Type.FullName to get the concrete class name instead of the interface name.Jabe
I always get ParentContext as null. I have NLogLogger inheriting from Loggerbase which is implementing ILogger.Obaza
If the base class is in a different project then make sure to initialize the logger again like: [assembly: log4net.Config.XmlConfigurator(Watch = true)]Reliance
M
17

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.

Moten answered 10/8, 2010 at 18:48 Comment(1)
Thanks for the tip about SLF and Common.Logging. I have actually experimented with these libraries recently and they are both pretty cool. In the end, I think that our project will probably not use DI to get logging, so we might end up using SLF or Common.Logging.Neurocoele
N
15

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.

Neurocoele answered 10/8, 2010 at 18:48 Comment(2)
It appears like MEF and Unity (IoC from MS's Patterns & Practices group) are like comparing apples and oranges. Maybe a true IoC container is what is needed for this particular issue which would have the extensibility points to add custom dependency resolution. #293551Tipton
Wow... some people have really made simple things far more difficult than they needed to be. I wonder how useful all of this really is when you just want to write some text to a file...Suborbital
U
5

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.

Undesigning answered 10/8, 2010 at 18:48 Comment(1)
Same comment as for qstarin. Thanks for your pointer to Common.Logging.Neurocoele
R
0

I made my custom ServiceExportProvider, by the provider I register Log4Net logger for dependency injection by MEF. As result you can use logger for different kinds of injections.

Example of injections:

[Export]
public class Part
{
    [ImportingConstructor]
    public Part(ILog log)
    {
        Log = log;
    }

    public ILog Log { get; }
}

[Export(typeof(AnotherPart))]
public class AnotherPart
{
    [Import]
    public ILog Log { get; set; }
}

Example of usage:

class Program
{
    static CompositionContainer CreateContainer()
    {
        var logFactoryProvider = new ServiceExportProvider<ILog>(LogManager.GetLogger);
        var catalog = new AssemblyCatalog(typeof(Program).Assembly);
        return new CompositionContainer(catalog, logFactoryProvider);
    }

    static void Main(string[] args)
    {
        log4net.Config.XmlConfigurator.Configure();
        var container = CreateContainer();
        var part = container.GetExport<Part>().Value;
        part.Log.Info("Hello, world! - 1");
        var anotherPart = container.GetExport<AnotherPart>().Value;
        anotherPart.Log.Fatal("Hello, world! - 2");
    }
}

Result in console:

2016-11-21 13:55:16,152 INFO  Log4Mef.Part - Hello, world! - 1
2016-11-21 13:55:16,572 FATAL Log4Mef.AnotherPart - Hello, world! - 2

ServiceExportProvider implementation:

public class ServiceExportProvider<TContract> : ExportProvider
{
    private readonly Func<string, TContract> _factoryMethod;

    public ServiceExportProvider(Func<string, TContract> factoryMethod)
    {
        _factoryMethod = factoryMethod;
    }

    protected override IEnumerable<Export> GetExportsCore(ImportDefinition definition, AtomicComposition atomicComposition)
    {
        var cb = definition as ContractBasedImportDefinition;
        if (cb?.RequiredTypeIdentity == typeof(TContract).FullName)
        {
            var ce = definition as ICompositionElement;
            var displayName = ce?.Origin?.DisplayName;
            yield return new Export(definition.ContractName, () => _factoryMethod(displayName));
        }
    }
}
Radian answered 10/8, 2010 at 18:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.