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.
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
.
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ériqueILogger
: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
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:
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. 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
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" } } }