patron inyeccion injection dependencias c# logging dependency-injection log4net nlog

c# - injection - inyeccion de dependencias youtube



Inyección de dependencia y registradores con nombre (5)

Esto es para el beneficio de cualquiera que intente descubrir cómo inyectar una dependencia de registrador cuando el registrador que desea inyectar tenga una plataforma de registro como log4net o NLog. Mi problema era que no podía entender cómo podía hacer que una clase (p. Ej. MyClass) dependiera de una interfaz tipo ILogger cuando sabía que la resolución del ILogger específico dependería de conocer el tipo de clase que depende de ILogger ( por ejemplo, MyClass). ¿Cómo obtiene la plataforma / contenedor DI / IoC el ILogger correcto?

Bueno, he visto la fuente de Castle y NInject y he visto cómo funcionan. También miré AutoFac y StructureMap.

Castle y NInject proporcionan una implementación de registro. Ambos admiten log4net y NLog. Castle también es compatible con System.Diagnostics. En ambos casos, cuando la plataforma resuelve las dependencias para un objeto dado (por ejemplo, cuando la plataforma crea MyClass y MyClass depende de ILogger), delega la creación de la dependencia (ILogger) en el "proveedor" de ILogger (el resolutor podría ser un termino común). La implementación del proveedor de ILogger es entonces responsable de crear una instancia de instancia de ILogger y entregarla de nuevo, para luego ser inyectada en la clase dependiente (por ejemplo, MyClass). En ambos casos, el proveedor / resolvedor conoce el tipo de clase dependiente (p. Ej. MyClass). Entonces, cuando se ha creado MyClass y se están resolviendo sus dependencias, el "solucionador" de ILogger sabe que la clase es MyClass. En el caso de utilizar las soluciones de registro proporcionadas por Castle o NInject, eso significa que la solución de registro (implementada como un contenedor sobre log4net o NLog) obtiene el tipo (MyClass), por lo que puede delegar en log4net.LogManager.GetLogger () o NLog.LogManager.GetLogger (). (No estoy seguro al 100% de la sintaxis para log4net y NLog, pero se entiende la idea).

Si bien AutoFac y StructureMap no brindan un recurso de registro (al menos eso podría decirlo al mirar), sí parecen ofrecer la capacidad de implementar resoluciones personalizadas. Entonces, si quisiera escribir su propia capa de abstracción de registro, también podría escribir una resolución personalizada correspondiente. De esta forma, cuando el contenedor desea resolver ILogger, su resolver se usaría para obtener el ILogger correcto Y tendría acceso al contexto actual (es decir, qué dependencias del objeto se están satisfaciendo actualmente, qué objeto depende de ILogger). Obtenga el tipo de objeto y estará listo para delegar la creación de ILogger en la plataforma de registro configurada actualmente (que probablemente haya abstraído detrás de una interfaz y para la cual haya escrito una resolución).

Por lo tanto, un par de puntos clave que sospechaba que eran necesarios pero que no entendí completamente antes son:

  1. En última instancia, el contenedor DI debe conocer, de alguna manera, qué plataforma de registro usar. Normalmente, esto se hace especificando que "ILogger" debe ser resuelto por un "resolver" que es específico de una plataforma de registro (por lo tanto, Castle tiene "resolvedores" de log4net, NLog y System.Diagnostics (entre otros)). La especificación de qué resolver usar se puede hacer a través de un archivo de configuración o programáticamente.

  2. El resolvedor necesita saber el contexto para el que se está resolviendo la dependencia (ILogger). Es decir, si MyClass ha sido creado y depende de ILogger, entonces cuando el resolver está tratando de crear el ILogger correcto, él (el resolver) debe conocer el tipo actual (MyClass). De esta forma, el resolvedor puede usar la implementación de registro subyacente (log4net, NLog, etc.) para obtener el registrador correcto.

Estos puntos pueden ser obvios para los usuarios de DI / IoC, pero ahora estoy entrando en eso, así que me ha tomado un tiempo entenderlo.

Una cosa que todavía no he descubierto es cómo o si algo como esto es posible con MEF. ¿Puedo hacer que un objeto dependa de una interfaz y luego ejecutar mi código después de que MEF haya creado el objeto y mientras se resuelve la interfaz / dependencia? Entonces, supongamos que tengo una clase como esta:

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

Cuando MEF está resolviendo las importaciones de MyClass, ¿puedo tener algo de mi propio código (a través de un atributo, a través de una interfaz adicional en la implementación de ILogger, en otro lugar?) Ejecutar y resolver la importación de ILogger basándose en el hecho de que es MyClass que actualmente está en contexto y devuelve una instancia de ILogger (potencialmente) diferente de la que se recuperaría para YourClass. ¿Implemento algún tipo de proveedor MEF?

