wake-up-neo.com

Abhängigkeitsinjektion und benannte Logger

Ich bin daran interessiert, mehr darüber zu erfahren, wie Menschen Protokolle mit Abhängigkeitseinspritzungsplattformen injizieren. Obwohl die Links unten und meine Beispiele auf log4net und Unity verweisen, werde ich nicht unbedingt beide verwenden. Für Abhängigkeitsinjektion/IOC werde ich wahrscheinlich MEF verwenden, da dies der Standard ist, auf den sich der Rest des Projekts (groß) legt.

Ich bin in Abhängigkeit von Injection/Ioc sehr neu und in C # und .NET ziemlich neu (habe sehr wenig Produktionscode in C # /. NET nach etwa 10 Jahren VC6 und VB6 geschrieben). Ich habe eine Menge Nachforschungen über die verschiedenen Protokollierungslösungen angestellt, die es auf dem Markt gibt. Ich denke, ich habe einen guten Überblick über ihre Funktionen. Ich bin einfach nicht vertraut genug mit den tatsächlichen Mechanismen, eine Abhängigkeit injiziert zu bekommen (oder, mehr "richtig", eine abstrahierte Version einer Abhängigkeit injiziert zu bekommen).

Ich habe andere Beiträge im Zusammenhang mit Protokollierung und/oder Abhängigkeitsinjektion gesehen, wie: Abhängigkeitsinjektion und Protokollierungsschnittstellen

Best Practices für die Protokollierung

Wie würde eine Log4Net Wrapper-Klasse aussehen?

noch einmal über log4net und Unity IOC config

Meine Frage bezieht sich nicht speziell auf "Wie injiziere ich die Logging-Plattform xxx mit dem ioc-Tool yyy?" Ich interessiere mich vielmehr dafür, wie die Benutzer die Protokollierungsplattform umwickelt haben (wie oft, aber nicht immer empfohlen) und die Konfiguration (d. H. App.config). Mit log4net als Beispiel könnte ich (in app.config) eine Reihe von Loggern konfigurieren und diese Logger (ohne Abhängigkeitseinspritzung) auf die übliche Art verwenden, Code wie folgt zu verwenden:

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

Wenn mein Logger nicht nach einer Klasse benannt ist, sondern nach einem Funktionsbereich, könnte ich Folgendes tun:

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

Ich denke also, dass meine "Anforderungen" so aussehen würden:

  1. Ich möchte die Quelle meines Produkts von einer direkten Abhängigkeit von einer Protokollierungsplattform isolieren.

  2. Ich möchte in der Lage sein, eine bestimmte benannte Loggerinstanz (die wahrscheinlich dieselbe Instanz unter allen Anforderern derselben benannten Instanz hat) entweder direkt oder indirekt durch eine Abhängigkeitsinjektion (wahrscheinlich MEF) aufzulösen.

  3. Ich weiß nicht, ob ich das als harte Anforderung bezeichnen würde, aber ich würde gerne die Möglichkeit haben, einen benannten Logger (abweichend vom Klassenlogger) auf Anforderung zu bekommen. Zum Beispiel kann ich einen Logger für meine Klasse erstellen, der auf dem Klassennamen basiert. Eine Methode erfordert jedoch eine besonders schwere Diagnose, die ich separat steuern möchte. Mit anderen Worten, ich möchte, dass eine einzelne Klasse von zwei separaten Logger-Instanzen "abhängig" ist.Beginnen wir mit der Nummer 1. Ich habe eine Reihe von Artikeln gelesen, in erster Linie hier bei stackoverflow, ob es eine gute Idee ist, zu verpacken. Sehen Sie den Link "Best Practices" oben und gehen Sie zum Kommentar von jeffrey hantin , um zu erfahren, warum es logisch ist, log4net einzuwickeln. Wenn Sie Wrap verwenden würden (und wenn Sie effektiv wickeln könnten), würden Sie es strikt einpacken, um die direkte Injektion zu entfernen/zu entfernen? Oder würden Sie auch versuchen, einige oder alle Informationen der log4net app.config-Datei zu abstrahieren?.

Nehmen wir an, ich möchte System.Diagnostics verwenden, ich würde wahrscheinlich einen Schnittstellen-basierten Logger implementieren wollen (vielleicht sogar mit der "allgemeinen" ILogger/ILog-Schnittstelle), der wahrscheinlich auf TraceSource basiert, damit ich ihn injizieren kann. Würden Sie die Schnittstelle beispielsweise über TraceSource implementieren und einfach die Informationen der System.Diagnostics app.config verwenden? 

Etwas wie das:

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

