将NLog与MEF一起使用的最佳方法是什么?

时间:2010-09-13 23:54:20

标签: dependency-injection inversion-of-control mef nlog

我想知道将NLog与托管可扩展性框架(MEF)一起使用的最佳方法是什么?

我有一个使用MEF架构支持插件的应用程序(导入和导出等) 我想为我的应用程序添加日志记录功能。 作为日志记录组件,我想使用NLog。

你会推荐什么? 1.为NLog创建一个包装器,即配置NLog并导出其他插件导入的void Log(字符串级别,字符串消息)等函数的附加插件 2.每个插件都应该拥有自己配置和使用的NLog实例。 (他们都会写到同一个文件)。

4 个答案:

答案 0 :(得分:6)

这是一种有趣的方法,然而,它似乎有一个缺点,即注入的所有记录器(或注入的一个单独的)将是相同的实例(或将具有相同的名称,名称是NLogLoggingService类的名称。这意味着你不能很容易地控制日志记录的粒度(即在一个类中将日志记录转换为“Info”级别,在另一个类中转换为“Warn”)。另外,如果您选择使用调用站点格式化令牌,您将始终获得呼叫的呼叫站点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。因此,要在app.config文件中配置NLog,您将根据记录器名为“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)或任何“祖先”命名空间配置“logger”来同时控制两个类中的日志记录。如果没有为完全限定的类型名配置记录器,则日志记录框架实质上是通过将名称设置为点分隔字符串并删除最后一个块并检查是否已配置该记录器来向上移动层次结构。因此,在这种情况下,我们要求MyNamespace.MyClass1和MyNamespace.MyClass2的记录器。我可以配置app.config文件让MyNamespace登录“info”并写入文件目标(log4net-speak中的appender)。如果我这样做,那么我通过其完全限定名称请求的两个记录器都将继承MyNamespace配置。

使用建议的通过MEF注入NLog的方法,您将只有一个记录器实例,因此您不能将每个类配置为以不同方式记录。此外,正如我之前提到的,如果您选择记录调用站点信息,您将始终获得该类的“SoapBox.Core.NLogLoggingService”和该方法的“Debug”(或DebugWithFormat,或Info或InfoWithFormat等)。

这似乎是从log4net和NLog成功注入记录器的问题。几个月前你可以看到我问过这个问题的question

最终,我能够弄清楚一些依赖注入框架如何成功注入特定于正在创建的类的log4net和NLog记录器(即,如果DI框架实例化MyClass,而MyClass又依赖于ILogger接口,那么MyClass将获得一个记录器,它基本上等同于MyClass通过LogManager.GetCurrentClassLogger api请求记录器本身时发生的记录器。通常,DI / IoC框架中的“解析器”被赋予当前上下文(包含当前正在创建的对象的类型等信息)。有了这种类型,一个简单的问题就是让特定于日志框架的解析器接收该类型并将其传递给日志框架以创建适合该类型的记录器。

为了充分利用NLog(和log4net)的功能,你真的希望能够告诉MEF你的类依赖于“ILogger”,而且还要注入“ILogger”的实例。你的班级应该取决于班级的类型。

我不知道用MEF实现这一目标是多么容易。或者,您可以将NLog的静态LogManager包装在ILogManager中并注入它。这将偏离正常的“注入ILogger”范式。

总结一下:如果以这种方式通过MEF注入NLog,您确实可以使用NLog进行日志记录,但是您只能拥有一个命名的记录器(SoapBox.Core.NLogLoggingService)。这意味着您将无法以任何粒度控制 - 无论是级别/开/关还是输出(NLog Target / log4net Appender)

对于通过MEF注入NLog并保持“原始”NLog为您提供的粒度/灵活性,我没有一个好的答案。

我可以说我们已决定使用Common.Logging for .NET来抽象日志框架,但我们决定不注入日志记录。相反,我们将使用静态LogManager(由Common.Logging提供)来分发记录器。

答案 1 :(得分:1)

我认为选项1更好。

您可以看一下开源框架SoapBox Core如何使用MEF导入对ILoggingService的引用。它还提供了基于NLog的日志服务的默认实现,但您可以轻松地将其替换为log4Net,例如。

供参考:

SoapBox Core是LGPL,因此您可以在应用程序中使用(此部分)。

答案 2 :(得分:1)

我一直在与这个问题作斗争。

真正重要的是日志文件中的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);
    }

但遗憾的是,带有MEF的Stacktrace非常长,我无法清楚地识别出I​​Logger请求者的正确来电者。

因此,我没有通过构造函数注入注入ILogger接口,而是创建了一个ILogFactory接口,可以通过构造函数注入注入,然后调用工厂上的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并在Mefed导入构造函数中调用Create方法:

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

希望这会有所帮助

答案 3 :(得分:1)

如果您创建一个新的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是我们自己的,你必须创建自己的接口或只是make它特定于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中取出的信息拉出来。