En este punto, todavía no sé sobre MEF.

Estoy interesado en aprender más sobre cómo las personas inyectan el registro con plataformas de inyección de dependencia. Aunque los enlaces a continuación y mis ejemplos se refieren a log4net y Unity, no necesariamente usaré ninguno de estos. Para la inyección de dependencia / IOC, probablemente use MEF ya que ese es el estándar en el que se basa el resto del proyecto (grande).

Soy muy nuevo en la inyección de dependencia / ioc y soy bastante nuevo en C # y .NET (he escrito muy poco código de producción en C # /. NET después de los últimos 10 años más o menos de VC6 y VB6). He investigado mucho las diversas soluciones de registro que existen, así que creo que tengo un buen manejo de sus conjuntos de características. Simplemente no estoy lo suficientemente familiarizado con la mecánica real de inyectar una dependencia (o, quizás más "correctamente", inyectar una versión abstracta de una dependencia).

He visto otras publicaciones relacionadas con el registro y / o la inyección de dependencias, como: inyección de dependencias e interfaces de registro

Mejores prácticas de registro

¿Cómo sería una clase Log4Net Wrapper?

de nuevo sobre log4net y la configuración Unity IOC

Mi pregunta no tiene que ver específicamente con "Cómo inyectar la plataforma de registro xxx con la herramienta ioc yyy?" Más bien, estoy interesado en cómo las personas han manejado el ajuste de la plataforma de registro (como suele ser, pero no siempre se recomienda) y la configuración (es decir, app.config). Por ejemplo, usando log4net como ejemplo, podría configurar (en app.config) una cantidad de registradores y luego obtener esos registradores (sin inyección de dependencia) en la forma estándar de usar código como este:

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

Alternativamente, si mi registrador no recibe el nombre de una clase, sino que, para un área funcional, podría hacer esto:

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

Entonces, supongo que mis "requisitos" serían algo como esto:

  1. Me gustaría aislar la fuente de mi producto de una dependencia directa de una plataforma de registro.

  2. Me gustaría poder resolver una instancia de registrador con nombre específico (probablemente compartiendo la misma instancia entre todos los solicitantes de la misma instancia nombrada), ya sea directa o indirectamente mediante algún tipo de inyección de dependencia, probablemente MEF.

  3. No sé si llamaría a esto un requisito difícil, pero me gustaría la capacidad de obtener un registrador con nombre (diferente del registrador de clases) a pedido. Por ejemplo, podría crear un registrador para mi clase basado en el nombre de la clase, pero un método necesita diagnósticos pesados ​​que me gustaría controlar por separado. En otras palabras, podría querer que una sola clase "dependa" de dos instancias de registrador separadas.

Comencemos con el número 1. He leído varios artículos, principalmente aquí en stackoverflow, sobre si es o no una buena idea para envolver. Vea el enlace de "mejores prácticas" arriba y vaya al comentario de jeffrey hantin para una vista sobre por qué es malo envolver log4net. Si lo hiciste (y si pudieras envolverlo efectivamente) ¿lo envolverías estrictamente con el propósito de inyectar / eliminar la depracción directa? ¿O también tratarías de abstraer toda o parte de la información de log4net app.config?

Digamos que quiero usar System.Diagnostics, probablemente desearía implementar un registrador basado en interfaz (tal vez incluso usando la interfaz ILogger / ILog "común"), probablemente basado en TraceSource, para poder inyectarlo. ¿Implementarías la interfaz, digamos sobre TraceSource, y solo usarías la información de System.Diagnostics app.config como está?

Algo como esto:

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

Y úsalo así:

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

Pasando al número 2 ... ¿Cómo resolver una determinada instancia de registrador con nombre? ¿Debería aprovechar la información de la aplicación de configuración de la plataforma de registro que elijo (es decir, resolver los registradores en función del esquema de nombres en el app.config)? Entonces, en el caso de log4net, ¿podría preferir "inyectar" LogManager (tenga en cuenta que sé que esto no es posible debido a que es un objeto estático)? Podría envolver LogManager (llámalo MyLogManager), darle una interfaz ILogManager y luego resolver la interfaz MyLogManager.ILogManager. Mis otros objetos podrían tener una depenencia (Importar en el lenguaje MEF) en ILogManager (Exportar desde el ensamblaje donde se implementa). Ahora podría tener objetos como este:

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

