2
votes

I am struggling with a lifestyle issue when injecting my logger.

NLog expects instances to be created "Instance per Dependency", as described here - https://simpleinjector.readthedocs.io/en/latest/lifetimes.html#instance-per-dependency.

Logger creation is done using a factory method LogManager.GetCurrentClassLogger(). Using this pattern allows the logger to obtain the name of the caller in order to include it in logged messages.

Simple Injector tells me that my Transient logger cannot be injected into a singleton. But I cannot change the lifestyle to Singleton because I would then lose the context information about which class is calling the logger.

My implementation is below:

I have written a factory that creates the NLog instance and returns it in a wrapper that implements my own ILogger abstraction:

internal class LoggerFactory : ILoggerFactory
{
    public ILogger GetLogger()
    {
       return new NLogLogger(LogManager.GetCurrentClassLogger());
    }
}

I registered this with Simple Injector thus:

container.RegisterSingleton<ILoggerFactory, LoggerFactoryInstance>();
container.Register<ILogger>(
    () => container.GetInstance<ILoggerFactory>().GetLogger());

And I depend on the ILogger interface in my classes that need logging:

public class MyClass
{
    private readonly ILogger _logger;
    public MyClass(ILogger logger)
    {
        _logger = logger;
    }
}

Do I have to inject the factory? I would rather not have to resolve the logger everywhere I want to use it, but perhaps this is what Simple Injector would expect?

How are other people dealing with this? Is there a better way?

1
'NLog expects instances to be created "Instance per Dependency"'. Where did you read this? To my knowledge, you loggers can be singleton.Steven
My statement was perhaps a bit blunt - I'm sure it can be used either way. But I wanted to make use of "LogManager.GetCurrentClassLogger()", which automatically obtains the name of the calling class and method. If the logger is a singleton, it cannot make use of this feature. (Which may be a valid compromise, depending on your needs...)freshr
The Logger<T> solves this problem.Steven

1 Answers

5
votes

You should use context based injection as described here. This means that you create a generic Logger<T> implementation for (ideally your own defined) ILogger and register it using the context of the consumer:

container.RegisterConditional(
    typeof(ILogger),
    c => typeof(Logger<>).MakeGenericType(c.Consumer.ImplementationType),
    Lifestyle.Singleton,
    c => true);

Such Logger<T> implementation would forward the call to NLog while using the supplied T:

public class Logger<T> : ILogger
{
    private readonly NLogLogger logger = new NLogLogger(typeof(T));

    public void Log(LogEntry entry)
    {
        logger.Log(...);
    }
}