2010-09-13 16 views
5

Me pregunto cuál es la mejor manera de usar NLog con Managed Extensibility Framework (MEF)?¿Cuál es la mejor forma de usar NLog con MEF?

Tengo una aplicación que admite complementos que utilizan la arquitectura MEF (Importar y exportar, etc.) Quiero agregar capacidad de registro a mi aplicación. Como componente de registro, quiero usar NLog.

¿Qué recomendarías? 1. Cree un contenedor para NLog, es decir, un complemento adicional que configura NLog y exporta funciones como void Log (nivel de cadena, mensaje de cadena) que otros complementos importan 2. Cada complemento debe tener su propia instancia de NLog configurada y utilizada. (Todos escribirían en el mismo archivo en realidad).

Respuesta

6

Este es un enfoque interesante, sin embargo, parece adolecer del inconveniente de que todos los registradores que se inyectan (o el único singleton que se inyecta) serán la misma instancia (o tendrán el mismo nombre, siendo el nombre el nombre de la clase NLogLoggingService. Eso significa que no puede controlar fácilmente la granularidad del registro (es decir, activar el registro en el nivel "Información" en una clase y "Advertir" en otra clase). También, si opta por utilizar el sitio de llamada formateo de fichas, siempre obtendrá el sitio llamado de la llamada, el registrador Nlog en lugar del sitio de llamadas en el código de aplicación

Aquí es una versión abreviada del registrador que se vinculó:.

[Export(Services.Logging.LoggingService, typeof(ILoggingService))] 
    class NLogLoggingService : ILoggingService 
    { 
    Logger log; public NLogLoggingService() 
    { 
     log = LogManager.GetCurrentClassLogger(); 
    } 

    public void Debug(object message) 
    { 
     log.Debug(message); 
    } 
    public void DebugWithFormat(string format, params object[] args) 
    { 
     if (args.Length == 0) 
     { 
     log.Debug(format); 
     } 
     else 
     { 
     Debug(string.Format(format, args)); 
     } 
    } 
    public bool IsDebugEnabled 
    { 
     get 
     { 
     return log.IsDebugEnabled; 
     } 
    } 
    } 

En el constructor LogManager.GetCurrentClassLogger() se utiliza para obtener el registrador NLog. GetCurrentClassLogger devolverá un registrador NLog que está "nombrado" basado en el tipo "actual", que, en este caso, es NLogLoggingService. Entonces, para configurar NLog en el archivo app.config, se configurará en función de que el registrador se llame "SoapBox.Core.NLogLoggingService". Comúnmente, en el código que utiliza Nlog (o log4net) directamente, cada clase tiene su propio nombre único registrador de la siguiente manera:

namespace MyNamespace 
{ 
    public class MyClass1 
    { 
    private static readonly Logger logger LogManager.GetCurrentClassLogger(); 

    public void DoSomeWork() 
    { 
     logger.Info("Logging from inside MyClass1.DoSomeWork"); 
    } 
    } 

    public class MyClass2 
    { 
    private static readonly Logger logger LogManager.GetCurrentClassLogger(); 

    public void DoSomeWork() 
    { 
     logger.Info("Logging from inside MyClass2.DoSomeWork"); 
    } 
    } 
} 

Ahora el registro para MyClass1 y miclase2 es regulable. Puedes configurar diferentes niveles para cada clase, enviarlos a diferentes objetivos o desactivarlos por completo. Alternativamente, debido al concepto de jerarquías de registrador en log4net y NLog, puede controlar el registro en ambas clases simultáneamente configurando un "registrador" para el espacio de nombres (MyNamespace en este caso), o cualquier espacio de nombres "ancestro". Si no hay un registrador configurado para el nombre de tipo completamente calificado, entonces el marco de registro esencialmente sube la jerarquía considerando el nombre una cadena delimitada por puntos y eliminando el último fragmento y comprobando si ese registrador está configurado. Entonces, en este caso, estamos solicitando registradores para MyNamespace.MyClass1 y MyNamespace.MyClass2.Podría configurar el archivo app.config para que MyNamespace inicie sesión en la "información" y escriba en un destino de archivo (appender en log4net-speak). Si lo hice, los dos registradores que solicité a través de sus nombres completos heredarán la configuración de MyNamespace.

Con la forma sugerida de inyectar NLog a través de MEF, solo tendrá una instancia de registrador, por lo que no puede configurar cada clase para que se registre de manera diferente. Además, como mencioné anteriormente, si opta por registrar la información del sitio de llamadas, siempre obtendrá "SoapBox.Core.NLogLoggingService" para la clase y "Debug" (o DebugWithFormat, o Info, o InfoWithFormat, etc.) para el método.

Esto parece ser un problema al inyectar con éxito los registradores de log4net y NLog. Puedes ver el question que pregunté sobre este mismo problema hace un par de meses.

Al final pude averiguar cómo algunos marcos de inyección de dependencia pueden inyectar log4net y NLog loggers que son específicos de la clase que se está creando (es decir, si el marco DI crea una instancia de MyClass, que a su vez depende de una interfaz de ILogger, entonces MyClass obtendrá un registrador que es esencialmente equivalente a lo que hubiera sucedido si MyClass solicitara el logger a través de LogManager.GetCurrentClassLogger api). En general, los "resolvers" en los marcos DI/IoC reciben el contexto actual (que contiene, entre otra información, el tipo de objeto que se está creando actualmente). Con ese tipo disponible, se convierte en una simple cuestión de tener un resolvedor específico de un framework de registro que reciba ese tipo y pasarlo junto al marco de registro para crear un logger apropiado para ese tipo.

Para aprovechar al máximo las capacidades de NLog (y log4net), realmente le gustaría poder decirle a MEF que su clase es dependiente de "ILogger", pero también que la instancia de "ILogger" que se inyecta en tu clase debe depender del tipo de tu clase.

No sé qué tan fácil será lograr eso con MEF. Alternativamente, podría ajustar el LogManager estático de NLog en un ILogManager e inyectarlo. Eso se desviaría del paradigma "inyectar ILogger" normal.

En resumen: si se inyecta NLog a través de MEF de esta manera, de hecho podrá iniciar sesión con NLog, pero solo tendrá un registrador con nombre (SoapBox.Core.NLogLoggingService). Esto significa que no podrá controlar con ningún grado de granularidad, ya sea para niveles/encendido/apagado o para salida (NLog Target/log4net Appender)

No tengo una buena respuesta para qué hacer en la medida de lo posible como inyectar NLog a través de MEF Y manteniendo la granularidad/flexibilidad que le da NLog "en bruto".

Puedo decir que hemos decidido usar Common.Logging for .NET para abstraer el marco de trabajo pero decidimos NO inyectar el registro. En su lugar, solo usaremos un LogManager estático (como lo proporciona Common.Logging) para entregar los registradores.

1

Creo que la Opción 1 es mejor.

Puede ver cómo el marco de código abierto SoapBox Core importa una referencia a un ILoggingService utilizando MEF. También proporciona una implementación predeterminada del servicio de registro basado en NLog, pero puede cambiarlo fácilmente para log4Net, por ejemplo.

Como referencia:

SoapBox Core es LGPL, por lo que podría ser capaz de utilizar (esta parte) en su aplicación.

+1

Esa es una buena aplicación de un registrador NLog capaz de MEF, pero adolece de un par de inconvenientes: 1. Todos los registradores MEF-ed para una aplicación serán en realidad el mismo registrador, por lo que no tiene mucho control sobre su registro. 2. La información del sitio de llamada se pierde porque se utilizará el sitio de llamada de la clase de registro. Por la parte superior de mi cabeza no tengo una mejor sugerencia para MEF, pero creo que vale la pena señalar esas deficiencias. Ver mi respuesta para una versión más larga. – wageoghe

0

He estado luchando con este problema desde hace un tiempo.

Realmente improtante era el Callsite (espacio de nombres completamente calificado) dentro de los archivos de registro.

En primer lugar, i tryed para obtener el registrador de la derecha de la StackTrace:

[MethodImpl(MethodImplOptions.NoInlining)] 
    private static NLog.Logger GetLogger() 
    { 
     var stackTrace = new StackTrace(false); 
     StackFrame[] frames = stackTrace.GetFrames(); 
     if (null == frames) throw new ArgumentException("Stack frame array is null."); 
     StackFrame stackFrame; 
     switch (frames.Length) 
     { 
      case 0: 
       throw new ArgumentException("Length of stack frames is 0."); 
      case 1: 
      case 2: 
       stackFrame = frames[frames.Length - 1]; 
       break; 
      default: 
       stackFrame = stackTrace.GetFrame(2); 
       break; 
     } 

     Type declaringType = stackFrame.GetMethod() 
             .DeclaringType; 

     return declaringType == null ? LogManager.GetCurrentClassLogger() :     LogManager.GetLogger(declaringType.FullName); 
    } 

Pero, lamentablemente, la StackTrace con MEF es muy larga y no puedo identificar claramente la persona que llama correcta para el Solicitante de la ILogger.

Así, en lugar de inyectar la interfaz ILogger través Constructor de inyección, he creado una interfaz ILogFactory, que puede conseguir inyectado a través del constructor de inyección y llamar entonces el método create en la fábrica

public interface ILogFactory 
    { 
     #region Public Methods and Operators 

     /// <summary> 
     ///  Creates a logger with the Callsite of the given Type 
     /// </summary> 
     /// <example> 
     ///  factory.Create(GetType()); 
     /// </example> 
     /// <param name="type">The type.</param> 
     /// <returns></returns> 
     ILogger Create(Type type); 

     #endregion 
    } 

Y puesto en práctica:

using System; 
    using System.ComponentModel.Composition; 

    [Export(typeof(ILogFactory))] 
    [PartCreationPolicy(CreationPolicy.Shared)] 
    public class LogFactory : ILogFactory 
    { 
     #region Public Methods and Operators 

     public ILogger Create(Type type) 
     { 
      var logger = new Logger().CreateLogger(type); 
      return logger; 
     } 

     #endregion 
    } 

Con la ILogger:

public interface ILogger 
    { 
     #region Public Properties 

     bool IsDebugEnabled { get; } 

     bool IsErrorEnabled { get; } 

     bool IsFatalEnabled { get; } 

     bool IsInfoEnabled { get; } 

     bool IsTraceEnabled { get; } 

     bool IsWarnEnabled { get; } 

     #endregion 

     #region Public Methods and Operators 

     void Debug(Exception exception); 
     void Debug(string format, params object[] args); 
     void Debug(Exception exception, string format, params object[] args); 
     void Error(Exception exception); 
     void Error(string format, params object[] args); 
     void Error(Exception exception, string format, params object[] args); 
     void Fatal(Exception exception); 
     void Fatal(string format, params object[] args); 
     void Fatal(Exception exception, string format, params object[] args); 
     void Info(Exception exception); 
     void Info(string format, params object[] args); 
     void Info(Exception exception, string format, params object[] args); 
     void Trace(Exception exception); 
     void Trace(string format, params object[] args); 
     void Trace(Exception exception, string format, params object[] args); 
     void Warn(Exception exception); 
     void Warn(string format, params object[] args); 
     void Warn(Exception exception, string format, params object[] args); 

     #endregion 
    } 

e Implementación de:

using System; 

     using NLog; 
     using NLog.Config; 

     /// <summary> 
     ///  The logging service. 
     /// </summary> 
     public class Logger : NLog.Logger, ILogger 
     { 
      #region Fields 

      private string _loggerName; 

      #endregion 

      #region Public Methods and Operators 

      /// <summary> 
      ///  The get logging service. 
      /// </summary> 
      /// <returns> 
      ///  The <see cref="ILogger" />. 
      /// </returns> 
      public ILogger CreateLogger(Type type) 
      { 
       if (type == null) throw new ArgumentNullException("type");    

       _loggerName = type.FullName; 

       var logger = (ILogger)LogManager.GetLogger(_loggerName, typeof(Logger)); 

       return logger; 
      } 

para usarlo ... simplemente inyectar el ILogFactory y la calle del método de creación de un constructor Importación de Mefed:

 [ImportingConstructor] 
     public MyConstructor(   
     ILogFactory logFactory) 
     { 
     _logger = logFactory.Create(GetType()); 
     } 

esperanza esto ayuda

0

Si crea un nuevo ExportProvider y envía el ImportDefinition que se pasa a un ICompositionElement. Puede obtener el tipo en el que se está inyectando el registrador.

Aquí es el ExportProvider

public class LoggerExportProvider : ExportProvider 
{ 
    private readonly ExportDefinition _loggerExportDefinition; 

    private readonly Func<string, ILogger> _loggerFactory; 

    /// <summary> 
    /// Initializes a new instance of the <see cref="LoggerExportProvider"/> class. 
    /// </summary> 
    /// <param name="loggerFactory">The logger factory function.</param> 
    public LoggerExportProvider(Func<string, ILogger> loggerFactory) 
    { 
     _loggerFactory = loggerFactory; 
     _loggerExportDefinition = new ExportDefinition(typeof (ILogger).FullName, new Dictionary<string, object> {{"ExportTypeIdentity", typeof (ILogger).FullName}}); 
    } 

    protected override IEnumerable<Export> GetExportsCore(ImportDefinition definition, AtomicComposition atomicComposition) 
    { 
     IList<Export> exports = new List<Export>(); 
     var compositionElement = definition as ICompositionElement; 
     if (compositionElement == null || compositionElement.Origin == null) 
      return exports; 

     var constraint = definition.Constraint.Compile(); 
     if (constraint(_loggerExportDefinition)) 
      exports.Add(new Export(_loggerExportDefinition,() => _loggerFactory(compositionElement.Origin.DisplayName))); 

     return exports; 
    } 
} 

Esta es la configuración de tal manera que va a trabajar con cualquier marco de registro como sea necesario para pasar de una función que devuelva un ILogger (el Ilogger es la nuestra, Tendrás que crear tu propia interfaz o simplemente hacerla específica para Nlog). La cadena que se pasa a la función es el nombre completo de la clase en la que también se está inyectando el tipo. (compositionElement.Origin.DisplayName)

Un ejemplo de MEF arranque con este se vería así:

public class Example 
{ 
    [Import] 
    public ILogger Logger { get; set;} 

    public Example() 
    { 
     var aggregatecatalogue = new AggregateCatalog(); 
     aggregatecatalogue.Catalogs.Add(new AssemblyCatalog(typeof (ILogger).Assembly)); 
     aggregatecatalogue.Catalogs.Add(new AssemblyCatalog(GetType().Assembly)); 
     var container = new CompositionContainer(aggregatecatalogue, new LoggerExportProvider(s => new MockLogger(s))); 
     container.ComposeParts(this); 
    } 
} 

El código anterior fue copiado de una prueba de unidad, así que estoy añadir montajes específicos en lugar de analizar un directorio. MockLogger es una implementación de la interfaz de ILogger que toma el nombre de la clase de registro (o tipo de inyección) como un parámetro para su constructor.

Esto no requiere el análisis de ningún rastro de pila y extrae la información que de lo contrario está directamente allí fuera de MEF.

+0

No estoy seguro de que compositionElement alguna vez sea nulo, pero eso fue para hacer feliz a la resharper, pasa las pruebas de la unidad pero todavía no está en el código de producción. – Casey

+0

Esto terminó en el código de producción y funciona muy bien por cierto! Aunque ya no estoy usando MEF en mi concierto actual, entonces ... – Casey

Cuestiones relacionadas