Injection de dépendances et loggers nommés

je suis intéressé à en savoir plus sur la façon dont les gens injectent la journalisation avec des plateformes d'injection de dépendances. Bien que les liens ci-dessous et mes exemples se réfèrent à log4net et Unity, Je ne vais pas nécessairement utiliser l'un ou l'autre de ceux-ci. Pour l'injection de dépendance / IOC, je vais probablement utiliser MEF car c'est le standard que le reste du projet (large) est en train de fixer.

je suis très nouveau à l'injection de dépendances/cio et je suis assez novice en C# et .NET (ont très peu écrit code de production dans C# / .NET après les 10 dernières années environ de VC6 et VB6). J'ai fait beaucoup de recherches sur les différentes solutions de connexion qui sont là-bas, donc je pense que j'ai une bonne maîtrise de leurs ensembles de fonctionnalités. Je ne suis tout simplement pas assez familiarisé avec la mécanique réelle de l'injection d'une dépendance (ou, peut-être plus "correctement", de l'injection d'une version abstraite d'une dépendance).

j'ai vu d'autres messages liés à la journalisation et / ou à la dépendance l'injection comme: l'injection de dépendance et de journalisation des interfaces

"1519150920 Exploitation" meilleures pratiques

à quoi ressemblerait une classe D'emballage Log4Net?

encore une fois à propos de log4net et Unity config du CIO

ma question n'a pas spécifiquement à voir avec "comment injecter la plate-forme de journalisation xxx en utilisant l'outil du CIO yyy?" Je m'intéresse plutôt à la façon dont les gens ont géré l'enrubannage de la plate-forme de journalisation (comme c'est souvent, mais pas toujours recommandé) et la configuration (c.-à-d. l'application.config). Par exemple, en utilisant log4net comme exemple, je pourrais configurer (dans app.config) un certain nombre de loggers et puis obtenir ces loggers (sans injection de dépendance) dans la manière standard d'utiliser le 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 pourrait faire ceci:

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. j'aimerais isoler la source de mon produit d'une dépendance directe à une plate-forme de journalisation.

  2. j'aimerais être capable de résoudre une instance spécifique nommée logger (partageant probablement la même instance entre tous les demandeurs de la même instance nommée) soit directement, soit indirectement par une sorte d'injection de dépendance, probablement MEF.

  3. Je ne sais pas si j'appellerais cela une exigence dure, mais je voudrais la possibilité d'obtenir un logger nommé (différent de l'enregistreur de classe) sur demande. Par exemple, je pourrais créer un historique de ma classe basée sur le nom de la classe, mais une méthode particulièrement lourd diagnostics que je voudrais contrôler séparément. En d'autres termes, j'aimerais une classe unique de "dépendre" sur deux séparé de l'enregistreur de cas.

commençons par le numéro 1. J'ai lu un certain nombre d'articles, principalement ici sur stackoverflow, quant à savoir si ou non il est une bonne idée d'envelopper. Voir le lien " meilleures pratiques "ci-dessus et aller à jeffrey hantin 's commentaire pour un point de vue sur la raison pour laquelle il est mauvais d'envelopper log4net. Si vous avez enveloppé (et si vous pouviez envelopper efficacement) vous envelopper strictement dans le but d'injection / enlèvement de la depdency? Vous pouvez également essayer d'effacer une partie ou la totalité de l'application log4net.les informations de configuration?

disons que je veux utiliser le système.Diagnostics, je voudrais probablement implémenter un logger basé sur l'interface (peut-être même en utilisant l'interface "commune" ILogger/ILog), probablement basé sur TraceSource, pour que je puisse l'injecter. Voulez-vous implémenter L'interface, par exemple sur TraceSource, et juste utiliser le système.Diagnostics app.des informations de configuration?

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);
  }
}

et l'utiliser comme ceci:

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

passons au numéro 2 ... Comment résoudre une instance logger particulière? Devrais-je tirer parti de l'application.informations de configuration de la plate-forme de journalisation que je choisis (c.-à-d. résoudre les loggers basés sur le schéma de nommage dans l'application.config)? Donc, dans le cas de log4net, pourrais-je préférer "injecter" LogManager (notez que je sais que ce n'est pas possible car c'est un objet statique)? Je pourrais envelopper LogManager (l'appeler MyLogManager), lui donner une interface ILogManager, et puis résoudre MyLogManager.Interface ILogManager. Mes autres objets peuvent avoir une depenency (importer en langage MEF) sur ILogManager (exporter de l'assemblage où il est implémenté). Maintenant je pourrais avoir des objets comme ceci:

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

