que net mvc ihostingenvironment for dotnet development asp c# .net logging .net-core .net-standard

c# - net - ¿Debo tomar ILogger, ILogger<T>, ILoggerFactory o ILoggerProvider para una biblioteca?



que es asp net core (6)

¿Esto puede estar relacionado de alguna manera con Pass ILogger o ILoggerFactory a los constructores en AspNet Core? Sin embargo, esto se refiere específicamente al diseño de bibliotecas , no a cómo la aplicación real que utiliza esas bibliotecas implementa su registro.

Estoy escribiendo una .net Standard 2.0 Library que se instalará a través de Nuget, y para permitir que las personas que la usan obtengan información de depuración, dependo de Microsoft.Extensions.Logging.Abstractions para permitir la inyección de un registrador estandarizado.

Sin embargo, estoy viendo varias interfaces, y el código de muestra en la web a veces usa ILoggerFactory y crea un registrador en el ctor de la clase. También hay ILoggerProvider que parece una versión de solo lectura de Factory, pero las implementaciones pueden o no implementar ambas interfaces, por lo que tendré que elegir. (La fábrica parece más común que el proveedor).

Algunos códigos que he visto utilizan la interfaz ILogger no genérica e incluso pueden compartir una instancia del mismo registrador, y algunos toman un ILogger<T> en su ctor y esperan que el contenedor DI admita tipos genéricos abiertos o el registro explícito de cada uno y cada ILogger<T> usa mi biblioteca.

En este momento, creo que ILogger<T> es el enfoque correcto, y tal vez un ctor que no toma ese argumento y simplemente pasa un Logger Nulo en su lugar. De esa manera, si no se necesita registro, no se usa ninguno. Sin embargo, algunos contenedores DI seleccionan el ctor más grande y, por lo tanto, fallarían de todos modos.

Tengo curiosidad de lo que se supone que debo hacer aquí para crear la menor cantidad de dolor de cabeza para los usuarios, al mismo tiempo que todavía admito el soporte de registro adecuado si lo desea.


Definición

Tenemos 3 interfaces: ILogger , ILoggerProvider e ILoggerFactory . Veamos el código fuente para averiguar sus responsabilidades:

ILogger : la responsabilidad principal es escribir un mensaje de registro de un nivel de registro dado.

ILoggerProvider : tiene una sola responsabilidad y es crear un ILogger .

ILoggerFactory : tiene 2 responsabilidades, crear un ILogger y agregar un ILoggerProvider .

Tenga en cuenta que podemos registrar uno o más proveedores (consola, archivo, etc.) en la fábrica. Cuando creamos un registrador utilizando la fábrica, utiliza a todos los proveedores registrados para crear su registrador correspondiente.

ILoggerFactory factory = new LoggerFactory().AddConsole(); // add console provider factory.AddProvider(new LoggerFileProvider("c://log.txt")); // add file provider Logger logger = factory.CreateLogger(); // <-- creates a console logger and a file logger

Así que Logger mantiene una lista de registradores, y escribe el mensaje de registro a todos ellos. Mirando el código fuente del ILoggers , podemos confirmar que el registrador tiene una matriz de ILoggers (es decir, LoggerInformation []), y al mismo tiempo está implementando la interfaz de ILogger .

Inyección de dependencia

La documentación de MS proporciona 2 métodos para inyectar un registrador:

1. Inyectar la fábrica:

public TodoController(ITodoRepository todoRepository, ILoggerFactory logger) { _todoRepository = todoRepository; _logger = logger.CreateLogger("TodoApi.Controllers.TodoController"); }

crea un registrador con Category = TodoApi.Controllers.TodoController .

2. Inyectando un ILogger<T> genérico ILogger<T> :

public TodoController(ITodoRepository todoRepository, ILogger<TodoController> logger) { _todoRepository = todoRepository; _logger = logger; }

crea un registrador con Categoría = nombre de tipo totalmente calificado de T

En mi opinión, lo que hace que la documentación sea confusa es que no menciona nada sobre la inyección de un ILogger no genérico. En el mismo ejemplo anterior, estamos inyectando un ITodoRepository no genérico, y aún así no explica por qué no estamos haciendo lo mismo para ILogger .

Según Mark Seemann :

Un Constructor de inyección no debe hacer más que recibir las dependencias.

Inyectar una fábrica en el controlador no es un buen método, ya que no es responsabilidad del controlador inicializar el registrador (violación de SRP). Al mismo tiempo, inyectar un ILogger<T> genérico ILogger<T> agrega ruido innecesario. Ver la publicación de Steven en el blog Simple Injector

Lo que se debe inyectar (al menos de acuerdo con el artículo anterior) es un ILogger no genérico, pero eso no es algo que el Contenedor de DI incorporado de Microsoft pueda hacer, y necesita usar una Biblioteca de DI de terceros.

Este es otro artículo de Nikola Malovic, en el que explica sus 5 leyes de IoC.

