2010-09-13 9 views
5

Мне интересно, как наилучшим образом использовать NLog с Managed Extensibility Framework (MEF)?Каков наилучший способ использования NLog с MEF?

У меня есть приложение, поддерживающее плагины с использованием архитектуры MEF (импорт и экспорт и т. Д.) Я хочу добавить возможности ведения журнала в свое приложение. В качестве компонента регистрации я хочу использовать NLog.

Что вы посоветуете? 1. Создайте обертку для NLog, то есть дополнительный плагин, который настраивает NLog и экспортирует такие функции, как void Log (строковый уровень, строковое сообщение), которые импортируют другие плагины: 2. Каждый плагин должен иметь собственный экземпляр NLog, настроенный и используемый. (Они все напишут в тот же файл на самом деле).

ответ

6

Это интересный подход, однако он, кажется, страдает от недостатка, что все введенные в систему регистраторы (или один инжектор) будут одним и тем же экземпляром (или будут иметь одно и то же имя, имя класса NLogLoggingService.Это означает, что вы не можете очень легко контролировать гранулярность ведения журнала (т. е. включить ведение журнала на уровень «Информация» в одном классе и «Предупреждать» в другом классе). Также, если вы решите использовать сайт вызова форматирования маркеров, вы всегда получите сайт вызова вызова в NLog регистратора, а не на месте вызова в коде приложения

Вот сокращенный вариант регистратора, который был связан:.

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

В конструкторе LogManager.GetCurrentClassLogger() используется для получения регистратора NLog. GetCurrentClassLogger вернет логгер NLog, который «назван» на основе «текущего» типа, который в этом случае является NLogLoggingService. Итак, чтобы настроить NLog в файле app.config, вы будете конфигурировать на основе того, что регистратор называется «SoapBox.Core.NLogLoggingService». Обычно, в коде, который использует NLog (или log4net) непосредственно, каждый класс получает свой собственный уникальное имя логгера, как это:

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

Теперь протоколирование для MyClass1 и MyClass2 индивидуально контролируемое. Вы можете настроить разные уровни для каждого класса, отправить их другим целям или полностью отключить один или оба. В качестве альтернативы, из-за концепции иерархии журналов как в log4net, так и в NLog, вы можете управлять журналом в обоих классах одновременно, настраивая «регистратор» для пространства имен (в данном случае это MyNamespace) или любое пространство имен «предков». Если для полнофункционального имени нет логина, то структура ведения журнала существенно перемещает иерархию, рассматривая имя строки с разделителями точек и удаляя последний кусок и проверяя, настроен ли этот журнал. Итак, в этом случае мы запрашиваем регистраторы для MyNamespace.MyClass1 и MyNamespace.MyClass2.Я могу настроить файл app.config, чтобы иметь журнал MyNamespace в «info» и записывать в целевой файл (appender в log4net-talk). Если бы я это сделал, то оба регистратора, которые я запросил через их полностью квалифицированные имена, наследуют конфигурацию MyNamespace.

С предлагаемым способом ввода NLog через MEF у вас будет только один экземпляр регистратора, поэтому вы не сможете настроить каждый класс для записи по-разному. Кроме того, как я уже упоминал ранее, если вы выберете регистрацию информации сайта звонка, вы всегда получите «SoapBox.Core.NLogLoggingService» для класса и «Debug» (или DebugWithFormat, или Info, или InfoWithFormat и т. Д.) Для этого метода.

Это, похоже, проблема с успешным вводом регистраторов из log4net и NLog. Вы можете увидеть question, что я спросил об этом выпуске пару месяцев назад.

В конечном итоге мне удалось выяснить, как некоторые среды инъекций зависимостей могут успешно вводить log4net и логические записи NLog, которые являются специфическими для создаваемого класса (т.е. если среда DI создает экземпляр MyClass, что, в свою очередь, зависит от интерфейса ILogger, то MyClass получит регистратор, который по сути эквивалентен тому, что произошло бы, если MyClass запросил сам журнал регистрации через LogManager.GetCurrentClassLogger api). Обычно «резольверы» в каркасах DI/IoC получают текущий контекст (содержащий, помимо прочего, тип создаваемого объекта). Имея доступ к этому типу, становится проще, чтобы речевой механизм, зависящий от структуры ведения журнала, получил этот тип и передал его вместе с фреймворком ведения журнала для создания регистратора, соответствующего этому типу.

Чтобы получить максимальную отдачу от возможностей NLog (и log4net), вам действительно хотелось бы сказать MEF, что ваш класс зависит от «ILogger», а также что экземпляр «ILogger», который вводится в ваш класс должен зависеть от типа вашего класса.

Я не знаю, как легко достичь этого с помощью MEF. Кроме того, вы можете обернуть статический LogManager NLog в ILogManager и ввести его. Это отклонилось бы от обычной парадигмы «инъекции ILogger».

Подводя итог: Если вы вводите NLog через MEF таким образом, вы действительно сможете зарегистрироваться в NLog, но у вас будет только один именованный логгер (SoapBox.Core.NLogLoggingService). Это означает, что вы не сможете контролировать с какой-либо степенью детализации - либо для уровней/включения/выключения, либо для вывода (NLog Target/log4net Appender)

У меня нет хорошего ответа, что делать дальше как ввод NLog через MEF и сохранение гранулярности/гибкости, которую дает вам «сырой» NLog.

Я могу сказать, что мы решили использовать Common.Logging for .NET для абстрагирования рамки ведения журнала, но мы решили НЕ вводить каротаж. Вместо этого мы просто будем использовать статический LogManager (как это предусмотрено Common.Logging) для раздачи журналов.

1

Я думаю, что вариант 1 лучше.

Вы можете посмотреть, как инфраструктура с открытым исходным кодом SoapBox Core импортирует ссылку на ILoggingService с использованием MEF. Он также предоставляет стандартную реализацию службы ведения журнала на основе NLog, но вы можете легко ее заменить для log4Net, например.

Для справки:

мыльница Ядро является LGPL'd, так что вы можете быть в состоянии использовать (эта часть) в вашем приложении.

+1

Это прекрасная реализация MEF-совместимого регистратора NLog, но он страдает от нескольких недостатков: 1. Все регистраторы MEF-ed для приложения фактически будут одним и тем же журналом, поэтому у вас нет большой контроль над вашим протоколированием. 2. Информация о вызывающем сайте теряется, поскольку будет использоваться сайт вызова класса ведения журнала. У меня на голове нет лучшего предложения для MEF, но я думаю, что стоит отметить эти недостатки. См. Мой ответ для более длинной версии. – wageoghe

0

Я уже давно борюсь с этой проблемой.

Действительно, в лог-файлах действительно был Callsite (FullyQualified Namespace).

Во-первых, я судимое, чтобы получить правильный регистратор из 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); 
    } 