chaque fois Qu'ILogManager est appelé, il délègue directement au LogManager de log4net. Autrement, le LogManager enveloppé pourrait-il prendre les instances ILogger qu'il obtient basé sur l'application.config et les ajouter à la(une ?) Conteneur MEF par nom. Plus tard, lorsqu'un logger du même nom est demandé, le LogManager enveloppé est demandé pour ce nom. Si L'ILogger est là, il est résolu de cette façon. Si cela est possible avec le MEF, y a-t-il un avantage à le faire?

dans ce cas, en réalité, seul ILogManager est" injecté " et il peut distribuer des instances D'ILogger comme le fait log4net normalement. Comment est-ce type d'injection (essentiellement d'une usine) comparer à l'injection des instances logger nommées? Cela permet de tirer plus facilement parti de l'application log4net (ou d'une autre plate-forme de journalisation).fichier de configuration.

je sais que je peux obtenir des cas nommés hors du conteneur MEF comme ceci:

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

mais comment obtenir les instances nommées dans le conteneur? Je connais le modèle basé sur les attributs où je pourrais avoir différentes implémentations D'ILogger, chacune qui est nommé (via un attribut MEF), mais cela ne m'aide pas vraiment. Est-il un moyen de créer quelque chose comme une application.config (ou une section de celui-ci) qui énumérerait les loggers (tous de la même implémentation) par nom et que MEF pourrait lire? Pourrait / devrait-il y avoir un "gestionnaire" central (comme MyLogManager) qui résout les loggers nommés via l'application sous-jacente.config puis insère le logger résolu dans le conteneur MEF? De cette façon, il serait disponible à quelqu'un d'autre qui a accès à même conteneur MEF (bien que sans la connaissance du MyLogManager de la façon d'utiliser l'application de log4net.les informations de configuration, il semble que le conteneur serait incapable de résoudre un nom de bûcherons directement).

C'est déjà assez long. J'espère que c'est cohérent. S'il vous plaît, n'hésitez pas à partager des informations spécifiques sur la façon dont vous dépendez injecté une plate-forme de journalisation (nous sommes très probablement en train de considérer log4net, NLog, ou quelque chose (si tout va bien mince) construit sur Système.Diagnostic) dans votre application.

avez-vous injecté le" manager " et lui avez-vous renvoyé des instances logger?

avez-vous ajouté quelques-unes de vos propres informations de configuration dans votre propre section de configuration ou dans la section de configuration de votre plate-forme DI pour qu'il soit plus facile/possible d'injecter des instances logger directement (c.-à-d. faire en sorte que vos dépendances soient sur ILogger plutôt que sur ILogManager).

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

je note ceci comme un wiki de communauté puisqu'il ne semble pas être une question avec une réponse définie. Si quelqu'un pense le contraire, n'hésitez pas à le changer.

Merci pour toute aide!

69
demandé sur wageoghe 2010-08-10 22:48:13

5 réponses

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

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);
}

cette approche devrait aussi fonctionner avec d'autres conteneurs IOC, je suppose.

36
répondu Robin van der Knaap 2011-06-26 22:19:13

on peut aussi utiliser commun.Enregistrement de la façade ou de la Journalisation Simple Façade .

ces deux-là emploient un modèle de style de localisateur de service pour récupérer un ILogger.

franchement, la journalisation est une de ces dépendances que je vois peu ou pas de valeur à injecter automatiquement.

la plupart de mes classes 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 la façade de journalisation Simple j'ai commuté un projet de log4net à NLog, et j'ai ajouté la journalisation à partir d'une bibliothèque tierce partie qui a utilisé log4net en plus de la journalisation de mon application en utilisant NLog. C'est-à-dire, la façade nous a bien servi.

un avertissement difficile à éviter est la perte de fonctionnalités spécifiques à un cadre de journalisation ou à un autre, dont l'exemple le plus fréquent est peut-être les niveaux de journalisation personnalisés.

15
répondu quentin-starin 2010-08-19 20:07:25

