185 votes

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

Cela peut être quelque peu lié à Passer ILogger ou ILoggerFactory aux constructeurs dans AspNet Core ? Cependant, il s'agit ici spécifiquement de Conception de la bibliothèque et non sur la manière dont l'application qui utilise ces bibliothèques met en œuvre sa journalisation.

J'écris une bibliothèque .net Standard 2.0 qui sera installée via Nuget, et pour permettre aux personnes qui utilisent cette bibliothèque d'obtenir des informations de débogage, je fais appel à Microsoft.Extensions.Logging.Abstractions pour permettre l'injection d'un logger standardisé.

Cependant, je vois des interfaces multiples, et les exemples de code sur le web utilisent parfois la fonction ILoggerFactory et crée un logger dans le ctor de la classe. Il y a aussi ILoggerProvider qui ressemble à une version en lecture seule de la Factory, mais les implémentations peuvent ou non implémenter les deux interfaces, donc je devrais choisir. (L'interface Factory semble plus courante que l'interface Provider).

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

Pour l'instant, je pense que ILogger<T> est la bonne approche, et peut-être un ctor qui ne prend pas cet argument et passe un Logger Null à la place. De cette façon, si aucun enregistrement n'est nécessaire, aucun n'est utilisé. Cependant, certains conteneurs DI choisissent le plus grand ctor et échouent donc de toute façon.

Je suis curieux de savoir ce que je supposé afin de créer le moins de maux de tête possible pour les utilisateurs, tout en permettant un support de journalisation adéquat si nécessaire.

214voto

Hooman Bahreini Points 3202

Définition

Nous disposons de 3 interfaces : ILogger , ILoggerProvider y ILoggerFactory . Examinons les code source pour connaître leurs responsabilités :

ILogger : est responsable de l'écriture d'un message de journal d'une Niveau d'enregistrement .

ILoggerProvider : est responsable de la création d'une instance de ILogger (vous n'êtes pas censé utiliser ILoggerProvider directement pour créer un logger)

ILoggerFactory : vous pouvez enregistrer un ou plusieurs ILoggerProvider à la fabrique, qui à son tour les utilise tous pour créer une instance de ILogger . ILoggerFactory contient une collection de ILoggerProviders .

Dans l'exemple ci-dessous, nous enregistrons deux fournisseurs (console et fichier) auprès de la fabrique. Lorsque nous créons un logger, la fabrique utilise ces deux fournisseurs pour créer une instance de Logger :

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

Ainsi, l'enregistreur lui-même maintient une collection de ILogger et écrit le message de journal sur chacun d'entre eux. Regarder le code source de l'enregistreur nous pouvons confirmer que Logger a un tableau de ILoggers (c'est-à-dire LoggerInformation[] ) et, en même temps, il met en œuvre le programme ILogger l'interface.


Injection de dépendance

Documentation de l'EM propose deux méthodes pour injecter un logger :

1. Injection de l'usine :

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

crée un Logger 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 logger avec Category = nom de type pleinement qualifié de TodoController


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

Según Mark Seemann :

Un constructeur d'injection ne doit pas faire plus que de recevoir le code dépendances.

Injecter une fabrique dans le contrôleur n'est pas une bonne approche, car il n'est pas de la responsabilité du contrôleur d'initialiser l'enregistreur (violation de SRP). En même temps, l'injection d'un objet générique ILogger<T> ajoute des bruits inutiles. Voir aussi Injecteurs simples pour plus de détails : Qu'est-ce qui ne va pas avec l'abstraction DI d'ASP.NET Core ?

