Dois-je prendre ILogger, ILogger , ILoggerFactory ou ILoggerProvider pour une bibliothèque?

Cela peut être quelque peu lié à Pass ILogger ou ILoggerFactory aux constructeurs d’AspNet Core? , cependant, il s’agit spécifiquement de la conception de la bibliothèque , et non de la façon dont l’application qui utilise ces bibliothèques implémente sa journalisation.

J’écris une bibliothèque .net Standard 2.0 qui sera installée via Nuget, et pour permettre aux utilisateurs de cette bibliothèque d’obtenir des informations de débogage, je dépend de Microsoft.Extensions.Logging.Abstractions pour permettre l’injection d’un enregistreur standardisé.

Cependant, je vois plusieurs interfaces, et l’exemple de code sur le Web utilise parfois ILoggerFactory et crée un enregistreur dans le ctor de la classe. Il y a aussi ILoggerProvider qui ressemble à une version en lecture seule de Factory, mais les implémentations peuvent ou non implémenter les deux interfaces, donc je devrais choisir. (Factory semble plus commun que Provider).

Certains codes que j’ai vus utilisent l’interface non générique ILogger et peuvent même partager une instance du même enregistreur, et d’autres prennent un ILogger dans leur moteur et s’attendent à ce que le conteneur DI prenne en charge les types génériques ouverts ou l’enregistrement explicite de chaque et chaque ILogger que ma bibliothèque utilise.

En ce moment, je pense ILogger est la bonne approche, et peut-être un créateur qui ne prend pas cet argument et passe simplement un Null Logger à la place. De cette façon, si aucune journalisation n’est nécessaire, aucune n’est utilisée. Cependant, certains conteneurs DI sélectionnent le plus grand ctor et échoueraient de toute façon.

Je suis curieux de savoir ce que je suis censé faire ici pour créer le moins de casse-tête pour les utilisateurs, tout en permettant une prise en charge de la journalisation appropriée si vous le souhaitez.

Définition

OK, nous avons 3 interfaces: ILogger , ILoggerProvider et ILoggerFacory . Regardons ( source Microsoft.Extensions.Logging.Abstractions ) pour connaître leurs responsabilités.

ILogger : la principale responsabilité est d’écrire un message de journal d’un niveau de journal donné.

ILoggerProvider : n’a qu’une seule responsabilité et consiste à créer un ILogger .

ILoggerFactory : a 2 responsabilités, pour créer un ILogger et append un ILoggerProvider .

Notez que nous pouvons enregistrer un ou plusieurs fournisseurs (console, fichier, etc.) à l’usine. Lorsque nous créons un enregistreur en usine, il utilise les fournisseurs enregistrés pour créer les enregistreurs correspondants.

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

Ainsi, Logger conserve une liste de consignateurs et écrit le message de consignation à tous. En regardant le code source de Logger, nous pouvons confirmer que Logger possède un tableau d' ILoggers (par exemple, LoggerInformation []), et en même temps, il implémente l'interface ILogger .


Injection de dépendance

La documentation MS fournit 2 méthodes pour injecter un enregistreur:

1. Injection de l'usine:

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

crée un enregistreur avec Category = TodoApi.Controllers.TodoController .

2. Injecter un ILogger générique ILogger :

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

crée un enregistreur avec Category = nom complet du type TodoController


À mon avis, ce qui rend la documentation confuse, c'est qu'elle ne mentionne rien sur l'injection d'un ILogger non générique. Dans le même exemple ci-dessus, nous injectons un ITodoRepository non générique, mais cela n'explique pas pourquoi nous ne faisons pas la même chose pour ILogger .

Selon les constructeurs d'injecteurs devraient être simples :

Un constructeur d'injection ne devrait pas faire plus que recevoir les dépendances.

L'injection d'une fabrique dans un contrôleur n'est pas une bonne approche, car il ne s'agit pas d'initialiser l'enregistreur. En même temps, l'injection d'un ILogger générique ILogger ajoute du bruit inutile. Voir le post de Steven sur le blog Simple Injector