Но к сожалению, StackTrace с MEF очень долго, и я не могу четко определить правильный вызывающему для Заявителя в ILogger.

Таким образом, вместо инъекции ILogger интерфейса с помощью конструктора инъекций, я создал ILogFactory интерфейс, который может получить впрыскивается через Constructor Injection и вызвать затем метод создания на заводе

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 
    } 

и реализовали его:

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 
    } 

с 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 
    } 

и реализация:

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

Чтобы использовать его ... просто впрыснуть ILogFactory и Калле Создать метод в Импортирования Конструктора Мефед:

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

надеюсь, что это помогает

0

Если вы создаете новый ExportProvider, и передайте ImportDefinition в ICompositionElement. Вы можете получить тип, в который вводится регистратор.

Вот 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; 
    } 
} 

Это установка таким образом, что он будет работать с любой среде журналирования, как вам нужно передать в функцию, которая будет возвращать ILogger (ILogger наши собственные, вам нужно будет создать свой собственный интерфейс или просто указать его на Nlog). Строка, передаваемая функции, - это полное имя класса, которое также вводится в тип. (compositionElement.Origin.DisplayName)

Пример бутстрапирования MEF с этим будет выглядеть следующим образом:

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

Код выше был скопирован из модульного теста, так что я просто добавить определенные узлы вместо разбора каталога. MockLogger представляет собой реализацию интерфейса ILogger, который принимает имя класса журнала (или тип ввода) в качестве параметра его конструктору.

Это не требует разбора любых следов стека и вытягивает информацию, которая иначе сидит там непосредственно из MEF.

+0

Я не уверен, что составElement будет когда-либо пустым, но это должно было сделать resharper счастливым, он проходит модульные тесты, но пока не находится в производственном коде. – Casey

+0

Это оказалось в производственном коде и отлично работает! Хотя я больше не использую MEF на моем текущем концерте, поэтому ... – Casey

Смежные вопросы