Inyección de dependencias y registradores con nombre


Estoy interesado en aprender más sobre cómo las personas inyectan el registro con plataformas de inyección de dependencias. Aunque los enlaces a continuación y mis ejemplos se refieren a log4net y Unity, no necesariamente voy a usar ninguno de ellos. Para dependency injection/IOC, probablemente usaré MEF ya que es el estándar en el que se está asentando el resto del proyecto (large).

Soy muy nuevo en dependency injection / 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 de VC6 y VB6). He investigado mucho las diversas soluciones de registro que existen, así que creo que tengo un manejo decente de sus conjuntos de características. Simplemente no estoy lo suficientemente familiarizado con la mecánica real de obtener una dependencia inyectada (o, tal vez más "correctamente", obtener una versión abstracta de una dependencia inyectada).

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

Mejores prácticas de registro

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

Otra vez acerca de log4net y Unity IOC config

Mi pregunta no tiene que ver específicamente con "¿Cómo inyectar la plataforma de registro xxx usando la herramienta ioc yyy?"Más bien, estoy interesado en cómo las personas han manejado el empaquetado de la plataforma de registro (como a menudo, pero no siempre se recomienda) y la configuración (es decir, la aplicación.config). Por ejemplo, usando log4net como ejemplo, podría configurar (en la aplicación.config) un número de loggers y luego obtener esos loggers (sin inyección de dependencias) 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 se nombra para una clase, sino 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 un directo dependencia de una plataforma de registro.

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

  3. No se si llamaría a esto un requisito difícil, pero me gustaría la capacidad de obtener un logger con nombre (diferente al logger de clase) bajo demanda. Por ejemplo, podría crear un logger para mi clase basado en el nombre de la clase, pero un método necesita diagnósticos particularmente pesados que me gustaría controlar por separado. En otras palabras, es posible que desee que una sola clase "dependa" de dos instancias de registro separadas.

Comencemos con el número 1. He leído una serie de artículos, principalmente aquí en stackoverflow, sobre si es o no una buena idea envolver. Consulte el enlace" mejores prácticas " anterior y vaya al comentario de jeffrey hantin para ver una vista por qué es malo envolver log4net. Si usted envolviera (y si usted pudiera envolver con eficacia) ¿usted envolvería estrictamente para el propósito de la inyección/del retiro del depdency directo? O también trataría de abstraer parte o la totalidad de la aplicación log4net.información de configuración?

Digamos que quiero usar System.Diagnósticos, probablemente me gustaría implementar un registrador basado en la interfaz (tal vez incluso utilizando la interfaz "común" ILogger/ILog), probablemente basado en TraceSource, para que pudiera inyectarlo. Le implemente la interfaz, por ejemplo, a través de TraceSource, y simplemente use el Sistema.Aplicación de diagnóstico.¿información de configuración tal cual?

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 instancia de logger con nombre particular? Debería aprovechar la aplicación.información de configuración de la plataforma de registro que elija (es decir, resolver los registradores en función del esquema de nombres en la aplicación.config)? Por lo tanto, en el caso de log4net, ¿podría preferir " inyectar" LogManager (tenga en cuenta que sé que esto no es posible ya que es un objeto estático)? Podría empaquetar LogManager (llamarlo MyLogManager), darle una interfaz ILogManager, y luego resolver MyLogManager.Interfaz ILogManager. Mis otros objetos podrían tener una dependencia (Importación en lenguaje MEF) en ILogManager (Exportación desde el ensamblado 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 al LogManager de log4net. Alternativamente, podría el LogManager envuelto tomar las instancias de ILogger que se basa en la aplicación.config y añadirlos a la (a ?) MEF contenedor por nombre. Más tarde, cuando se solicita un logger del mismo nombre, se consulta el LogManager envuelto para ese nombre. Si el ILogger está allí, se resuelve de esa manera. Si esto es posible con MEF, ¿hay algún beneficio hacerlo?

En este caso, realmente, solo ILogManager se "inyecta" y puede entregar instancias de ILogger de la manera en que log4net normalmente lo hace. ¿Cómo se compara este tipo de inyección (esencialmente de una fábrica) con la inyección de las instancias de logger nombradas? Esto permite un aprovechamiento más fácil de la aplicación log4net (u otra plataforma de registro).archivo de configuración.

Sé que puedo obtener 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 puedo introducir las instancias nombradas en el contenedor? Conozco el modelo basado en atributos donde podría tener diferentes implementaciones de ILogger, cada una de que se nombra (a través de un atributo MEF), pero eso realmente no me ayuda. ¿Hay una manera de crear algo así como una aplicación.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 los loggers nombrados a través de la aplicación subyacente.config y luego inserta el logger resuelto en el contenedor MEF? De esta manera estaría disponible para alguien más que tenga acceso a la mismo contenedor MEF (aunque sin el conocimiento de MyLogManager de cómo usar la aplicación de log4net.información de configuración, parece que el contenedor no sería capaz de resolver ningún registrador con nombre directamente).

Esto ya ha llegado 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 consideremos log4net, NLog o algo (con suerte delgado) construido en el Sistema.Diagnóstico) en su aplicación.

¿Inyectó el "manager" y lo hizo devolver instancias de logger?

¿Ha añadido alguna 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 logger directamente (es decir, hacer que sus dependencias estén en ILogger en lugar de ILogManager).

