首页 文章

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

提问于
浏览
5

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

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

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

4 回答

  • 6

    这是一个有趣的方法,然而,它似乎有一个缺点,即注入的所有 Logger (或注入的一个单独的)将是相同的实例(或将具有相同的名称,名称是NLogLoggingService类 . 这意味着你不能很容易地控制日志记录的粒度(即在一个类中将日志记录转换为“Info”级别,在另一个类中转换为“Warn”) . 另外,如果您选择使用调用站点格式化令牌,那么将始终将呼叫的呼叫站点作为NLog Logger 而不是应用程序代码中的呼叫站点 .

    以下是链接的 Logger 的缩写版本:

    [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 Logger . GetCurrentClassLogger将返回一个基于"current"类型的"named"的NLog Logger ,在这种情况下,它是NLogLoggingService . 因此,要在app.config文件中配置NLog,您将根据 Logger 的名称"SoapBox.Core.NLogLoggingService"进行配置 . 通常,在直接使用NLog(或log4net)的代码中,每个类都有自己的唯一命名 Logger ,如下所示:

    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中 Logger 层次结构的概念,您可以通过为命名空间(本例中为MyNamespace)或任何“祖先”命名空间配置“ Logger ”来同时控制两个类中的记录 . 如果没有为完全限定的类型名配置 Logger ,则日志记录框架实质上是通过将名称设置为点分隔字符串并删除最后一个块并检查是否已配置该 Logger 来向上移动层次结构 . 因此,在这种情况下,我们要求MyNamespace.MyClass1和MyNamespace.MyClass2的 Logger . 我可以配置app.config文件让MyNamespace登录“info”并写入文件目标(log4net-speak中的appender) . 如果我这样做,那么我通过其完全限定名称请求的两个 Logger 都将继承MyNamespace配置 .

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

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

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

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

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

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

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

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

  • 1

    我认为选项1更好 .

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

    以供参考:

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

  • 1

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

    真正重要的是日志文件中的Callsite(FullyQualified Namespace) .

    首先,我尝试从Stacktrace中获取正确的 Logger :

    [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非常长,我无法清楚地识别出ILogger请求者的正确调用者 .

    因此,我没有通过构造函数注入注入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());
            }
    

    希望这可以帮助

  • 1

    如果您创建一个新的ExportProvider并将ImportDefinition转换为ICompositionElement . 您可以获取 Logger 注入的类型 .

    这是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中拉出来 .

相关问题