Каков наилучший способ использования NLog с MEF? - PullRequest
5 голосов
/ 14 сентября 2010

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

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

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

Ответы [ 4 ]

6 голосов
/ 06 октября 2010

Это интересный подход, однако, как представляется, он страдает от недостатка, заключающегося в том, что все вводимые логгеры (или один внедренный синглтон) будут одинаковым экземпляром (или будут иметь одинаковое имя, причем имя является имя класса 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-speak). Если бы я это сделал, то оба регистратора, которые я запрашивал через свои полные имена, унаследовали бы конфигурацию MyNamespace.

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

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

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

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

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

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

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

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

1 голос
/ 31 декабря 2013

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

Вот экспортный провайдер

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.

1 голос
/ 18 июля 2013

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

Действительно неподходящим был 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Интерфейс с помощью Constructor Injection, я создал интерфейс ILogFactory, который может вводиться с помощью Constructor Injection и вызывать затем метод Create на фабрике

    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 и вызовите метод Create в конструкторе импорта Mefed:

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

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

1 голос
/ 22 сентября 2010

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

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

Для справки:

SoapBox Core - LGPL, поэтому вы можете использовать (эту часть) в своем приложении.

...