web-dev-qa-db-fra.com

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 plateformes d'injection de dépendance. Bien que les liens ci-dessous et mes exemples renvoient à log4net et à Unity, je ne les utiliserai pas nécessairement. Pour l'injection de dépendance/IOC, j'utiliserai probablement MEF car c'est la norme sur laquelle le reste du projet (grand) s'installera.

Je suis très nouveau en dépendance/ioc et assez nouveau en C # et .NET (j'ai écrit très peu de code de production en C #/.NET après environ 10 ans de VC6 et VB6). J'ai fait beaucoup de recherches sur les différentes solutions de journalisation existantes, donc je pense avoir une bonne maîtrise de leurs fonctionnalités. Je ne suis tout simplement pas assez au courant des mécanismes permettant d'obtenir une dépendance injectée (ou, peut-être plus "correctement", d'obtenir une version abstraite d'une dépendance injectée).

J'ai vu d'autres publications liées à la journalisation et/ou à l'injection de dépendance, telles que: injection de dépendance et interfaces de journalisation

Meilleures pratiques de journalisation

À quoi ressemblerait une classe Log4Net Wrapper?

de nouveau à propos de log4net et de la configuration Unity IOC

Ma question n'a pas spécifiquement à voir avec "Comment faire pour injecter la plate-forme de journalisation xxx en utilisant ioc tool yyy?" Je suis plutôt intéressé par la façon dont les gens ont traité le wrapping de la plate-forme de journalisation (comme cela est souvent, mais pas toujours recommandé) et la configuration (c'est-à-dire 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 injection de dépendance) de la manière standard d'utiliser un code comme celui-ci:

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

Alternativement, si mon logger 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 souhaite isoler la source de mon produit d'une dépendance directe à une plate-forme de journalisation.

  2. J'aimerais pouvoir résoudre une instance de consignataire nommée spécifique (partageant probablement la même instance entre tous les demandeurs de la même instance nommée) directement ou indirectement par une sorte d'injection de dépendance, probablement MEF.

  3. Je ne sais pas si j'appellerais cela une exigence absolue, 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 souhaite contrôler séparément. En d'autres termes, je souhaiterais peut-être qu'une seule classe "dépende" de deux instances de consignateur distinctes.Commençons par le numéro 1. J'ai lu un certain nombre d'articles, principalement ici sur stackoverflow, sur le fait de savoir si c'est une bonne idée de boucler. Voir le lien "meilleures pratiques" ci-dessus et aller au commentaire de jeffrey hantin pour un aperçu de la raison pour laquelle il est mauvais d’envelopper log4net. Si vous enveloppiez (et si vous pouviez encapsuler efficacement), encapsuleriez-vous uniquement dans le but d’injecter/de retirer une dépense directe? Ou tenteriez-vous également d’abstraire 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é sur TraceSource, afin de pouvoir l'injecter. Souhaitez-vous implémenter l'interface, disons sur TraceSource, et simplement utiliser les informations System.Diagnostics app.config telles quelles? 

Quelque chose comme ça:

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

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

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

var container = new CompositionContainer(<catalogs and other stuff>);
ILogger logger = container.GetExportedValue<ILogger>("ThisLogger");

Cela a déjà été assez long. J'espère que c'est cohérent. N'hésitez pas à partager toute information spécifique sur la manière dont votre dépendance a injecté une plate-forme de journalisation (nous envisageons probablement log4net, NLog ou quelque chose (espérons-le mince) basé sur System.Diagnostics) dans votre application.

Avez-vous injecté le "manager" et l'avez-vous renvoyé des instances de consignateur? 

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 afin de faciliter l’injection directe d’instances de consignation (c’est-à-dire que vos dépendances soient sur ILogger plutôt que sur ILogManager)? 

Qu'en est-il d'avoir un conteneur statique ou global qui contient l'interface ILogManager ou l'ensemble des instances ILogger nommées. Ainsi, plutôt que d'injecter au sens conventionnel (via le constructeur, la propriété ou les données de membre), la dépendance de la journalisation est explicitement résolue à la demande. Est-ce une bonne ou une mauvaise façon d’injecter de la dépendance? 

Je marque ceci en tant que wiki de communauté car cela 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!.

Thanks for any help!

70
wageoghe

Cela profite à quiconque tente de comprendre comment injecter une dépendance de consignateur lorsque le consignateur que vous souhaitez injecter reçoit une plate-forme de journalisation telle que log4net ou NLog. Mon problème était que je ne comprenais pas comment rendre une classe (par exemple, MyClass) dépendante d’une interface de type ILogger quand je savais que la résolution de cet ILogger spécifique dépendait de la connaissance du type de classe dépendant d’ILogger ( par exemple MyClass). Comment la plate-forme/conteneur DI/IoC obtient-il le bon ILogger?