Ce qui devrait être injecté (du moins selon l'article ci-dessus) est un médicament non générique, le ILogger mais ce n'est pas quelque chose que le conteneur DI intégré de Microsoft peut faire, et vous devez utiliser une bibliothèque DI tierce. Ces deux expliquent comment utiliser des bibliothèques tierces avec .NET Core.


Il s'agit de autre article de Nikola Malovic, dans lequel il explique ses 5 lois de l'IoC.

La 4ème loi de Nikola sur l'IoC

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

14 votes

Votre réponse et l'article de Steven sont à la fois les plus corrects et les plus déprimants.

1 votes

Si AddConsole n'est pas trouvé, assurez-vous que NuGet "Microsoft.Extensions.Logging.Console" est installé, que votre fichier source inclut using Microsoft.Extensions.Logging; . Enfin, essayez ILoggerFactory factory = LoggerFactory.Create(builder => builder.AddConsole());

1 votes

Si LoggerFileProvider n'est pas trouvé, utiliser FileLoggerProvider de NuGet "NReco.Logging.File".

40voto

dfowler Points 16530

Elles sont toutes valables, à l'exception de ILoggerProvider . ILogger y ILogger<T> sont ceux que vous êtes censés 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 le ILogger pour effectuer la journalisation, chaque ILoggerProvider a la possibilité de traiter ce message. Il n'est pas vraiment valable que le code de consommation fasse appel à la fonction ILoggerProvider directement.

0 votes

Merci. Cela me fait penser qu'un ILoggerFactory serait un excellent moyen de câbler DI pour les consommateurs en n'ayant qu'une seule dépendance ( "Donnez-moi une usine et je créerai mes propres bûcherons. ), mais empêcherait l'utilisation d'un logger existant (à moins que le consommateur n'utilise un wrapper). La prise d'un ILogger - générique ou non - permet au consommateur de me donner un logger qu'il a spécialement préparé, mais rend la configuration DI potentiellement beaucoup plus complexe (à moins d'utiliser un conteneur DI qui supporte Open Generics). Cela vous semble-t-il correct ? Dans ce cas, je pense que je vais opter pour la fabrique.

4 votes

@MichaelStum - Je ne suis pas sûr de suivre votre logique. Vous attendez de vos consommateurs qu'ils utilisent un système d'identification automatique, mais vous voulez ensuite prendre le relais et gérer manuellement les dépendances au sein de votre bibliothèque ? Pourquoi cela semble-t-il être la bonne approche ?

0 votes

@Damien_The_Unbeliever C'est un bon point. Prendre une usine semble bizarre. Je pense qu'au lieu de prendre un ILogger<T> Je prendrai un ILogger<MyClass> ou même simplement ILogger à la place - de cette façon, l'utilisateur peut le câbler avec un seul enregistrement, et sans avoir besoin de génériques ouverts dans son conteneur DI. En faveur de l'approche non générique ILogger mais nous ferons de nombreuses expériences au cours du week-end.

12voto

Barr J Points 5394

En ILogger<T> était celui qui a été conçu pour l'assurance-invalidité. Les ILogger<T> est venu pour aider à mettre en œuvre le modèle de fabrique beaucoup plus facilement, au lieu que vous écriviez vous-même toute la logique DI et Factory, ce qui a été l'une des décisions les plus intelligentes dans ASP.NET Core.

Vous avez le choix entre :

ILogger<T> si vous avez besoin d'utiliser les modèles factory et DI dans votre code ou vous pouvez utiliser le ILogger pour mettre en œuvre une journalisation simple sans DI.

Compte tenu de ce qui précède, le ILoggerProvider n'est qu'une passerelle permettant de gérer chacun des messages du journal enregistré. Il n'est pas nécessaire de l'utiliser, car il n'a aucun effet sur lequel vous devriez intervenir dans le code. Il écoute les messages de ILoggerProvider et traite les messages. C'est à peu près tout.

2 votes

Quel est le rapport entre ILogger et ILogger<T> et DI ? L'un ou l'autre serait injecté, non ?

1 votes

Il s'agit en fait de ILogger<TCategoryName> dans MS Docs. Il dérive de ILogger et n'ajoute aucune nouvelle fonctionnalité, si ce n'est le nom de la classe à enregistrer. Il fournit également un type unique afin que l'interface utilisateur injecte le logger correctement nommé. Les extensions Microsoft étendent la classe non générique this ILogger .

9voto

tia Points 5566

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

  1. Injection ILoggerFactory obligent l'utilisateur à céder le contrôle de la fabrique d'enregistreurs globale mutable à votre bibliothèque de classes. De plus, en acceptant ILoggerFactory votre classe peut maintenant écrire dans le journal avec n'importe quel nom de catégorie arbitraire avec CreateLogger méthode. Tout en ILoggerFactory est généralement disponible en tant que singleton dans un conteneur DI, en tant qu'utilisateur, je ne vois pas pourquoi une bibliothèque aurait besoin de l'utiliser.
  2. Alors que la méthode ILoggerProvider.CreateLogger n'en a pas l'air, il n'est pas destiné à l'injection. Il est utilisé avec ILoggerFactory.AddProvider afin que la fabrique puisse créer des ILogger qui écrit dans plusieurs ILogger créés à partir de chaque fournisseur enregistré. Cela apparaît clairement lorsque l'on inspecte la mise en œuvre de LoggerFactory.CreateLogger
  3. Acceptation ILogger semble également être la solution, mais elle est impossible avec .NET Core DI. Cela semble en fait être la raison pour laquelle ils ont eu besoin de fournir le module ILogger<T> à la première place.

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 envelopperait les éléments non génériques de la norme ILogger qui, dans ce cas, doit être non générique. L'idée est qu'en l'enveloppant dans votre propre classe, vous prenez le contrôle total de la façon dont l'utilisateur peut la configurer.

9voto

Ihar Yakimush Points 292

Pour la conception d'une bibliothèque, une bonne approche serait la suivante :

  1. Ne forcez pas les consommateurs à injecter un logger dans vos classes. Créez simplement un autre constructeur 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 afin de permettre aux consommateurs de configurer facilement le filtrage des enregistrements.

    this._loggerFactory.CreateLogger(Consts.CategoryName)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X