Das ist schon ziemlich lang geworden. Ich hoffe es ist kohärent. Fühlen Sie sich frei, um spezifische Informationen darüber zu teilen, wie Ihre Abhängigkeit eine Protokollierungsplattform injiziert hat (wir berücksichtigen höchstwahrscheinlich log4net, NLog oder etwas (hoffentlich dünnes), das auf System.Diagnostics aufgebaut ist) in Ihrer Anwendung.

Haben Sie den "Manager" injiziert und Logger-Instanzen zurückgeben lassen? 

Haben Sie einige Ihrer eigenen Konfigurationsinformationen in Ihren eigenen Konfigurationsbereich oder in den Konfigurationsbereich Ihrer DI-Plattform eingefügt, um das direkte Einfügen von Logger-Instanzen zu erleichtern/ermöglichen (d. H. Ihre Abhängigkeiten von ILogger und nicht von ILogManager abhängig machen). 

Wie wäre es mit einem statischen oder globalen Container, der entweder die ILogManager-Schnittstelle oder die Menge benannter ILogger-Instanzen enthält? Die Protokollabhängigkeit wird also nicht im herkömmlichen Sinne (über Konstruktor-, Eigenschafts- oder Member-Daten) injiziert, sondern bei Bedarf explizit aufgelöst. Ist dies ein guter oder schlechter Weg, um Abhängigkeit zu injizieren. 

Ich bezeichne dies als Community-Wiki, da es nicht wie eine Frage mit einer eindeutigen Antwort erscheint. Wenn sich jemand anders fühlt, können Sie es gerne ändern.

Danke für jede Hilfe!.

Thanks for any help!

70
wageoghe

Dies ist für alle von Vorteil, die herausfinden möchten, wie eine Loggerabhängigkeit eingefügt werden soll, wenn dem Logger, den Sie einfügen möchten, eine Protokollierungsplattform wie log4net oder NLog bereitgestellt wird. Mein Problem war, dass ich nicht verstehen konnte, wie ich eine Klasse (z. B. MyClass) von einer ILogger-Schnittstelle abhängig machen könnte, wenn ich wusste, dass die Auflösung des jeweiligen ILoggers davon abhängt, ob der Typ der Klasse, der von ILogger abhängt, bekannt ist ( zB MyClass). Wie erhält der DI/IoC-Plattform/Container den richtigen ILogger?

Ich habe mir die Quelle von Castle und NInject angesehen und gesehen, wie sie funktionieren. Ich habe auch AutoFac und StructureMap angeschaut.

Castle und NInject bieten eine Implementierung der Protokollierung. Beide unterstützen log4net und NLog. Castle unterstützt auch System.Diagnostics. Wenn die Plattform die Abhängigkeiten für ein bestimmtes Objekt auflöst (z. B. wenn die Plattform MyClass erstellt und MyClass von ILogger abhängig ist), delegiert sie in beiden Fällen die Erstellung der Abhängigkeit (ILogger) an den ILogger-Provider (der Resolver ist möglicherweise mehr) allgemeiner Begriff). Die Implementierung des ILogger-Providers ist dann dafür verantwortlich, eine Instanz von ILogger zu instanziieren und wieder auszugeben, um dann in die abhängige Klasse (z. B. MyClass) eingefügt zu werden. In beiden Fällen kennt der Provider/Resolver den Typ der abhängigen Klasse (z. B. MyClass). Wenn MyClass erstellt wurde und seine Abhängigkeiten aufgelöst wurden, weiß der ILogger-Resolver, dass die Klasse MyClass ist. Wenn Sie die von Castle oder NInject bereitgestellten Protokollierungslösungen verwenden, bedeutet dies, dass die Protokollierungslösung (die als Wrapper über log4net oder NLog implementiert ist) den Typ (MyClass) erhält, sodass sie an log4net.LogManager.GetLogger () oder delegiert werden kann NLog.LogManager.GetLogger (). (Die Syntax für log4net und NLog ist nicht zu 100% sicher, aber Sie haben die Idee).

AutoFac und StructureMap bieten zwar keine Protokollierungsfunktion (zumindest könnte ich das beim Suchen erkennen), sie scheinen jedoch die Möglichkeit zu bieten, benutzerdefinierte Resolver zu implementieren. Wenn Sie also eine eigene Abstraktionsschicht für die Protokollierung erstellen möchten, können Sie auch einen entsprechenden benutzerdefinierten Resolver schreiben. Auf diese Weise würde der Resolver verwendet, wenn der Container ILogger auflösen möchte, um den richtigen ILogger zu erhalten UND er hätte Zugriff auf den aktuellen Kontext (d. H. Welche Abhängigkeiten des Objekts gerade erfüllt werden - welches Objekt hängt von ILogger ab). Rufen Sie den Typ des Objekts ab, und Sie können die Erstellung des ILogger an die derzeit konfigurierte Protokollierungsplattform delegieren (die Sie wahrscheinlich hinter einer Schnittstelle abstrahiert haben und für die Sie einen Resolver geschrieben haben).

