16

I'm working with a project which utilizes Simple Injector as dependency injector. On the other hand, this project uses Microsoft.Extensions.Logging in order to log the events that occurs in certain classes.

My technical issue is pretty simple to explain. I want to register in my DI the ILogger independently of the class T which is being invoked, but I DO NEED to do it from my ILoggerFactory.CreateLogger<T>() method because this gets the logger configuration using Microsoft.Extensions.Configuration.

I need to use something like this in order to instance my logger:

private Microsoft.Extensions.Logging.ILogger CreateLogger<T>()
{
     var factory = this.ResolveService<ILoggerFactory>();
     var logger = factory.CreateLogger<T>();
     return logger;
}

I could achieve the injection by doing:

Container.Register(typeof(ILogger<>), typeof(Logger<>));

And this allows us to resolve something like:

public class SomeApiController : ApiController
{
     public SomeApiController(ILogger<SomeApiController> logger)
     {
         //logger is well instantiated, but doesn't got the configuration
         logger.LogInformation("test log.");
     }
}

But as I said, this does it without passing through the configuration obtained from the Microsoft.Extensions.Logging.ILoggerFactory class, so this isn't useful.

Is there a way to register ILogger<T> by using my CreateLogger<T>?

Liam
  • 22,818
  • 25
  • 93
  • 157
Mauro Bilotti
  • 3,823
  • 2
  • 30
  • 55

2 Answers2

36

Use the following registrations:

container.RegisterInstance<ILoggerFactory>(loggerFactory);
container.RegisterSingleton(typeof(ILogger<>), typeof(Logger<>));

Or, in case you are integrating Simple Injector into a generic host or ASP.NET Core application, make use of the .AddLogging() extension method to even inject a non-generic ILogger into your application components, as demonstrates in this ASP.NET Core Startup class:

public class Startup
{
    ...

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddLogging(); // Adds logging to the framework

        // AddSimpleInjector enables "cross wiring," which means you can let
        // Simple Injector-resolved components to depend on the generic
        // ILogger<T> abstraction.
        services.AddSimpleInjector(container, options =>
        {
            options.AddAspNetCore();
            
            // AddLogger allows Simple Injector-resolved components to depend on 
            // the non-generic Microsoft.Extensions.Logging.ILogger interface.
            // Simple Injector will automatically inject the correct ILogger<T>
            // for you.
            options.AddLogging();
        });
    }

    ...
}

For a full example, see the ASP.NET Core and ASP.NET Core MVC Integration Guide.

Letting application components depend on ILogger instead of ILogger<T>, makes your code simpler, easier to test, and less error prone. If you're using Simple Injector without Service Collection integration (as the previous example showed, you can use the following registration to let Simple Injector ensure the correct Logger<T> is still injected whenever an ILogger is injected:

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

This ensures that every application component gets its own Logger<T> instance, where T is the type of the component the logger is injected into. Take the following class for example that depends on ILogger:

public class ComponentA : IService
{
    public ComponentA(ILogger logger) { ... }
}

The above registration will ensure that ComponentA is injected with a Logger<ComponentA>, even though it simply depends on ILogger and not on ILogger<T>.

You can stop reading here if the above suits your needs... or continue reading if you're interested in a more SOLID solution.

A SOLID solution

Instead of letting application components depend on the framework-defined ILogger abstraction, you could also choose to define an application-specific logger abstraction, as prescribed by the Dependency Inversion Principle (DIP).

The DIP states that abstractions should be defined by the application itself—this means you define your own logger abstraction (also see this for an explanation of why you want to do this) and on top of that you build an adapter, much like described here. You can simply derive your generic adapter from the described MicrosoftLoggingAdapter as follows:

public sealed class MicrosoftLoggingAdapter<T> : MicrosoftLoggingAdapter 
{
    public MicrosoftLoggingAdapter(ILoggerFactory factory) 
        : base(factory.CreateLogger<T>()) { }
}

Using this generic adapter, you can configure Simple Injector as follows:

container.RegisterInstance<ILoggerFactory>(factory);

