5

Managed Extensibility Framework(MEF)でNLogを使用する最良の方法は何ですか?

MEFアーキテクチャ(インポートおよびエクスポートなど)を使用するプラグインをサポートするアプリケーションがあります。アプリケーションにロギング機能を追加したいと思います。ロギングコンポーネントとして、NLogを使用したいと思います。

あなたは何をお勧めします?1. NLogのラッパーを作成します。つまり、NLogを構成し、他のプラグインがインポートするvoid Log(文字列レベル、文字列メッセージ)などの機能をエクスポートする追加のプラグインを作成します。2。すべてのプラグインにNLogの独自のインスタンスを構成して使用する必要があります。(実際には、すべて同じファイルに書き込みます)。

4

4 に答える 4

6

これは興味深いアプローチですが、注入されるすべてのロガー(または注入される1つのシングルトン)が同じインスタンスになる(または同じ名前になり、名前が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は、「現在の」タイプ(この場合はNLogLoggingService)に基づいて「名前が付けられた」NLogロガーを返します。したがって、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)または任意の「祖先」名前空間の「ロガー」を構成することにより、両方のクラスのロギングを同時に制御できます。完全修飾タイプ名用に構成されたロガーがない場合、ロギングフレームワークは基本的に、名前をドット区切りの文字列と見なし、最後のチャンクを削除して、そのロガーが構成されているかどうかを確認することにより、階層を上に移動します。したがって、この場合、MyNamespace.MyClass1とMyNamespace.MyClass2のロガーを要求しています。アプリを設定できました。MyNamespaceが「info」にログを記録し、ファイルターゲット(log4net-speakのappender)に書き込むようにファイルを構成します。その場合、完全修飾名を介して要求した両方のロガーは、MyNamespace構成を継承します。

MEFを介してNLogを挿入するための推奨される方法では、ロガーインスタンスは1つしかないため、各クラスを異なる方法でログに記録するように構成することはできません。また、前述したように、呼び出しサイト情報をログに記録することを選択した場合、クラスには常に「SoapBox.Core.NLogLoggingService」が取得され、メソッドには「Debug」(またはDebugWithFormat、Info、InfoWithFormatなど)が取得されます。

これは、log4netおよびNLogからロガーを正常に挿入する際の問題のようです。数ヶ月前に私がこの問題について尋ね た質問を見ることができます。

最終的に、いくつかの依存性注入フレームワークが、作成されているクラスに固有のlog4netおよびNLogロガーを正常に注入する方法を理解することができました(つまり、DIフレームワークがMyClassをインスタンス化しており、これがILoggerインターフェイスに依存している場合、MyClassは次のようになります。 MyClassがLogManager.GetCurrentClassLoggerapiを介してロガー自体を要求した場合に発生することと本質的に同等のロガー。一般に、DI / IoCフレームワークの「リゾルバー」には、現在のコンテキストが与えられます(他の情報の中でも、現在作成されているオブジェクトのタイプが含まれます)。そのタイプが使用可能になると、ロギングフレームワーク固有のリゾルバーがそのタイプを受信し、それをロギングフレームワークに渡して、そのタイプに適したロガーを作成するという単純な問題になります。

NLog(およびlog4net)の機能を最大限に活用するには、クラスが「ILogger」に依存していることをMEFに伝えたいだけでなく、クラスに注入される「ILogger」のインスタンスもクラスのタイプによって異なります。

MEFでそれを達成するのがどれほど簡単かはわかりません。または、NLogの静的LogManagerをILogManagerでラップして、それを挿入することもできます。これは、通常の「ILoggerの注入」パラダイムから逸脱します。

要約すると、この方法でMEFを介してNLogを挿入すると、実際にNLogでログを記録できますが、名前付きロガー(SoapBox.Core.NLogLoggingService)は1つしかありません。これは、レベル/オン/オフまたは出力(NLog Target / log4net Appender)のいずれについても、ある程度の粒度で制御できないことを意味します。

MEFを介してNLogを注入し、「生の」NLogが提供する粒度/柔軟性を維持する限り、何をすべきかについての良い答えはありません。

Common.Logging for .NETを使用してロギングフレームワークを抽象化することを決定したと言えますが、ロギングを挿入しないことにしました。代わりに、静的LogManager(Common.Loggingによって提供される)を使用してロガーを配布します。

于 2010-10-06T17:23:53.167 に答える
1

オプション1の方が良いと思います。

オープンソースフレームワークのSoapBoxCoreがMEFを使用してILoggingServiceへの参照をインポートする方法を確認できます。また、NLogに基づくロギングサービスのデフォルトの実装も提供しますが、たとえば、log4Netと簡単に交換できます。

参考のために:

SoapBox CoreはLGPLであるため、アプリケーションで(この部分を)使用できる可能性があります。

于 2010-09-22T16:43:11.550 に答える
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を使用したスタックトレースは非常に長く、ILoggerのリクエスターの正しい呼び出し元を明確に識別できません。

したがって、コンストラクタインジェクションを介してILoggerインターフェイスを注入する代わりに、コンストラクタインジェクションを介して注入され、ファクトリでCreateメソッドを呼び出すことができるILogFactoryインターフェイスを作成しました。

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

お役に立てれば

于 2013-07-18T09:03:36.300 に答える
1

新しいExportProviderを作成し、ICompositionElementに渡されるImportDefinitionをキャストする場合。ロガーが注入されているタイプを取得できます。

これが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から直接引き出します。

于 2013-12-31T19:33:30.880 に答える