c'est pour le bénéfice de toute personne qui essaye de comprendre comment injecter une dépendance logger lorsque le logger que vous voulez injecter est fourni 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 de l'ILogger spécifique dépendrait de savoir le type de la classe qui dépend de ILogger (par exemple MyClass). Comment le DI / CIO plate-forme / conteneur obtenir le droit 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 mise en œuvre de l'exploitation forestière. Les deux prennent en charge log4net et NLog. Castle soutient également le système.Diagnostic. Dans les deux cas, lorsque la plate-forme résout les dépendances pour un objet donné (par exemple quand la plate-forme crée MyClass et MyClass depends on ILogger) il délègue la création de la dépendance (ILogger) au ILogger "provider" (resolver pourrait être un terme plus commun). L'implémentation de l'ILogger provider est alors responsable de l'instanciation effective d'une instance D'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). Donc, quand Maclasse a été créé et ses dépendances sont résolu, le "résolveur" D'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 sous forme de wrapper sur log4net ou NLog) reçoit le type (MyClass), de sorte qu'elle peut déléguer à log4net.LogManager.GetLogger () ou NLog.LogManager.GetLogger (). (Pas sûr à 100% de la syntaxe pour log4net et NLog, mais vous avez l'idée).

alors que AutoFac et StructureMap ne fournissent pas une journalisation facilité (au moins que je pourrais dire en regardant), ils semblent fournir la capacité d'implémenter des résolveurs personnalisés. Donc, si vous voulez écrire votre propre couche d'abstraction de journalisation, vous pouvez aussi é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 accès au contexte actuel (i.e. quelles dépendances de l'objet sont actuellement satisfaites - quel objet dépend de ILogger). Obtenez le type de l'objet, et vous êtes prêt à déléguer la création de L'ILogger à la plate-forme de journalisation actuellement configurée (que vous avez probablement abstraite derrière une interface et pour laquelle vous avez écrit un résolveur).

ainsi, quelques points clés que je suspectais être nécessaires mais que je n'ai pas pleinement compris avant sont:

  1. finalement, le conteneur DI doit être conscience, en quelque sorte, de quel enregistrement plate-forme à utiliser. Typiquement, cela est fait en spécifiant que "ILogger" est être résolu par une "résolution" qui est spécifique à une plateforme d'enregistrement (par conséquent, Castle a log4net, NLog, et le Système.Résolveurs de Diagnostics" (parmi d'autres)). Spécification dont résolveur à utiliser peut être fait par fichier de configuration ou par programmation.

  2. le résolveur doit connaître la contexte pour lequel la dépendance (ILogger) est en cours de résolution. Que est, si Maclasse a été créé et elle dépend D'ILogger, alors lorsque le résolveur essaie de créer le bon ILogger, il (le resolver) doit connaître le type courant (Maclasse). De cette façon, le résolveur peut utiliser l'enregistrement sous-jacent mise en œuvre (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 d'y entrer, donc il m'a fallu un certain temps pour obtenir mon tête autour d'elle.

une chose que je n'ai pas encore compris est comment ou si quelque chose comme ça est possible avec MEF. Puis-je avoir un objet dépendant d'une interface et ensuite faire exécuter mon code APRÈS QUE MEF a créé l'objet et pendant que l'interface/la dépendance est résolue? 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!");
  }
}

quand MEF résout les importations pour MyClass, puis-je avoir un peu de mon propre code (via un attribut, via un interface sur la mise en œuvre D'ILogger, ailleurs???) exécuter et résoudre L'importation ILogger basé sur le fait que C'est MyClass qui est actuellement dans le contexte et de donner en arrière une instance ILogger (potentiellement) différente de ce qui serait récupéré pour Votreclasse? Est-ce que je mets en place une sorte de fournisseur de MEF?

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

13
répondu wageoghe 2010-08-19 19:25:16

je vois que vous avez trouvé votre propre réponse :) mais, pour les gens dans le futur qui ont cette question sur la façon de ne pas vous attacher à un cadre de journalisation particulier, cette bibliothèque: commun.Logging aide avec exactement ce scénario.

5
répondu CubanX 2010-08-19 19:38:37

j'ai fait ma coutume ServiceExportProvider , par le fournisseur J'enregistre Logger Log4Net pour l'injection de dépendance par MEF. En conséquence, vous pouvez utiliser logger 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'usage:

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 dans 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 la mise en œuvre:

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épondu R.Titov 2016-11-21 10:58:37