container.RegisterConditional(
    typeof(MyApplication.Abstractions.ILogger),
    c => typeof(MicrosoftLoggingAdapter<>).MakeGenericType(c.Consumer.ImplementationType),
    Lifestyle.Singleton,
    _ => true);
Steven
  • 151,500
  • 20
  • 287
  • 393
  • Thanks a lot Steven!! That was an awesome answer. I have only one question: Since I'm using the newer version of Simple Injector, do I have to invoke the methods inherited from ILogger (BeginScope, IsEnabled and Log) based on the adaptee injected, isn't it? – Mauro Bilotti Dec 20 '16 at 14:27
  • @MauroBilotti younonly need to call those methods if you actually need that behavior. BeginScope and IsEnabled are typically not needed AFAIK. – Steven Dec 20 '16 at 14:38
  • So, it doesn't bother anyone that we're writing an adapter on top of what is already supposed to be an adapter? I mean, why even bother with `Microsoft.Extensions.Logging` at this point? – Scott Fraley May 24 '17 at 20:35
  • Scott, it shouldn't bother you, because this is actually the right thing to do, as explained [here](https://stackoverflow.com/a/5646876/264697). – Steven May 25 '17 at 06:26
  • @Steven Why use a RegisterConditional when your conditional is always set to true? Why not just register a singleton? – Necoras Sep 11 '17 at 17:48
  • @Necoras take a close look at what exactly is registered. This is actually a 'contextual registration', since the resolved type is based on its consumer, but there is no `RegisterContextual` in SI, so you can simulate this using `RegisterConditional` with the predicate always returning `true`. – Steven Sep 11 '17 at 20:08
  • @Steven Shouldn't the DI constructor be simple and not do anything apart from caturing parameters? `MicrosoftLoggingAdapter` seems to violate this by invoking the `CreateLogger` factory method. Can you please explain a bit on this? – rexcfnghk Jan 15 '18 at 06:55
  • 1
    @rexcfnghk: This rule exists to make composing object graphs reliable, and prevent complicating unit testing. Since the `MicrosoftLoggingAdapter` is part of the Composition Root, is registered as singleton, and calling `factory.CreateLogger()` is reliable, there is no real downside in doing this extra work in the constructor. On the other hand, placing this logic inside the constructor of that generic type, enables the container to auto-wire that component. Moving that logic out of its constructor, complicates the Composition Root tremendously. – Steven Jan 15 '18 at 08:54
14

Based on Steven's solution, I post my answer to help anyone else:

    private void RegisterServices()
    {
        Container.Register(ConfigureLogger, Lifestyle.Singleton);            
        Container.Register(typeof(ILogger<>), typeof(LoggingAdapter<>));
    }

    private ILoggerFactory ConfigureLogger()
    {
        LoggerFactory factory = new LoggerFactory();

        var config = new ConfigurationBuilder()
            .AddJsonFile("logging.json")
            .Build();

        //serilog provider configuration
        var log = new LoggerConfiguration()
                 //.ReadFrom.Configuration(config)
                 .WriteTo
                 .RollingFile(ConfigSettings.LogsPath)
                 .CreateLogger();

        factory.AddSerilog(log);

        return factory;
    }

    public class LoggingAdapter<T> : ILogger<T>
    {
        private readonly Microsoft.Extensions.Logging.ILogger adaptee;          

        public LoggingAdapter(ILoggerFactory factory)
        {
            adaptee = factory.CreateLogger<T>();
        }

        public IDisposable BeginScope<TState>(TState state)
        {
            return adaptee.BeginScope(state);
        }

        public bool IsEnabled(LogLevel logLevel)
        {
            return adaptee.IsEnabled(logLevel);
        }

        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
        {
            adaptee.Log(logLevel, eventId, state, exception, formatter);
        }
    }   

As you can see, my solution is using Serilog as a provider for logging in Microsoft.Extensions.Logging.

Hope it helps!

Mauro Bilotti
  • 3,823
  • 2
  • 30
  • 55