Injection de dépendance et enregistreurs nommés

Je suis intéressé à en apprendre davantage sur la façon dont les gens injectent la journalisation avec des plates-formes d’dependency injection. Bien que les liens ci-dessous et mes exemples se réfèrent à log4net et à Unity, je ne vais pas nécessairement les utiliser. Pour l’dependency injection / IOC, j’utiliserai probablement MEF car c’est la norme sur laquelle repose le rest du projet (grand).

Je suis très débutant dans l’dependency injection / ioc et je suis assez nouveau dans C # et .NET (j’ai écrit très peu de code de production dans C # /. NET après environ 10 ans de VC6 et VB6). J’ai fait beaucoup de recherches sur les différentes solutions de journalisation disponibles. Je pense donc que je maîsortingse bien leurs fonctionnalités. Je ne suis juste pas assez familier avec la mécanique réelle pour obtenir une dépendance injectée (ou, peut-être plus “correctement”, obtenir une version abstraite d’une dépendance injectée).

J’ai vu d’autres articles liés à la journalisation et / ou à l’dependency injection comme: les interfaces d’dependency injection et de journalisation

Meilleures pratiques de journalisation

À quoi ressemblerait une classe Log4Net Wrapper?

à propos de log4net et de la configuration d’Unity IOC

Ma question ne concerne pas spécifiquement “Comment injecter la plate-forme de journalisation xxx à l’aide de l’outil ioc yyy?” Plutôt, je suis intéressé par la façon dont les gens ont géré la plate-forme de journalisation (comme c’est souvent le cas, mais pas toujours recommandé) et la configuration (par exemple, app.config). Par exemple, en utilisant log4net comme exemple, je pourrais configurer (dans app.config) un certain nombre d’enregistreurs puis obtenir ces enregistreurs (sans dependency injections) de la manière standard d’utilisation du code comme ceci:

private static readonly ILog logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); 

Sinon, si mon enregistreur n’est pas nommé pour une classe, mais plutôt pour un domaine fonctionnel, je pourrais le faire:

 private static readonly ILog logger = LogManager.GetLogger("Login"); private static readonly ILog logger = LogManager.GetLogger("Query"); private static readonly ILog logger = LogManager.GetLogger("Report"); 

Donc, je suppose que mes “exigences” seraient quelque chose comme ceci:

  1. Je voudrais isoler la source de mon produit d’une dépendance directe sur une plate-forme de journalisation.

  2. Je voudrais pouvoir résoudre une instance de logger nommée spécifique (partageant probablement la même instance parmi tous les demandeurs de la même instance nommée) soit directement, soit indirectement par une sorte d’dependency injection, probablement MEF.

  3. Je ne sais pas si j’appellerais cela une exigence difficile, mais j’aimerais pouvoir obtenir un enregistreur nommé (différent de l’enregistreur de classe) à la demande. Par exemple, je pourrais créer un enregistreur pour ma classe en fonction du nom de la classe, mais une méthode nécessite des diagnostics particulièrement lourds que je souhaiterais contrôler séparément. En d’autres termes, je souhaiterais qu’une seule classe «dépende» de deux instances de journalisation distinctes.

Commençons par le numéro 1. J’ai lu un certain nombre d’articles, principalement sur stackoverflow, pour savoir si c’est une bonne idée ou non. Voir le lien “meilleures pratiques” ci-dessus et aller au commentaire de jeffrey hantin pour un sharepoint vue sur la raison pour laquelle il est mauvais d’envelopper log4net. Si vous enveloppiez (et si vous pouviez envelopper efficacement), seriez-vous emballé ssortingctement aux fins d’injection / de retrait de la dépendance directe? Ou voudriez-vous également supprimer certaines ou toutes les informations de log4net app.config?

Disons que je veux utiliser System.Diagnostics, je voudrais probablement implémenter un enregistreur basé sur une interface (peut-être même en utilisant l’interface “commune” ILogger / ILog), probablement basée sur TraceSource, pour que je puisse l’injecter. Implémenteriez-vous l’interface, par exemple sur TraceSource, et utilisez simplement les informations System.Diagnostics app.config telles quelles?

Quelque chose comme ça:

 public class MyLogger : ILogger { private TraceSource ts; public MyLogger(ssortingng name) { ts = new TraceSource(name); } public void ILogger.Log(ssortingng msg) { ts.TraceEvent(msg); } } 