Cada vez que se llama a ILogManager, delegaría directamente en LogManager de log4net. Alternativamente, ¿podría el LogManager empaquetado tomar las instancias de ILogger que se basan en el app.config y agregarlas al contenedor MEF (a?) Por su nombre. Más tarde, cuando se solicita un registrador del mismo nombre, se consulta al LogManager envuelto por ese nombre. Si el ILogger está allí, se resuelve de esa manera. Si esto es posible con MEF, ¿hay algún beneficio al hacerlo?

En este caso, realmente, solo ILogManager se "inyecta" y puede distribuir instancias de ILogger de la forma en que normalmente lo hace log4net. ¿Cómo se compara este tipo de inyección (esencialmente de fábrica) con inyectar las instancias del registrador con nombre? Esto permite un aprovechamiento más fácil del archivo app.config de log4net (u otra plataforma de registro).

Sé que puedo sacar instancias nombradas del contenedor MEF de esta manera:

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

¿Pero cómo consigo las instancias nombradas en el contenedor? Sé sobre el modelo basado en atributos donde podría tener diferentes implementaciones de ILogger, cada una de las cuales tiene un nombre (a través de un atributo MEF), pero eso realmente no me ayuda. ¿Hay alguna manera de crear algo así como un app.config (o una sección en él) que enumeraría los registradores (todos de la misma implementación) por nombre y que MEF podría leer? ¿Podría / debería haber un "administrador" central (como MyLogManager) que resuelva registradores con nombre a través del app.config subyacente y luego inserte el registrador resuelto en el contenedor MEF? De esta forma estaría disponible para otra persona que tenga acceso al mismo contenedor MEF (aunque sin el conocimiento de MyLogManager de cómo usar la información de log.net de la aplicación log4net, parece que el contenedor no podría resolver ningún logger nombrado directamente).

Esto ya se ha hecho bastante largo. Espero que sea coherente. Por favor, siéntase libre de compartir cualquier información específica sobre cómo su dependencia inyectó una plataforma de registro (lo más probable es que tengamos en cuenta log4net, NLog o algo (esperemos que delgado) creado en System.Diagnostics) en su aplicación.

¿Inyectó el "administrador" y le pidió que devuelva las instancias del registrador?

¿Agregó algo de su propia información de configuración en su propia sección de configuración o en la sección de configuración de su plataforma DI para que sea más fácil / posible inyectar instancias de registrador directamente (es decir, hacer que sus dependencias estén en ILogger en lugar de ILogManager).

¿Qué tal tener un contenedor estático o global que tenga la interfaz de ILogManager o el conjunto de instancias de ILogger nombradas en él? Por lo tanto, en lugar de inyectar en el sentido convencional (a través de constructor, propiedad o datos de miembro), la dependencia de registro se resuelve explícitamente a petición. ¿Es esta una buena o mala forma de inyectar la dependencia?

Estoy marcando esto como una wiki comunitaria, ya que no parece una pregunta con una respuesta definitiva. Si alguien siente lo contrario, siéntase libre de cambiarlo.

¡Gracias por cualquier ayuda!


Estoy usando Ninject para resolver el nombre de clase actual para la instancia del registrador como esta:

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

El constructor de una implementación NLog podría verse así:

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

Este enfoque también debería funcionar con otros contenedores de IOC, supongo.


También se puede usar Common.Logging o Simple Logging Facade .

Ambos utilizan un patrón de estilo de localizador de servicio para recuperar un ILogger.

Francamente, el registro es una de esas dependencias que veo poco o ningún valor en la inyección automática.

La mayoría de mis clases que requieren servicios de registro se ven así:

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

Al utilizar Simple Logging Facade, he cambiado un proyecto de log4net a NLog, y he agregado el registro de una biblioteca de terceros que usa log4net además del registro de mi aplicación usando NLog. Es decir, la fachada nos ha servido bien.

Una advertencia que es difícil de evitar es la pérdida de características específicas de un marco de registro u otro, quizás el ejemplo más frecuente es el nivel de registro personalizado.


Veo que descubrió su propia respuesta :) Pero, para las personas en el futuro que tengan esta pregunta sobre cómo NO relacionarse con un marco de trabajo de registro en particular, esta biblioteca: Common.Logging ayuda exactamente con ese escenario.


Creé mi ServiceExportProvider personalizado, por el proveedor registré log4Net logger para inyección de dependencias por MEF. Como resultado, puede usar el registrador para diferentes tipos de inyecciones.

Ejemplo de inyecciones:

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

Ejemplo de uso:

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

Resultado en la consola:

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

Implementación de ServiceExportProvider :

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