web-dev-qa-db-fra.com

Devrais-je prendre ILogger, ILogger <T>, ILoggerFactory ou ILoggerProvider pour une bibliothèque?

Cela peut être en quelque sorte lié à Passer ILogger ou ILoggerFactory aux constructeurs dans AspNet Core? , mais ceci concerne spécifiquement la conception de la bibliothèque , pas sur la manière 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 personnes utilisant cette bibliothèque d'obtenir des informations de débogage, je compte sur Microsoft.Extensions.Logging.Abstractions pour autoriser un enregistreur standardisé à injecter.

Cependant, je vois plusieurs interfaces et un exemple de code sur le Web utilise parfois ILoggerFactory et crée un journal 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. (L'usine semble plus commune que le fournisseur).

Certains codes que j'ai vus utilisent l'interface ILogger non générique et peuvent même partager une instance du même enregistreur. Certains prennent un ILogger<T> dans leur ctor et attendent du conteneur DI qu'il prenne en charge les types génériques ouverts. ou enregistrement explicite de chaque ILogger<T> variante utilisée par ma bibliothèque.

Actuellement, je pense que ILogger<T> est la bonne approche, et peut-être un acteur qui ne prend pas cet argument et ne fait que passer un Null Logger. De cette façon, si aucune journalisation n’est nécessaire, aucune n’est utilisée. Cependant, certains conteneurs DI choisissent le plus gros fabricant et échoueraient de toute façon.

Je suis curieux de savoir ce que je suis supposé faire ici pour créer le moins de maux de tête possible pour les utilisateurs, tout en permettant un support de journalisation approprié, le cas échéant.

82
Michael Stum

Définition

Nous avons 3 interfaces: ILogger, ILoggerProvider et ILoggerFactory. Regardons le code source pour connaître leurs responsabilités:

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

ILoggerProvider: n'a qu'une responsabilité: créer un ILogger.

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

Notez que nous pouvons enregistrer un ou plusieurs fournisseurs (console, fichier, etc.) auprès de l’usine. Lorsque nous créons un enregistreur à l'aide de la fabrique, tous les fournisseurs enregistrés sont créés pour créer l'enregistreur correspondant.

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

Donc, Logger maintient une liste de consignateurs et écrit le message de journalisation à tous. En regardant le code source de Logger nous pouvons confirmer que Logger possède un tableau de ILoggers (c'est-à-dire LoggerInformation []), et met en même temps en œuvre ILogger interface.


Injection de dépendance

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

1. Injection en 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. Injection d'un générique ILogger<T>:

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

crée un enregistreur avec Category = nom de type complet de T


À mon avis, ce qui rend la documentation confuse, c'est qu'elle ne mentionne rien sur l'injection d'un non-générique, ILogger. 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 Mark Seemann :

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

L'injection d'une usine dans le contrôleur n'est pas une bonne approche, car ce n'est pas la responsabilité du contrôleur d'initialiser l'enregistreur (violation de SRP). En même temps, l'injection d'un ILogger<T> générique ajoute du bruit inutile. Voir le blog Simple Injector pour plus de détails: Quel est le problème avec l'abstraction ASP.NET Core DI??

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

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

4ème loi de Nikola sur IoC

Chaque constructeur d'une classe en cours de résolution ne doit avoir aucune implémentation autre que l'acceptation d'un ensemble de ses propres dépendances.

69
Hooman Bahreini

Ceux-ci sont tous valables sauf pour ILoggerProvider. ILogger et ILogger <T> sont ce que vous êtes censé utiliser pour la journalisation. Pour obtenir un ILogger, vous utilisez un ILoggerFactory. ILogger <T> 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 la journalisation, chacun des ILoggerProviders enregistrés a la possibilité de gérer ce message de journalisation. Ce n'est pas vraiment valable pour un code consommateur d'appeler directement ILoggerProvider.

24
davidfowl

Le ILogger<T> était celui qui a été créé pour DI. ILogger est venu pour aider à mettre en œuvre le modèle d'usine beaucoup plus facilement, au lieu d'écrire vous-même toute la logique DI et Factory, ce qui était l'une des décisions les plus intelligentes du cœur asp.net.

Vous pouvez choisir entre:

ILogger<T> si vous avez besoin d'utiliser des modèles d'usine et DI dans votre code o, vous pouvez utiliser la ILogger pour implémenter une journalisation simple sans ID nécessaire.

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

8
Barr J

Pour la conception des bibliothèques, une bonne approche serait:

1.Ne forcez pas les consommateurs à injecter l’enregistreur dans vos cours. Créez simplement un autre ctor en passant par 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 consommateurs de configurer facilement le filtrage des journaux. this._loggerFactory.CreateLogger(Consts.CategoryName)

5
Ihar Yakimush

Pour en revenir à la question, je pense que ILogger<T> est la bonne option, compte tenu des inconvénients des autres options:

  1. L'injection ILoggerFactory force votre utilisateur à donner le contrôle de la fabrique de journalisation globale mutable à votre bibliothèque de classes. De plus, en acceptant ILoggerFactory, votre classe peut maintenant écrire pour se connecter avec n’importe quel nom de catégorie arbitraire avec la méthode CreateLogger. Bien que ILoggerFactory soit généralement disponible en tant que singleton dans le conteneur DI, en tant qu'utilisateur, je ne saurais trop pourquoi une bibliothèque aurait besoin de l'utiliser.
  2. Bien que la méthode ILoggerProvider.CreateLogger lui ressemble, elle n’est pas destinée à l’injection. Il est utilisé avec ILoggerFactory.AddProvider afin que la fabrique puisse créer un groupe ILogger agrégé qui écrit sur plusieurs ILogger créés à partir de chaque fournisseur enregistré. Ceci est clair lorsque vous inspectez la mise en oeuvre de LoggerFactory.CreateLogger
  3. Accepter ILogger semble également être la solution, mais il est impossible avec .NET Core DI. Cela ressemble en fait à la raison pour laquelle ils avaient besoin de fournir ILogger<T> en premier lieu.

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

Une autre approche consisterait à injecter quelque chose d'autre qui englobe ILogger non générique, qui dans ce cas devrait être non générique. L’idée est qu’en l’emballant avec votre propre classe, vous maîtrisez totalement la manière dont l’utilisateur peut le configurer.

5
tia

L'approche par défaut est censée être ILogger<T>. 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 en tant que contexte. Par exemple, si le nom complet de votre classe est MyLibrary.MyClass, vous l'obtiendrez dans les entrées de journal créées par cette classe. Par exemple:

MyLibrary.MyClass: Information: Mon journal d'informations

Vous devez utiliser le ILoggerFactory si vous voulez spécifier votre propre contexte. Par exemple, tous les journaux de votre bibliothèque ont le même contexte de journal que toutes les classes. Par exemple:

loggerFactory.CreateLogger("MyLibrary");

Et puis le journal ressemblera à ceci:

MyLibrary: Information: Mon journal d'informations

Si vous faites cela dans toutes les classes, le contexte sera simplement 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 optionnelle. Je pense que vous devriez toujours exiger ILogger ou ILoggerFactory dans le constructeur et laisser le consommateur de la bibliothèque le désactiver ou fournir un enregistreur qui ne fait rien dans l'injection de dépendance s'il ne veut pas se connecter. Il est très facile d'activer la journalisation pour un contexte spécifique dans la configuration. Par exemple:

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