Et l’utiliser comme ça:

 private static readonly ILogger logger = new MyLogger("stackoverflow"); logger.Info("Hello world!") 

Passons au numéro 2 … Comment résoudre une instance de journal nommée particulière? Dois-je simplement utiliser les informations app.config de la plate-forme de journalisation que je choisis (c.-à-d. Résoudre les enregistreurs en fonction du schéma de nommage dans le fichier app.config)? Donc, dans le cas de log4net, puis-je préférer “injecter” LogManager (notez que je sais que ce n’est pas possible car c’est un object statique)? Je pourrais envelopper LogManager (l’appeler MyLogManager), lui donner une interface ILogManager, puis résoudre l’interface MyLogManager.ILogManager. Mes autres objects peuvent avoir une dépendance (importation dans le langage MEF) sur ILogManager (exportation depuis l’assemblage où elle est implémentée). Maintenant, je pourrais avoir des objects comme celui-ci:

 public class MyClass { private ILogger logger; public MyClass([Import(typeof(ILogManager))] logManager) { logger = logManager.GetLogger("MyClass"); } } 

A chaque appel d’ILogManager, il serait directement délégué au LogManager de log4net. Sinon, le LogManager encapsulé peut-il prendre les instances ILogger basées sur app.config et les append au nom du conteneur MEF (a?). Plus tard, lorsqu’un enregistreur du même nom est demandé, le LogManager encapsulé est interrogé pour ce nom. Si ILogger est là, il est résolu de cette façon. Si cela est possible avec MEF, y a-t-il un avantage à le faire?

Dans ce cas, en réalité, seul ILogManager est “injecté” et il peut dissortingbuer des instances ILogger comme le fait normalement log4net. Comment ce type d’injection (essentiellement d’usine) se compare-t-il à l’injection des instances de journalisation nommées? Cela permet une utilisation plus facile du fichier app.config de log4net (ou d’une autre plateforme de journalisation).

Je sais que je peux obtenir des instances nommées du conteneur MEF comme ceci:

 var container = new CompositionContainer(); ILogger logger = container.GetExportedValue("ThisLogger"); 

Mais comment puis-je obtenir les instances nommées dans le conteneur? Je connais le modèle basé sur les atsortingbuts où je pourrais avoir différentes implémentations d’ILogger, chacune étant nommée (via un atsortingbut MEF), mais cela ne m’aide pas vraiment. Existe-t-il un moyen de créer quelque chose comme un app.config (ou une section) qui listerait les enregistreurs (tous de la même implémentation) par leur nom et que MEF pourrait lire? Pourrait-il / devrait-il y avoir un “gestionnaire” central (comme MyLogManager) qui résout les loggers nommés via le app.config sous-jacent, puis insère le logger résolu dans le conteneur MEF? De cette façon, il serait disponible pour quelqu’un d’autre ayant access au même conteneur MEF (bien que sans la connaissance de MyLogManager sur l’utilisation des informations app.config de log4net, il semble que le conteneur ne puisse résoudre directement aucun enregistreur nommé).

Cela a déjà été assez long. J’espère que c’est cohérent. N’hésitez pas à partager des informations spécifiques sur la manière dont vous avez injecté une plate-forme de journalisation dans vos dépendances (nous considérons probablement log4net, NLog, ou quelque chose (si tout va bien) sur System.Diagnostics) dans votre application.

Avez-vous injecté le “gestionnaire” et l’avez-vous renvoyé des instances de l’enregistreur?

Avez-vous ajouté certaines de vos propres informations de configuration dans votre propre section de configuration ou dans la section de configuration de votre plate-forme DI pour faciliter / rendre possible l’injection directe des instances de journalisation (par exemple, vos dépendances sur ILogger plutôt que sur ILogManager).

Qu’en est-il de la présence d’un conteneur statique ou global contenant soit l’interface ILogManager, soit l’ensemble des instances nommées ILogger. Ainsi, plutôt que d’injecter au sens conventionnel (via des données de constructeur, de propriété ou de membre), la dépendance de journalisation est explicitement résolue à la demande. Est-ce un bon ou un mauvais moyen d’injecter des dépendances?

Je marque ceci comme un wiki de communauté car il ne semble pas être une question avec une réponse définitive. Si quelqu’un se sent autrement, n’hésitez pas à le changer.

Merci pour toute aide!