Einige wichtige Punkte, die ich vermutet hatte, waren jedoch erforderlich, die ich vorher nicht vollständig verstanden habe:

  1. Letztendlich muss der DI-Container .__ irgendwie wissen, welche Protokollierungsplattform Verwendet werden soll. Normalerweise geschieht dies .__, indem angegeben wird, dass "ILogger" durch einen "Auflöser" aufgelöst werden soll, der für eine Protokollierungsplattform spezifisch ist .__ (daher hat Castle log4net, NLog, ). und System.Diagnostics "Resolver" (ua)). Die Spezifikation .__ des zu verwendenden Resolvers kann Über die Konfigurationsdatei oder programmgesteuert erfolgen.

  2. Der Resolver muss die .__ kennen. Kontext, für den die Abhängigkeit (ILogger) wird aufgelöst. Das ist, wenn MyClass erstellt wurde und es ist abhängig von ILogger, dann wenn der Resolver versucht, Erstellen Sie den richtigen ILogger. Er (der Resolver) muss den aktuellen Typ .__ kennen. (Meine Klasse). Auf diese Weise wird der Resolver kann die zugrunde liegende Protokollierung verwenden Implementierung (log4net, NLog usw.) um den richtigen Logger zu bekommen.

Diese Punkte sind für DI/IoC-Benutzer auf der Welt offensichtlich, aber ich komme gerade dazu, also habe ich eine Weile gebraucht, um den Kopf durchzudrehen.

Was ich noch nicht herausgefunden habe, ist, wie oder ob mit MEF so etwas möglich ist. Kann ich ein von einer Schnittstelle abhängiges Objekt haben und dann meinen Code ausführen lassen, nachdem MEF das Objekt erstellt hat und während die Schnittstelle/Abhängigkeit aufgelöst wird? Angenommen, ich habe eine Klasse wie diese:

public class MyClass
{
  [Import(ILogger)]
  public ILogger logger;

  public MyClass()
  {
  }

  public void DoSomething()
  {
    logger.Info("Hello World!");
  }
}

Wenn MEF die Importe für MyClass auflöst, kann ich etwas von meinem eigenen Code (über ein Attribut, über eine zusätzliche Schnittstelle zur Implementierung von ILogger, woanders ???) ausführen und den ILogger-Import auf der Grundlage der Tatsache ausführen und auflösen MyClass, das sich aktuell im Kontext befindet und eine (möglicherweise) andere ILogger-Instanz zurückgibt, als für YourClass abgerufen werden würde? Implementiere ich eine Art MEF-Provider?

Zu diesem Zeitpunkt weiß ich noch nicht, was MEF betrifft.

13
wageoghe

Ich benutze Ninject, um den aktuellen Klassennamen für die Logger-Instanz wie folgt aufzulösen:

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

Der Konstruktor einer NLog-Implementierung könnte folgendermaßen aussehen:

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

Dieser Ansatz sollte auch mit anderen IOC -Containern funktionieren, denke ich.

37

Man kann auch Common.Logging facade oder die Simple Logging Facade verwenden.

Beide verwenden ein Service Locator-Stilmuster, um einen ILogger abzurufen.

Um ehrlich zu sein, ist die Protokollierung eine dieser Abhängigkeiten, die ich beim automatischen Injizieren wenig oder gar keinen Wert sehe.

Die meisten meiner Klassen, für die Protokollierungsdienste erforderlich sind, sehen folgendermaßen aus:

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

}

Mit der Simple Logging Facade habe ich ein Projekt von log4net auf NLog umgestellt und die Protokollierung aus einer Bibliothek eines Drittanbieters hinzugefügt, die neben der Protokollierung meiner Anwendung mit NLog auch log4net verwendet. Das heißt, die Fassade hat uns gute Dienste geleistet.

Ein Nachteil, der schwer zu vermeiden ist, ist der Verlust von Funktionen, die für ein oder mehrere Protokollierungsframework spezifisch sind. Das häufigste Beispiel dafür sind benutzerdefinierte Protokollierungsstufen.

15
quentin-starin

Ich sehe, Sie haben Ihre eigene Antwort herausgefunden :) Aber für Leute in der Zukunft, die diese Frage haben, wie Sie sich NICHT an ein bestimmtes Protokollierungs-Framework binden können, hilft diese Bibliothek: Common.Logging hilft genau diesem Szenario.

5
CubanX

Ich habe meinen benutzerdefinierten ServiceExportProvider erstellt, durch den Anbieter registriere ich den Log4Net-Logger für die Abhängigkeitsinjektion von MEF. Als Ergebnis können Sie den Logger für verschiedene Injektionsarten verwenden.

Beispiel für Injektionen:

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

Verwendungsbeispiel:

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

Ergebnis in der Konsole:

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 Implementierung:

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