Inyector simple: registre ILogger<T> usando ILoggerFactory.CreateLogger<T>()

Resuelto Mauro Bilotti asked hace 8 años • 2 respuestas

Estoy trabajando con un proyecto que utiliza Simple Injector como inyector de dependencia. Por otro lado, este proyecto utiliza Microsoft.Extensions.Logging para registrar los eventos que ocurren en ciertas clases.

Mi problema técnico es bastante sencillo de explicar. Quiero registrar en mi DI el ILogger independientemente de la clase T que se esté invocando, pero NECESITO hacerlo desde mi ILoggerFactory.CreateLogger<T>()método porque este obtiene la configuración del registrador usando Microsoft.Extensions.Configuration.

Necesito usar algo como esto para crear una instancia de mi registrador:

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

Podría lograr la inyección haciendo:

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

Y esto nos permite resolver algo como:

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

Pero como dije, esto lo hace sin pasar por la configuración obtenida de la Microsoft.Extensions.Logging.ILoggerFactoryclase, por lo que no es útil.

¿ Hay alguna manera de registrarse ILogger<T>usando mi CreateLogger<T>?

Mauro Bilotti avatar Dec 20 '16 20:12 Mauro Bilotti
Aceptado

Utilice los siguientes registros:

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

O, en caso de que esté integrando Simple Injector en un host genérico o una aplicación ASP.NET Core, utilice el método de extensión .AddLogging() para incluso inyectar un componente no genérico ILoggeren los componentes de su aplicación, como se demuestra en este ASP.NET Core. Startupclase:

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();
        });
    }

    ...
}

Para ver un ejemplo completo, consulte la Guía de integración de ASP.NET Core y ASP.NET Core MVC .

Permitir que los componentes de la aplicación dependan de ILoggeren lugar de ILogger<T>, hace que su código sea más simple, más fácil de probar y menos propenso a errores. Si está utilizando Simple Injector sin integración de Service Collection (como se mostró en el ejemplo anterior, puede usar el siguiente registro para permitir que Simple Injector se asegure de que Logger<T>se siga inyectando lo correcto cada vez que ILoggerse inyecta:

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

Esto garantiza que cada componente de la aplicación obtenga su propia Logger<T>instancia, donde Testá el tipo de componente en el que se inyecta el registrador. Tomemos como ejemplo la siguiente clase que depende de ILogger:

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

El registro anterior garantizará que ComponentAse inyecte un Logger<ComponentA>, aunque simplemente dependa de ILoggery no de ILogger<T>.

Puedes dejar de leer aquí si lo anterior se adapta a tus necesidades... o continuar leyendo si estás interesado en una solución más SÓLIDA.

Una solución SÓLIDA

En lugar de permitir que los componentes de la aplicación dependan de la ILoggerabstracción definida por el marco, también puede optar por definir una abstracción de registrador específica de la aplicación, según lo prescrito por el Principio de inversión de dependencia (DIP).

El DIP establece que las abstracciones deben ser definidas por la propia aplicación; esto significa que usted define su propia abstracción del registrador (consulte también esto para obtener una explicación de por qué desea hacer esto) y, además, construye un adaptador, muy parecido a lo que se describe aquí. . Simplemente puede derivar su adaptador genérico a partir de lo que se describe MicrosoftLoggingAdaptera continuación:

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

Con este adaptador genérico, puede configurar Simple Injector de la siguiente manera:

container.RegisterInstance<ILoggerFactory>(factory);

container.RegisterConditional(
    typeof(MyApplication.Abstractions.ILogger),
    c => typeof(MicrosoftLoggingAdapter<>).MakeGenericType(c.Consumer.ImplementationType),
    Lifestyle.Singleton,
    _ => true);
Steven avatar Dec 20 '2016 14:12 Steven

Basado en la solución de Steven, publico mi respuesta para ayudar a cualquier otra persona:

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);
    }
} 

Como puede ver, mi solución es utilizar Serilog como proveedor para iniciar sesión Microsoft.Extensions.Logging.

¡Espero eso ayude!

Mauro Bilotti avatar Dec 21 '2016 14:12 Mauro Bilotti