J’utilise Ninject pour résoudre le nom de classe actuel de l’instance de journalisation comme ceci:

 kernel.Bind().To() .WithConstructorArgument("currentClassName", x => x.Request.ParentContext.Request.Service.FullName); 

Le constructeur d’une implémentation NLog pourrait ressembler à ceci:

 public NLogLogger(ssortingng currentClassName) { _logger = LogManager.GetLogger(currentClassName); } 

Je suppose que cette approche devrait fonctionner avec d’autres conteneurs IOC.

On peut aussi utiliser la façade Common.Logging ou la façade Simple Logging .

Les deux utilisent un modèle de style de localisateur de services pour récupérer un ILogger.

Franchement, la journalisation est l’une de ces dépendances que je vois peu ou pas d’injection automatique.

La plupart de mes classes nécessitant des services de journalisation ressemblent à ceci:

 public class MyClassThatLogs { private readonly ILogger log = Slf.LoggerService.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.FullName); } 

En utilisant Simple Logging Facade, j’ai commuté un projet de log4net à NLog et j’ai ajouté une journalisation à partir d’une bibliothèque tierce qui utilisait log4net en plus de la journalisation de mon application utilisant NLog. C’est-à-dire que la façade nous a bien servi.

Une mise en garde difficile à éviter est la perte de fonctionnalités spécifiques à une structure de journalisation ou à une autre, l’exemple le plus fréquent étant peut-être les niveaux de journalisation personnalisés.

Ceci est à l’avantage de tous ceux qui tentent de déterminer comment injecter une dépendance de l’enregistreur lorsque le consignateur que vous souhaitez injecter est fourni avec une plate-forme de journalisation telle que log4net ou NLog. Mon problème était que je ne pouvais pas comprendre comment je pouvais rendre une classe (par exemple, MyClass) dépendante d’une interface de type ILogger quand je savais que la résolution du ILogger spécifique dépendrait de connaître le type de la classe qui dépend de ILogger ( par exemple MyClass). Comment la plateforme / le conteneur DI / Io reçoit-il le bon ILogger?

Eh bien, j’ai regardé la source pour Castle et NInject et j’ai vu comment ils fonctionnent. J’ai aussi regardé AutoFac et StructureMap.

Castle et NInject fournissent tous deux une implémentation de la journalisation. Les deux prennent en charge log4net et NLog. Castle supporte également System.Diagnostics. Dans les deux cas, lorsque la plate-forme résout les dépendances pour un object donné (par exemple, lorsque la plateforme crée MyClass et MyClass dépend d’ILogger), elle délègue la création de la dépendance (ILogger) au “fournisseur” ILogger (le résolveur peut être plus terme commun). L’implémentation du fournisseur ILogger est alors responsable de l’instanciation effective d’une instance de ILogger et de sa restitution, pour ensuite être injectée dans la classe dépendante (par exemple, MyClass). Dans les deux cas, le fournisseur / résolveur connaît le type de la classe dépendante (par exemple, MyClass). Ainsi, lorsque MyClass a été créé et que ses dépendances ont été résolues, le résolveur ILogger sait que la classe est MyClass. Dans le cas de l’utilisation des solutions de journalisation fournies par Castle ou NInject, cela signifie que la solution de journalisation (implémentée en tant que wrapper sur log4net ou NLog) obtient le type (MyClass) et peut donc être déléguée à log4net.LogManager.GetLogger () ou NLog.LogManager.GetLogger (). (Pas 100% sûr de syntaxe pour log4net et NLog, mais vous avez l’idée).

Alors qu’AutoFac et StructureMap ne fournissent pas de fonction de journalisation (du moins que je puisse le dire en regardant), ils semblent pouvoir implémenter des résolveurs personnalisés. Donc, si vous voulez écrire votre propre couche d’abstraction de journalisation, vous pouvez également écrire un résolveur personnalisé correspondant. De cette façon, lorsque le conteneur veut résoudre ILogger, votre résolveur sera utilisé pour obtenir le bon ILogger ET il aura access au contexte actuel (c.-à-d. Quelles dépendances d’object sont actuellement satisfaites – quel object dépend d’ILogger). Obtenez le type de l’object et vous êtes prêt à déléguer la création de ILogger à la plate-forme de journalisation actuellement configurée (que vous avez probablement extraite derrière une interface et pour laquelle vous avez écrit un résolveur).