Ce qui devrait être injecté (au moins selon l'article ci-dessus) est un ILogger non générique, mais vous ne pouvez pas utiliser le conteneur DI intégré de Microsoft et vous devez utiliser une bibliothèque DI tierce.

Ceci est un autre article de Nikola Malovic, dans lequel il explique ses 5 lois de l'IoC.

4ème loi de Nikola de l'IoC

Tout constructeur d'une classe en cours de résolution ne devrait pas avoir d'implémentation autre que d'accepter un ensemble de ses propres dépendances.

Celles-ci sont toutes valables sauf pour ILoggerProvider. ILogger et ILogger sont ce que vous êtes censé utiliser pour la journalisation. Pour obtenir un ILogger, vous utilisez un ILoggerFactory. ILogger est un raccourci pour obtenir un enregistreur pour une catégorie particulière (raccourci pour le type en tant que catégorie).

Lorsque vous utilisez ILogger pour effectuer une journalisation, chacun des ILoggerProviders enregistrés peut gérer ce message de journal. La consommation de code pour appeler directement ILoggerProvider n’est pas vraiment valable.

ILogger était celui qui est fait pour DI. L’ILogger est venu afin de faciliter la mise en œuvre du modèle d’usine, au lieu que vous écriviez vous-même toute la logique DI et Factory, c’était l’une des décisions les plus intelligentes du kernel asp.net.

Vous pouvez choisir entre:

ILogger si vous avez besoin d’utiliser des modèles d’usine et DI dans votre code ou vous pouvez utiliser ILogger , pour implémenter une journalisation simple sans DI.

étant donné que ILoggerProvider n’est qu’un pont pour gérer chacun des messages du journal enregistré. Il n’y a pas besoin de l’utiliser, car cela n’affecte en rien l’intervention du code, il écoute le ILoggerProvider enregistré et gère les messages. C’est à peu près ça.

En ILogger à la question, je pense que ILogger est la bonne option, considérant les inconvénients des autres options:

  1. Injecter ILoggerFactory oblige votre utilisateur à céder le contrôle de la fabrique mondiale de loggers mutables à votre bibliothèque de classes. De plus, en acceptant ILoggerFactory votre classe peut maintenant écrire dans un journal avec n’importe quel nom de catégorie arbitraire avec la méthode CreateLogger . Bien ILoggerFactory soit généralement disponible en tant que singleton dans le conteneur DI, en tant qu’utilisateur, je doute que toute bibliothèque ILoggerFactory utiliser.
  2. Bien que la méthode ILoggerProvider.CreateLogger ressemble, elle n’est pas destinée à l’injection. Il est utilisé avec ILoggerFactory.AddProvider afin que la fabrique puisse créer un ILogger agrégé qui écrit sur plusieurs ILogger créés à partir de chaque fournisseur enregistré. Ceci est clair lorsque vous inspectez l’implémentation de LoggerFactory.CreateLogger
  3. Accepter ILogger ressemble aussi à la voie à suivre, mais c’est impossible avec .NET Core DI. Cela semble être la raison pour laquelle ils ont dû fournir ILogger en premier lieu.

Donc, après tout, nous n’avons pas de meilleur choix que ILogger , si nous devions choisir parmi ces classes.

Une autre approche consisterait à injecter autre chose qui ILogger non générique, qui dans ce cas devrait être non générique. L’idée est qu’en l’enveloppant avec votre propre classe, vous prenez le contrôle total de la manière dont l’utilisateur peut le configurer.

Pour la conception de la bibliothèque, une bonne approche serait:

1.Ne forcez pas les consommateurs à injecter un enregistreur dans vos cours. Créez simplement un autre moteur en transmettant NullLoggerFactory.

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

2. Limitez le nombre de catégories que vous utilisez lorsque vous créez des enregistreurs pour permettre aux utilisateurs de configurer facilement les journaux de filtrage. this._loggerFactory.CreateLogger(Consts.CategoryName)

L’approche par défaut est ILogger . Cela signifie que dans le journal, les journaux de la classe spécifique seront clairement visibles car ils incluront le nom complet de la classe comme contexte. Par exemple, si le nom complet de votre classe est MyLibrary.MyClass vous le trouverez dans les entrées de journal créées par cette classe. Par exemple:

MyLibrary.MyClass: Information: Mon journal d’information

Vous devez utiliser ILoggerFactory si vous souhaitez spécifier votre propre contexte. Par exemple, tous les journaux de votre bibliothèque ont le même contexte de journalisation que chaque classe. Par exemple:

 loggerFactory.CreateLogger("MyLibrary"); 

Et puis le journal ressemblera à ceci:

MyLibrary: Information: Mon journal d’information

Si vous faites cela dans toutes les classes, le contexte sera juste MyLibrary pour toutes les classes. J’imagine que vous voudriez faire cela pour une bibliothèque si vous ne voulez pas exposer la structure de classe interne dans les journaux.

En ce qui concerne la journalisation en option. Je pense que vous devriez toujours exiger ILogger ou ILoggerFactory dans le constructeur et laisser le consommateur de la bibliothèque l’éteindre ou fournir un enregistreur qui ne fait rien dans l’dependency injection s’il ne veut pas se connecter. Il est très facile de tourner la journalisation pour un contexte spécifique dans la configuration. Par exemple:

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