Eh bien, j'ai regardé la source de 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 supportent log4net et NLog. Castle prend également en charge System.Diagnostics. Dans les deux cas, lorsque la plateforme résout les dépendances d'un objet donné (par exemple, lorsqu'elle crée MyClass et que MyClass dépend d'ILogger), elle délègue la création de la dépendance (ILogger) au "fournisseur" d'ILogger (le résolveur peut être plus terme commun). L’implémentation du fournisseur ILogger est ensuite chargée d’instancier réellement une instance d’ILogger et de la restituer, afin qu’elle soit ensuite 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 sont en cours de résolution, 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 qu'encapsuleur sur log4net ou NLog) obtient le type (MyClass), de sorte qu'elle peut déléguer à log4net.LogManager.GetLogger () ou NLog.LogManager.GetLogger (). (Pas 100% sûr de la syntaxe pour log4net et NLog, mais vous avez l'idée).

Bien qu'AutoFac et StructureMap n'offrent pas de fonction de journalisation (du moins, j'ai pu le constater en regardant), ils semblent permettre de mettre en œuvre des résolveurs personnalisés. Ainsi, si vous souhaitez écrire votre propre couche d'abstraction de journalisation, vous pouvez également écrire un résolveur personnalisé correspondant. Ainsi, lorsque le conteneur veut résoudre ILogger, votre résolveur sera utilisé pour obtenir le bon ILogger ET il aura accès au contexte actuel (c'est-à-dire quelles dépendances d'objet sont actuellement satisfaites - quel objet dépend d'ILogger). Obtenez le type de l'objet et vous êtes prêt à déléguer la création d'ILogger à la plate-forme de journalisation actuellement configurée (que vous avez probablement abstraite derrière une interface et pour lequel vous avez écrit un résolveur).

Voici donc quelques points essentiels que je soupçonnais nécessaires mais que je n’avais pas bien compris:

  1. En fin de compte, le conteneur d’ID doit être conscient, d’une manière ou d’une autre, de la plate-forme de journalisation à utiliser. Cela est généralement fait En spécifiant que "ILogger" doit être résolu par un "résolveur" spécifique à une plate-forme de journalisation (Par conséquent, Castle a log4net, NLog, et System.Diagnostics "résolveurs" (entre autres)). La spécification Du résolveur à utiliser peut être effectuée Via un fichier de configuration ou par programmation.

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

Ces points sont peut-être évidents pour les utilisateurs de DI/IoC, mais je viens tout juste de commencer, donc il m'a fallu un certain temps pour comprendre.

Une chose que je n'ai pas encore comprise, c'est comment ou si quelque chose comme cela est possible avec MEF. Puis-je avoir un objet dépendant d'une interface, puis faire exécuter mon code après que MEF ait créé l'objet et tant que l'interface/la dépendance est en cours de résolution? Alors, supposons que j'ai un cours comme celui-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 disposer de mon propre code (via un attribut, via une interface supplémentaire sur la mise en oeuvre d'ILogger, ailleurs ???) et exécuter et résoudre l'importation ILogger en se basant sur le fait qu'il est MyClass qui est actuellement dans le contexte et restitue une instance ILogger (potentiellement) différente de celle qui serait extraite pour YourClass? Dois-je implémenter une sorte de fournisseur MEF?

À ce stade, je ne connais toujours pas le MEF.

13
wageoghe

J'utilise Ninject pour résoudre le nom de classe actuel de l'instance de consignation, comme suit:

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

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

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

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

37

On peut également utiliser Common.Logging facade ou le Simple Logging Facade .

Ces deux méthodes utilisent un modèle de style de localisateur de service pour extraire un ILogger.

Franchement, la journalisation est l’une de ces dépendances pour lesquelles l’injection automatique a peu ou pas de valeur.

La plupart de mes cours qui nécessitent 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 basculé un projet de log4net vers NLog et j'ai ajouté la journalisation à partir d'une bibliothèque tierce qui utilisait log4net en plus de la journalisation de mon application via NLog. C'est-à-dire que la façade nous a bien servi.

Un inconvénient difficile à éviter est la perte de fonctionnalités propres à un cadre de journalisation ou à un autre, l'exemple le plus fréquent étant peut-être les niveaux de journalisation personnalisés.

15
quentin-starin

Je vois que vous avez compris votre propre réponse :) Mais pour les futurs utilisateurs de cette question sur la façon de ne PAS vous lier à un cadre de journalisation particulier, cette bibliothèque: Common.Logging aide exactement à ce scénario.

5
CubanX

J'ai créé mon service personnalisé ServiceExportProvider, par le fournisseur. J'enregistre le consignateur Log4Net pour l'injection de dépendance par MEF. En conséquence, vous pouvez utiliser l'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<ILog>(LogManager.GetLogger);
        var catalog = new AssemblyCatalog(typeof(Program).Assembly);
        return new CompositionContainer(catalog, logFactoryProvider);
    }

    static void Main(string[] args)
    {
        log4net.Config.XmlConfigurator.Configure();
        var container = CreateContainer();
        var part = container.GetExport<Part>().Value;
        part.Log.Info("Hello, world! - 1");
        var anotherPart = container.GetExport<AnotherPart>().Value;
        anotherPart.Log.Fatal("Hello, world! - 2");
    }
}

Résultat en 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

ServiceExportProvider implémentation:

public class ServiceExportProvider<TContract> : ExportProvider
{
    private readonly Func<string, TContract> _factoryMethod;

    public ServiceExportProvider(Func<string, TContract> factoryMethod)
    {
        _factoryMethod = factoryMethod;
    }

    protected override IEnumerable<Export> 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));
        }
    }
}
0
R.Titov