Simple Injector: Register ILogger by using ILog

2019-03-16 07:22发布

问题:

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>?

回答1:

The way to do this with Simple Injector is by specifying a generic proxy class that delegates the call to the ILoggerFactory.

This however causes a problem when using Microsoft.Extensions.Logging, because it's ILogger<T> abstraction is one big Interface Segregation Principle violation. ISP violations are problematic, because they make it muc harder to create (among other things) proxy classes. But you should refrain from using that abstraction directly in your application components any way, as prescribed by the Dependency Inversion Principle.

Since abstractions should be defined by the application itself, this basically means you need to define your own logger abstraction 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.RegisterSingleton<ILoggerFactory>(factory);

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

In case creating your own abstraction is not (yet) an option, your second best bet is to override Simple Injector's dependency injection behavior, with the following Microsoft.Extensions.Logging-specific class:

class MsContextualLoggerInjectionBehavior : IDependencyInjectionBehavior
{
    private readonly ILoggerFactory factory;
    private readonly IDependencyInjectionBehavior original;
    private readonly Container container;

    public MsContextualLoggerInjectionBehavior(
        ILoggerFactory factory, Container container)
    {
        this.factory = factory;
        this.original = container.Options.DependencyInjectionBehavior;
        this.container = container;
    }

    public void Verify(InjectionConsumerInfo consumer) => original.Verify(consumer);

    public InstanceProducer GetInstanceProducer(InjectionConsumerInfo i, bool t) =>
        i.Target.TargetType == typeof(ILogger)
            ? GetLoggerInstanceProducer(i.ImplementationType)
            : original.GetInstanceProducer(i, t);

    private InstanceProducer<ILogger> GetLoggerInstanceProducer(Type type) =>
        Lifestyle.Singleton.CreateProducer(() => factory.CreateLogger(type), container);
}

You can replace the original behavior as follows:

container.Options.DependencyInjectionBehavior =
    new MsContextualLoggerInjectionBehavior(loggingFactory, container);


回答2:

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!