¿Qué hay de tener un contenedor estático o global que tenga la interfaz ILogManager en él o el conjunto de instancias ILogger con nombre en ella. Por lo tanto, en lugar de inyectar en el sentido convencional (a través de datos de constructor, propiedad o miembro), la dependencia de registro se resuelve explícitamente bajo demanda. Es esta una buena o mala manera de inyectar 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!

Author: wageoghe, 2010-08-10

5 answers

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

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 del COI, supongo.

 36
Author: Robin van der Knaap,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-06-26 22:19:13

También se puede usar Común.Fachada Logging o la Fachada Logging Simple.

Ambos emplean un patrón de estilo de localizador de servicios 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);

}

Utilizando la Fachada de Registro Simple he cambiado un proyecto de log4net a NLog, y he añadido registro desde una biblioteca de terceros que usó 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 de los cuales son los niveles de registro personalizados.

 15
Author: quentin-starin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-08-19 20:07:25

Esto es para el beneficio de cualquier persona que esté tratando de averiguar cómo inyectar una dependencia del registrador cuando el registrador que desea inyectar se proporciona una plataforma de registro como log4net o NLog. Mi problema era que no podía entender cómo podía hacer que una clase (por ejemplo, MyClass) dependiera de una interfaz de tipo ILogger cuando sabía que la resolución del ILogger específico dependería de saber el tipo de clase que depende de ILogger (por ejemplo, MyClass). ¿Cómo funciona el DI/IoC plataforma / contenedor ¿tienes el ILogger correcto?

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

Castle y NInject proporcionan una implementación de logging. Ambos soportan log4net y NLog. El castillo también apoya el sistema.Diagnostico. En ambos casos, cuando la plataforma resuelve las dependencias de un objeto dado (por ejemplo, cuando la plataforma está creando MyClass y MyClass depende de ILogger) delega la creación de la dependencia (ILogger) al "proveedor" de ILogger (resolver podría ser un término más común). La implementación del proveedor ILogger es entonces responsable de instanciar realmente una instancia de ILogger y devolverla, para luego ser inyectada en la clase dependiente (por ejemplo, MyClass). En ambos casos, el proveedor / solucionador conoce el tipo de la clase dependiente (por ejemplo, MyClass). Entonces, cuando MyClass ha sido creado y sus dependencias están siendo resueltas, el ILogger "resolver" sabe que la clase es MyClass. En el caso de usar 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 a log4net.LogManager.getLogger () o NLog.LogManager.getLogger(). (No estoy 100% seguro de la sintaxis para log4net y NLog, pero entiendes la idea).

Mientras que AutoFac y StructureMap no proporcionan una instalación de registro (al menos eso podría decir por mirando), parecen proporcionar la capacidad de implementar resoluciones personalizadas. Por lo tanto, si desea escribir su propia capa de abstracción de registro, también podría escribir un solucionador personalizado correspondiente. De esa manera, cuando el contenedor quiera resolver ILogger, su resolución se usaría para obtener el ILogger correcto y tendría acceso al contexto actual (es decir, qué dependencias de objeto se están satisfaciendo actualmente - qué objeto depende de ILogger). Obtener el tipo de objeto, y usted es listo para delegar la creación del ILogger a la plataforma de registro configurada actualmente (que probablemente haya abstraído detrás de una interfaz y para la que haya escrito un solucionador).

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 ser consciente, de alguna manera, de lo que la tala plataforma a utilizar. Por lo general, esto es hecho especificando que "ILogger" es para ser resuelto por un " solucionador" que es específico de una plataforma de registro (por lo tanto, Castle tiene log4net, NLog, y el Sistema.Diagnóstico " solucionadores" (entre otros)). Especificación de los cuales resolver a utilizar se puede hacer mediante archivo de configuración o mediante programación.

  2. El solucionador necesita saber el contexto para el cual la dependencia Se está resolviendo. Que es, si MyClass ha sido creado y depende de ILogger, entonces cuando el solucionador está tratando de crear el ILogger correcto, es (el resolver) debe conocer el tipo actual (MyClass). De esa manera, el solucionador puede utilizar el registro subyacente implementación (log4net, NLog, etc) para obtener el registrador correcto.

Estos puntos pueden ser obvios para los usuarios de DI/IoC por ahí, pero ahora estoy entrando en él, por lo que me ha llevado un tiempo para obtener mi cabeza alrededor de él.

Una cosa que aún no he descubierto es cómo o si algo como esto es posible con MEF. ¿Puedo tener un objeto dependent on an interface and then have my code execute after MEF has created the object and while the interface/dependency is being resolved? Por lo tanto, 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 para 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 ILogger basado en el hecho de que es MyClass que está actualmente en contexto y devolver un (potencialmente) ¿una instancia de ILogger diferente a la que se recuperaría para YourClass? ¿Implemento algún tipo de Proveedor de MEF?

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

 13
Author: wageoghe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-08-19 19:25:16

Veo que descubriste tu propia respuesta :) Pero, para la gente en el futuro que tenga esta pregunta sobre cómo NO atarse a un marco de registro en particular, esta biblioteca: Común.El registro ayuda con exactamente ese escenario.

 5
Author: CubanX,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-08-19 19:38:37

Hice mi personalizado ServiceExportProvider, por el proveedor registro Log4Net logger para inyección de dependencia por MEF. Como resultado, puede usar logger 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

ServiceExportProvider implementación:

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
Author: R.Titov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-21 10:58:37