Iniezione delle dipendenze e logger nominati

Sono interessato a saperne di più su come le persone si iniettano la registrazione con le piattaforms di iniezione delle dipendenze. Sebbene i collegamenti riportati di seguito ei miei esempi si riferiscano a log4net e Unity, non utilizzerò necessariamente nessuno di questi. Per l’iniezione di dipendenza / IOC, probabilmente userò MEF poiché questo è lo standard su cui si sta stabilendo il resto del progetto (grande).

Sono molto nuovo a dependency injection / ioc e sono abbastanza nuovo in C # e .NET (ho scritto pochissimo codice di produzione in C # /. NET dopo gli ultimi 10 anni circa di VC6 e VB6). Ho fatto molte ricerche sulle varie soluzioni di registrazione che sono là fuori, quindi penso di avere un buon controllo sui loro set di funzionalità. Non sono abbastanza familiare con la meccanica attuale di ottenere una dipendenza iniettata (o, forse più “correttamente”, ottenendo una versione astratta di una dipendenza iniettata).

Ho visto altri post relativi alle operazioni di registrazione e / o dipendenza come iniezioni di dipendenza e di registrazione

Registrazione delle migliori pratiche

Come sarebbe una class Wrapper Log4Net?

    di nuovo su log4net e Unity IOC config

    La mia domanda non ha specificamente a che fare con “Come posso iniettare la piattaforma di registrazione xxx utilizzando lo strumento ioc yyy?” Piuttosto, sono interessato a come le persone hanno gestito il wrapping della piattaforma di registrazione (come spesso, ma non sempre consigliato) e la configurazione (ad esempio app.config). Ad esempio, utilizzando log4net come esempio, potrei configurare (in app.config) un numero di logger e quindi ottenere quei logger (senza l’integrazione delle dipendenze) nel modo standard di usare un codice come questo:

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

    In alternativa, se il mio logger non è chiamato per una class, ma piuttosto, per un’area funzionale, potrei fare questo:

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

    Quindi, immagino che i miei “requisiti” siano qualcosa del genere:

    1. Vorrei isolare la fonte del mio prodotto da una dipendenza diretta su una piattaforma di registrazione.

    2. Mi piacerebbe essere in grado di risolvere una specifica istanza di logger denominata (probabilmente condividendo la stessa istanza tra tutti i richiedenti della stessa istanza denominata) direttamente o indirettamente da una sorta di dipendenza da iniezione, probabilmente MEF.

    3. Non so se lo definirei un requisito indispensabile, ma mi piacerebbe poter ottenere su richiesta un logger con nome (diverso dal registratore di class). Ad esempio, potrei creare un logger per la mia class in base al nome della class, ma un metodo richiede una diagnostica particolarmente approfondita che vorrei controllare separatamente. In altre parole, potrei desiderare che una singola class “dipenda” da due istanze di logger separate.

    Iniziamo con il numero 1. Ho letto un numero di articoli, principalmente qui su StackOverflow, sul fatto se sia una buona idea avvolgere. Vedere il link “best practice” sopra e andare al commento di jeffrey hantin per una vista sul perché è male avvolgere log4net. Se lo facessi avvolgere (e se potessi avvolgere efficacemente) ti avvolgerei strettamente allo scopo di iniezione / rimozione della depdenza diretta? O proverai anche ad astrarre alcune o tutte le informazioni di app.config di log4net?

    Diciamo che voglio usare System.Diagnostics, probabilmente vorrei implementare un logger basato su interfaccia (magari usando anche l’interfaccia “comune” di ILogger / ILog), probabilmente basato su TraceSource, in modo da poterlo iniettare. Implementeresti l’interfaccia, ad esempio su TraceSource, e userai semplicemente le informazioni di app.config System.Diagnostics così come sono?

    Qualcosa come questo:

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

    E usalo in questo modo:

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

    Passare al numero 2 … Come risolvere una particolare istanza del logger denominata? Dovrei semplicemente sfruttare le informazioni di app.config della piattaforma di registrazione che scelgo (ovvero risolvere i logger in base allo schema di denominazione nell’app.config)? Quindi, nel caso di log4net, potrei preferire “iniettare” LogManager (nota che so che non è ansible poiché si tratta di un object statico)? Potrei racchiudere LogManager (chiamalo MyLogManager), fornirgli un’interfaccia di ILogManager e quindi risolvere l’interfaccia di MyLogManager.ILogManager. I miei altri oggetti potrebbero avere una depenency (Importa nel linguaggio MEF) su ILogManager (Esporta dall’assembly in cui è implementato). Ora potrei avere oggetti come questo:

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

    Ogni volta che viene chiamato ILogManager, questo delegherà direttamente al LogManager di log4net. In alternativa, il LogManager incapsulato può prendere le istanze di ILogger che si basa su app.config e aggiungerle al contenitore (a?) MEF per nome. Successivamente, quando viene richiesto un logger con lo stesso nome, viene interrogato il LogManager spostato per tale nome. Se l’ILogger è lì, è risolto in questo modo. Se ciò è ansible con MEF, c’è qualche vantaggio nel farlo?

    In questo caso, in realtà, solo ILogManager viene “iniettato” e può distribuire le istanze di ILogger nel modo in cui log4net normalmente lo fa. In che modo questo tipo di iniezione (essenzialmente di fabbrica) si confronta con l’iniezione delle istanze del logger nominate? Ciò consente di sfruttare più facilmente il file app.config di log4net (o altra piattaforma di registrazione).

    So che posso ottenere istanze denominate dal contenitore MEF in questo modo:

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

    Ma come ottengo le istanze nominate nel contenitore? Conosco il modello basato su attributi in cui posso avere diverse implementazioni di ILogger, ognuna delle quali è denominata (tramite un attributo MEF), ma questo non mi aiuta veramente. C’è un modo per creare qualcosa come un app.config (o una sezione in esso) che elencerebbe i logger (tutti della stessa implementazione) per nome e che MEF potrebbe leggere? Potrebbe / dovrebbe esserci un “gestore” centrale (come MyLogManager) che risolve i logger nominati tramite l’app.config sottostante e quindi inserisce il logger risolto nel contenitore MEF? In questo modo sarebbe disponibile a qualcun altro che ha accesso allo stesso contenitore MEF (sebbene senza la conoscenza di MyLogManager su come utilizzare le informazioni app.config di log4net, sembra che il contenitore non sia in grado di risolvere direttamente i logger nominati).

    Questo è già abbastanza lungo. Spero che sia coerente. Sentitevi liberi di condividere qualsiasi informazione specifica su come la vostra dipendenza ha iniettato una piattaforma di registrazione (molto probabilmente consideriamo log4net, NLog, o qualcosa (si spera sottile) costruito su System.Diagnostics) nella vostra applicazione.

    Hai iniettato il “gestore” e gli hai restituito le istanze del logger?

    Hai aggiunto alcune delle tue informazioni di configurazione nella tua sezione di configurazione o nella sezione di configurazione della tua piattaforma DI per rendere più semplice / ansible iniettare direttamente le istanze del logger (cioè rendere le tue dipendenze su ILogger piuttosto che su ILogManager).

    Che dire di avere un contenitore statico o globale che contiene l’interfaccia di ILogManager o il set di istanze di ILogger nominate. Quindi, piuttosto che iniettare in senso convenzionale (tramite costruttore, proprietà o dati dei membri), la dipendenza della registrazione viene esplicitamente risolta su richiesta. È questo un modo buono o cattivo di iniettare dipendenza.

    Sto marcando questo come un wiki della comunità poiché non sembra una domanda con una risposta definita. Se qualcuno si sente diversamente, sentiti libero di cambiarlo.

    Grazie per qualsiasi aiuto!

    Sto usando Ninject per risolvere il nome della class corrente per l’istanza del logger come questo:

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

    Il costruttore di un’implementazione di NLog potrebbe avere questo aspetto:

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

    Credo che questo approccio dovrebbe funzionare anche con altri contenitori IOC.

    Si può anche usare la facciata Common.Logging o la Simple Logging Facade .

    Entrambi utilizzano un modello di stile locator di servizio per recuperare un ILogger.

    Francamente, il logging è una di quelle dipendenze che vedo poco o nessun valore nell’iniezione automatica.

    La maggior parte delle mie classi che richiedono servizi di registrazione si presenta così:

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

    Utilizzando la Simple Logging Facade, ho passato un progetto da log4net a NLog e ho aggiunto la registrazione da una libreria di terze parti che utilizzava log4net oltre alla registrazione della mia applicazione utilizzando NLog. Vale a dire, la facciata ci ha servito bene.

    Un avvertimento che è difficile da evitare è la perdita di caratteristiche specifiche di un framework di registrazione o di un altro, forse l’esempio più frequente dei quali sono i livelli di registrazione personalizzati.

    Questo è a vantaggio di chiunque stia cercando di capire come iniettare una dipendenza dal logger quando il logger che si desidera iniettare viene fornito una piattaforma di registrazione come log4net o NLog. Il mio problema era che non riuscivo a capire come avrei potuto rendere una class (es. MyClass) dipendente da un’interfaccia di tipo ILogger quando sapevo che la risoluzione dello specifico ILogger dipenderebbe dal conoscere il tipo di class che dipende da ILogger ( es. MyClass). In che modo la piattaforma / contenitore DI / IoC ottiene il giusto ILogger?

    Bene, ho guardato la fonte di Castle e NInject e ho visto come funzionano. Ho anche guardato AutoFac e StructureMap.

    Castle e NInject forniscono entrambi un’implementazione del logging. Entrambi supportano log4net e NLog. Castle supporta anche System.Diagnostics. In entrambi i casi, quando la piattaforma risolve le dipendenze per un determinato object (ad esempio quando la piattaforma sta creando MyClass e MyClass dipende da ILogger), delega la creazione della dipendenza (ILogger) al “provider” di ILogger (il resolver potrebbe essere un altro termine comune). L’implementazione del provider di ILogger è quindi responsabile di istanziare effettivamente un’istanza di ILogger e ridistribuirla, per poi essere iniettata nella class dipendente (ad es. MyClass). In entrambi i casi il fornitore / risolutore conosce il tipo di class dipendente (es. MyClass). Quindi, quando MyClass è stato creato e le sue dipendenze sono state risolte, il “resolver” di ILogger sa che la class è MyClass. Nel caso di utilizzo delle soluzioni di logging fornite da Castle o NInject, ciò significa che la soluzione di registrazione (implementata come wrapper su log4net o NLog) ottiene il tipo (MyClass), quindi può debind a log4net.LogManager.GetLogger () o NLog.LogManager.GetLogger (). (Non sicuro al 100% della syntax per log4net e NLog, ma si ottiene l’idea).

    Mentre AutoFac e StructureMap non forniscono una funzionalità di registrazione (almeno ciò che posso dire guardando), sembrano fornire la possibilità di implementare resolver personalizzati. Pertanto, se si desidera scrivere il proprio livello di astrazione di registrazione, è anche ansible scrivere un resolver personalizzato corrispondente. In questo modo, quando il contenitore vuole risolvere ILogger, il tuo resolver verrebbe utilizzato per ottenere il corretto ILogger e avrebbe accesso al contesto corrente (cioè quali dipendenze dell’object sono attualmente soddisfatte – quale object dipende da ILogger). Ottieni il tipo di object, e sei pronto a debind la creazione di ILogger alla piattaforma di logging attualmente configurata (che hai probabilmente astratto dietro un’interfaccia e per cui hai scritto un resolver).

    Quindi, un paio di punti chiave che sospettavo erano necessari, ma che non avevo pienamente compreso prima sono:

    1. In definitiva, il contenitore DI deve essere a conoscenza, in qualche modo, della piattaforma di registrazione da utilizzare. In genere ciò viene fatto specificando che “ILogger” deve essere risolto da un “resolver” specifico per una piattaforma di registrazione (quindi, Castle ha log4net, NLog e System.Diagnostics “resolver” (tra gli altri)). La specifica del risolutore da utilizzare può essere effettuata tramite il file di configurazione o programmaticamente.

    2. Il risolutore deve conoscere il contesto per il quale è stata risolta la dipendenza (ILogger). Cioè, se MyClass è stato creato e dipende da ILogger, allora quando il resolver sta cercando di creare il corretto ILogger, questo (il resolver) deve conoscere il tipo corrente (MyClass). In questo modo, il resolver può utilizzare l’implementazione di registrazione sottostante (log4net, NLog, ecc.) Per ottenere il logger corretto.

    Questi punti potrebbero essere ovvi per quegli utenti DI / IoC là fuori, ma ci sto provando proprio ora, quindi mi ci è voluto un po ‘per capirlo.

    Una cosa che non ho ancora capito è come o se qualcosa di simile è ansible con MEF. Posso avere un object dipendente da un’interfaccia e quindi eseguire il mio codice dopo che MEF ha creato l’object e mentre l’interfaccia / dipendenza è stata risolta? Quindi, supponi di avere una class come questa:

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

    Quando MEF sta risolvendo le importazioni per MyClass, posso avere un po ‘del mio codice (tramite un attributo, tramite un’interfaccia aggiuntiva sull’implementazione di ILogger, altrove ???) eseguire e risolvere l’importazione di ILogger in base al fatto che è MyClass che è attualmente nel contesto e restituisce un’istanza di ILogger (potenzialmente) diversa da quella che verrebbe recuperata per YourClass? Implemento una sorta di provider MEF?

    A questo punto, non conosco ancora MEF.

    Vedo che hai capito la tua risposta 🙂 Ma, per le persone in futuro che hanno questa domanda su come NON legarsi ad un particolare framework di registrazione, questa libreria: Common.Logging aiuta esattamente con questo scenario.

    Ho creato il mio ServiceExportProvider personalizzato, dal fornitore che registro il logger Log4Net per l’integrazione delle dipendenze da MEF. Come risultato è ansible utilizzare il logger per diversi tipi di iniezioni.

    Esempio di iniezioni:

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

    Esempio di utilizzo:

     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(string[] 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"); } } 

    Risultato in 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 

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