Donc, quelques points clés que je soupçonnais étaient nécessaires mais que je n’ai pas bien compris avant sont:

  1. En fin de compte, le conteneur DI doit être conscient de la plate-forme de journalisation à utiliser. Généralement, cela se fait en spécifiant que “ILogger” doit être résolu par un “resolver” spécifique à une plate-forme de journalisation (Castle possède donc des résolveurs log4net, NLog et System.Diagnostics (entre autres)). La spécification du résolveur à utiliser peut être effectuée via un fichier de configuration ou par programme.

  2. Le résolveur doit connaître le contexte pour lequel la dépendance (ILogger) est en cours de résolution. En d’autres termes, si MyClass a été créé et qu’il dépend d’ILogger, alors lorsque le résolveur tente de créer le bon ILogger, il doit connaître le type actuel (MyClass). De cette façon, le résolveur peut utiliser l’implémentation de journalisation sous-jacente (log4net, NLog, etc.) pour obtenir le bon enregistreur.

Ces points peuvent être évidents pour les utilisateurs de DI / Io, mais je viens tout juste d’y entrer, alors il m’a fallu du temps pour comprendre.

Une chose que je n’ai pas encore compris est comment ou si quelque chose comme cela est possible avec MEF. Puis-je avoir un object dépendant d’une interface, puis faire exécuter mon code après que MEF ait créé l’object et que l’interface / la dépendance est en cours de résolution? Donc, supposons que j’ai une classe comme celle-ci:

 public class MyClass { [Import(ILogger)] public ILogger logger; public MyClass() { } public void DoSomething() { logger.Info("Hello World!"); } } 

Lorsque MEF résout les importations pour MyClass, puis-je avoir une partie de mon propre code (via un atsortingbut, via une interface supplémentaire sur l’implémentation d’ILogger, ailleurs ???) pour exécuter et résoudre l’importation ILogger en fonction du fait qu’il est MyClass qui est actuellement en contexte et qui renvoie une instance ILogger (potentiellement) différente de celle qui serait récupérée pour YourClass? Est-ce que je mets en place une sorte de fournisseur MEF?

À ce stade, je ne sais toujours pas à propos de MEF.

Je vois que vous avez compris votre propre réponse 🙂 Mais, pour les futurs utilisateurs qui ont cette question sur la façon de ne pas se lier à une structure de journalisation particulière, cette bibliothèque: Common.Logging aide exactement avec ce scénario.

J’ai fait mon ServiceExportProvider personnalisé, par le fournisseur, j’enregistre Logger Net4 pour l’dependency injection par MEF. En conséquence, vous pouvez utiliser un enregistreur pour différents types d’injections.

Exemple d’injections:

 [Export] public class Part { [ImportingConstructor] public Part(ILog log) { Log = log; } public ILog Log { get; } } [Export(typeof(AnotherPart))] public class AnotherPart { [Import] public ILog Log { get; set; } } 

Exemple d’utilisation:

 class Program { static CompositionContainer CreateContainer() { var logFactoryProvider = new ServiceExportProvider(LogManager.GetLogger); var catalog = new AssemblyCatalog(typeof(Program).Assembly); return new CompositionContainer(catalog, logFactoryProvider); } static void Main(ssortingng[] args) { log4net.Config.XmlConfigurator.Configure(); var container = CreateContainer(); var part = container.GetExport().Value; part.Log.Info("Hello, world! - 1"); var anotherPart = container.GetExport().Value; anotherPart.Log.Fatal("Hello, world! - 2"); } } 

Résultat dans la console:

 2016-11-21 13:55:16,152 INFO Log4Mef.Part - Hello, world! - 1 2016-11-21 13:55:16,572 FATAL Log4Mef.AnotherPart - Hello, world! - 2 

Implémentation de ServiceExportProvider :

 public class ServiceExportProvider : ExportProvider { private readonly Func _factoryMethod; public ServiceExportProvider(Func factoryMethod) { _factoryMethod = factoryMethod; } protected override IEnumerable GetExportsCore(ImportDefinition definition, AtomicComposition atomicComposition) { var cb = definition as ContractBasedImportDefinition; if (cb?.RequiredTypeIdentity == typeof(TContract).FullName) { var ce = definition as ICompositionElement; var displayName = ce?.Origin?.DisplayName; yield return new Export(definition.ContractName, () => _factoryMethod(displayName)); } } }