4ta ley de Nikola de la IoC

Cada constructor de una clase que se esté resolviendo no debería tener otra implementación que no sea aceptar un conjunto de sus propias dependencias.


El ILogger<T> fue el real que está hecho para DI. El ILogger vino para ayudar a implementar el patrón de fábrica con mucha más facilidad, en lugar de que escribiera por su cuenta toda la lógica de DI y de fábrica, esa fue una de las decisiones más inteligentes en el núcleo de asp.net.

Puedes elegir entre:

ILogger<T> si necesita utilizar patrones de fábrica y DI en su código o puede usar el ILogger , para implementar un registro simple sin necesidad de DI.

dado que, el ILoggerProvider es solo un puente para manejar cada uno de los mensajes del registro registrado. No hay necesidad de usarlo, ya que no afecta a nada de lo que debe intervenir en el código. Escucha el ILoggerProvider registrado y maneja los mensajes. Eso es todo.


El enfoque predeterminado es ILogger<T> . Esto significa que en el registro los registros de la clase específica serán claramente visibles porque incluirán el nombre completo de la clase como contexto. Por ejemplo, si el nombre completo de su clase es MyLibrary.MyClass , obtendrá esto en las entradas de registro creadas por esta clase. Por ejemplo:

MyLibrary.MyClass: Información: Mi registro de información

Debería usar ILoggerFactory si desea especificar su propio contexto. Por ejemplo, todos los registros de su biblioteca tienen el mismo contexto de registro en lugar de cada clase. Por ejemplo:

loggerFactory.CreateLogger("MyLibrary");

Y luego el registro se verá así:

MyLibrary: Información: Mi registro de información

Si lo hace en todas las clases, el contexto será solo MyLibrary para todas las clases. Me imagino que querría hacer eso para una biblioteca si no quiere exponer la estructura interna de la clase en los registros.

Respecto al registro opcional. Creo que siempre debe exigir ILogger o ILoggerFactory en el constructor y dejar que el consumidor de la biblioteca lo apague o proporcione un registrador que no haga nada en la inyección de dependencia si no quieren el registro. Es muy fácil desactivar el registro para un contexto específico en la configuración. Por ejemplo:

{ "Logging": { "LogLevel": { "Default": "Warning", "MyLibrary": "None" } } }


Para el diseño de bibliotecas un buen enfoque sería:

1.No obligue a los consumidores a inyectar logger en sus clases. Simplemente crea otro ctor pasando NullLoggerFactory.

class MyClass { private readonly ILoggerFactory _loggerFactory; public MyClass():this(NullLoggerFactory.Instance) { } public MyClass(ILoggerFactory loggerFactory) { this._loggerFactory = loggerFactory ?? NullLoggerFactory.Instance; } }

2. Limite la cantidad de categorías que utiliza cuando crea los registradores para permitir que los consumidores configuren fácilmente el filtrado de registros. this._loggerFactory.CreateLogger(Consts.CategoryName)


Siguiendo con la pregunta, creo que ILogger<T> es la opción correcta, considerando el inconveniente de otras opciones:

  1. La inyección de ILoggerFactory obliga a su usuario a ceder el control de la fábrica de registradores globales mutables a su biblioteca de clases. Además, al aceptar ILoggerFactory su clase ahora puede escribir en el registro con cualquier nombre de categoría arbitrario con el método CreateLogger . Si bien ILoggerFactory generalmente está disponible como un singleton en el contenedor DI, yo como usuario dudaría de por qué cualquier biblioteca necesitaría usarlo.
  2. Si bien el método ILoggerProvider.CreateLogger ve así, no está diseñado para ser inyectado. Se utiliza con ILoggerFactory.AddProvider para que la fábrica pueda crear ILogger agregado que escribe en múltiples ILogger creados a partir de cada proveedor registrado. Esto queda claro cuando inspecciona la implementación de LoggerFactory.CreateLogger
  3. Aceptar ILogger también parece ser el camino a seguir, pero es imposible con .NET Core DI. Esto realmente suena como la razón por la que necesitaban proporcionar ILogger<T> en primer lugar.

Así que, después de todo, no tenemos una mejor opción que ILogger<T> , si tuviéramos que elegir entre esas clases.

Otro enfoque sería inyectar otra cosa que envuelva ILogger no genérico, que en este caso no debería ser genérico. La idea es que al envolverlo con su propia clase, tome el control total de cómo el usuario podría configurarlo.


Todos esos son válidos, excepto para el proveedor de ILogger. ILogger e ILogger <T> son lo que se supone que debes usar para el registro. Para obtener un ILogger, utiliza un ILoggerFactory. El ILogger <T> es un acceso directo para obtener un registrador para una categoría en particular (acceso directo para el tipo como categoría).

Cuando usa el ILogger para realizar el registro, cada uno de los proveedores registrados de ILogger tiene la oportunidad de manejar ese mensaje de registro. No es realmente válido para consumir código para llamar directamente al ILoggerProvider.