77

人々が依存性注入プラットフォームを使用してログを注入する方法についてもっと知りたいと思っています。以下のリンクと私の例は log4net と Unity を参照していますが、必ずしもこれらのいずれかを使用するわけではありません。依存性注入/IOC については、プロジェクトの残りの部分 (大規模) が落ち着いている標準であるため、おそらく MEF を使用します。

私は依存性注入/ioc に非常に慣れておらず、C# と .NET にもかなり慣れていません (過去 10 年ほど VC6 と VB6 を使用した後、C#/.NET でプロダクション コードをほとんど作成していません)。さまざまなロギング ソリューションについて多くの調査を行ってきたので、それらの機能セットについては十分に理解できていると思います。私は、1 つの依存関係を注入する (または、より「正しく」、1 つの依存関係の抽象化されたバージョンを注入する) 実際のメカニズムに十分に精通していません。

ロギングおよび/または依存性注入に関連する他の投稿を見たことがあります: 依存性注入およびロギング インターフェイス

ロギングのベスト プラクティス

Log4Net ラッパー クラスはどのようになりますか?

再びlog4netとUnity IOC構成について

私の質問は、「ioc ツール yyy を使用してロギング プラットフォーム xxx を挿入するにはどうすればよいですか?」とは特に関係ありません。むしろ、ロギング プラットフォームのラッピング (常に推奨されているわけではありませんが、よくあることです) と構成 (つまり app.config) を人々がどのように処理しているかに興味があります。たとえば、log4net を例として使用すると、(app.config で) 多数のロガーを構成し、次のようなコードを使用する標準的な方法でそれらのロガーを (依存性注入なしで) 取得できます。

private static readonly ILog logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

別の方法として、ロガーの名前がクラスではなく機能領域である場合は、次のようにすることができます。

private static readonly ILog logger = LogManager.GetLogger("Login");
private static readonly ILog logger = LogManager.GetLogger("Query");
private static readonly ILog logger = LogManager.GetLogger("Report");

したがって、私の「要件」は次のようなものになると思います。

  1. 製品のソースをロギング プラットフォームに直接依存しないようにしたいと考えています。

  2. 特定の名前付きロガー インスタンス (おそらく、同じ名前付きインスタンスのすべてのリクエスター間で同じインスタンスを共有する) を、何らかの依存性注入 (おそらく MEF) によって直接的または間接的に解決できるようにしたいと考えています。

  3. これを難しい要件と呼ぶかどうかはわかりませんが、必要に応じて名前付きロガー (クラス ロガーとは異なる) を取得できるようにしたいと考えています。たとえば、クラス名に基づいてクラスのロガーを作成する場合がありますが、1 つのメソッドには、個別に制御したい特に重い診断が必要です。言い換えれば、1 つのクラスを 2 つの個別のロガー インスタンスに「依存」させたいと思うかもしれません。

番号 1 から始めましょう。ラップするのが良いかどうかについて、主にここでスタックオーバーフローに関する多くの記事を読みました。上記の「ベスト プラクティス」リンクを参照し、jeffrey hantinのコメントにアクセスして、log4net をラップすることがなぜ悪いのかについての 1 つのビューを参照してください。ラップした場合 (そして効果的にラップできた場合) は、直接依存性の注入/除去の目的で厳密にラップしますか? それとも、log4net app.config 情報の一部またはすべてを抽象化しようとしますか?

System.Diagnostics を使用したいとしましょう。おそらく、TraceSource に基づいて、インターフェイス ベースのロガーを実装したいと思います (「共通の」ILogger/ILog インターフェイスを使用することもあるでしょう)。インターフェイスを実装し、たとえば TraceSource を使用して、System.Diagnostics の app.config 情報をそのまま使用しますか?

このようなもの:

public class MyLogger : ILogger
{
  private TraceSource ts;
  public MyLogger(string name)
  {
    ts = new TraceSource(name);
  }

  public void ILogger.Log(string msg)
  {
    ts.TraceEvent(msg);
  }
}

そして、次のように使用します。

private static readonly ILogger logger = new MyLogger("stackoverflow");
logger.Info("Hello world!")

番号 2 に移ります ... 特定の名前付きロガー インスタンスを解決するにはどうすればよいですか? 選択したロギング プラットフォームの app.config 情報をそのまま利用する必要がありますか (つまり、app.config の命名スキームに基づいてロガーを解決します)? では、log4net の場合、LogManager を「注入」する方がよいでしょうか (これは静的オブジェクトであるため、これは不可能であることに注意してください)。LogManager (これを MyLogManager と呼びます) をラップし、それに ILogManager インターフェイスを与えてから、MyLogManager.ILogManager インターフェイスを解決することができます。私の他のオブジェクトは、ILogManager (実装されているアセンブリからのエクスポート) に依存関係 (MEF 用語でのインポート) を持つことができます。これで、次のようなオブジェクトを持つことができます。

public class MyClass
{
  private ILogger logger;
  public MyClass([Import(typeof(ILogManager))] logManager)
  {
    logger = logManager.GetLogger("MyClass");
  }
}

ILogManager が呼び出されるたびに、log4net の LogManager に直接委任されます。または、ラップされた LogManager は、app.config に基づいて取得した ILogger インスタンスを取得し、それらを (?) MEF コンテナーに名前で追加できます。その後、同じ名前のロガーが要求されると、ラップされた LogManager に対してその名前が照会されます。ILogger が存在する場合は、その方法で解決されます。これが MEF で可能である場合、そうするメリットはありますか?

この場合、実際には ILogManager だけが「注入」され、log4net が通常行う方法で ILogger インスタンスを配布できます。このタイプの (本質的にはファクトリの) インジェクションは、名前付きロガー インスタンスのインジェクションと比べてどうですか? これにより、log4net (または他のロギング プラットフォーム) の app.config ファイルをより簡単に活用できます。

次のように、MEF コンテナーから名前付きインスタンスを取得できることを知っています。

var container = new CompositionContainer(<catalogs and other stuff>);
ILogger logger = container.GetExportedValue<ILogger>("ThisLogger");

しかし、名前付きインスタンスをコンテナーに入れるにはどうすればよいでしょうか? ILogger のさまざまな実装を持つことができる属性ベースのモデルについては知っていますが、それぞれに (MEF 属性を介して) 名前が付けられていますが、それは実際には役に立ちません。ロガー (すべて同じ実装) を名前でリストし、MEF が読み取ることができる app.config (またはその中のセクション) のようなものを作成する方法はありますか? 基礎となるapp.configを介して名前付きロガーを解決し、解決されたロガーをMEFコンテナーに挿入する中央の「マネージャー」(MyLogManagerなど)が存在する可能性がありますか? このようにして、同じ MEF コンテナーにアクセスできる他の誰かが利用できるようになります (ただし、log4net の app.config 情報の使用方法に関する MyLogManager の知識がなくても、

これはすでにかなり長くなりました。首尾一貫していることを願っています。ロギング プラットフォーム (log4net、NLog、または System.Diagnostics 上に構築されたもの (できれば薄い) を検討している可能性が最も高い) をアプリケーションにどのように依存関係に注入したかについて、具体的な情報を自由に共有してください。

「マネージャー」を注入して、ロガーインスタンスを返すようにしましたか?

独自の構成セクションまたは DI プラットフォームの構成セクションに独自の構成情報の一部を追加して、ロガー インスタンスを直接注入することを容易/可能にしましたか (つまり、依存関係を ILogManager ではなく ILogger にします)。

ILogManager インターフェイスまたは名前付きの ILogger インスタンスのセットを含む静的またはグローバル コンテナーを使用する場合はどうでしょうか。そのため、従来の意味で (コンストラクター、プロパティ、またはメンバー データを介して) 注入するのではなく、ログの依存関係はオンデマンドで明示的に解決されます。これは、依存性を注入するための良い方法ですか、それとも悪い方法ですか。

明確な答えがある質問のようには見えないため、これをコミュニティ wiki としてマークします。違うと感じた人は遠慮なく変更してください。

助けてくれてありがとう!

4

5 に答える 5

39

Ninject を使用して、ロガー インスタンスの現在のクラス名を次のように解決しています。

kernel.Bind<ILogger>().To<NLogLogger>()
  .WithConstructorArgument("currentClassName", x => x.Request.ParentContext.Request.Service.FullName);

NLog 実装のコンストラクターは次のようになります。

public NLogLogger(string currentClassName)
{
  _logger = LogManager.GetLogger(currentClassName);
}

このアプローチは、他の IOC コンテナーでも機能するはずです。

于 2011-06-26T22:19:13.100 に答える
16

Common.LoggingファサードまたはSimple Logging Facadeを使用することもできます。

どちらもサービス ロケーター スタイル パターンを使用して ILogger を取得します。

率直に言って、ロギングは、自動的に注入する価値がほとんどまたはまったくないと思われる依存関係の 1 つです。

ロギング サービスを必要とする私のクラスのほとんどは、次のようになります。

public class MyClassThatLogs {
    private readonly ILogger log = Slf.LoggerService.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.FullName);

}

Simple Logging Facade を利用して、プロジェクトを log4net から NLog に切り替え、NLog を使用したアプリケーションのログ記録に加えて、log4net を使用したサード パーティ ライブラリからのログ記録を追加しました。つまり、ファサードは私たちによく役立っています。

回避するのが難しい警告の 1 つは、特定のログ フレームワークに固有の機能が失われることです。おそらく、その最も一般的な例は、カスタム ログ レベルです。

于 2010-08-19T20:07:25.777 に答える
13

これは、注入するロガーに log4net や NLog などのロギング プラットフォームが提供されている場合に、ロガーの依存関係を注入する方法を理解しようとしている人のためのものです。私の問題は、特定の ILogger の解決が ILogger に依存するクラスの型を知ることに依存することを知っていたときに、クラス (MyClass など) を ILogger 型のインターフェイスに依存させる方法を理解できなかったことです (例: MyClass)。DI/IoC プラットフォーム/コンテナーはどのようにして適切な ILogger を取得しますか?

さて、私は Castle と NInject のソースを見て、それらがどのように機能するかを見てきました。AutoFac と StructureMap も調べました。

Castle と NInject はどちらもロギングの実装を提供します。どちらも log4net と NLog をサポートしています。Castle は System.Diagnostics もサポートしています。どちらの場合も、プラットフォームが特定のオブジェクトの依存関係を解決するとき (たとえば、プラットフォームが MyClass を作成していて、MyClass が ILogger に依存している場合)、依存関係 (ILogger) の作成を ILogger の「プロバイダー」に委任します (リゾルバーはより多くの場合があります)。通称)。次に、ILogger プロバイダーの実装は、ILogger のインスタンスを実際にインスタンス化し、それを元に戻して、依存クラス (MyClass など) に注入する役割を果たします。どちらの場合も、プロバイダー/リゾルバーは依存クラス (MyClass など) のタイプを認識しています。そのため、MyClass が作成され、その依存関係が解決されると、ILogger の "リゾルバー" はそのクラスが MyClass であることを認識します。Castle または NInject が提供するログ ソリューションを使用する場合、ログ ソリューション (log4net または NLog のラッパーとして実装) が型 (MyClass) を取得するため、log4net.LogManager.GetLogger() またはNLog.LogManager.GetLogger()。(log4net と NLog の構文は 100% 確実ではありませんが、理解できます)。

AutoFac と StructureMap はログ機能を提供しませんが (少なくとも私が見ただけではわかりません)、カスタム リゾルバーを実装する機能を提供しているようです。したがって、独自のログ抽象化レイヤーを作成する場合は、対応するカスタム リゾルバーを作成することもできます。そうすれば、コンテナーが ILogger を解決したい場合、リゾルバーを使用して正しい ILogger を取得し、現在のコンテキスト (つまり、どのオブジェクトの依存関係が現在満たされているか - どのオブジェクトが ILogger に依存しているか) にアクセスできます。オブジェクトの型を取得すると、ILogger の作成を現在構成されているロギング プラットフォーム (おそらくインターフェイスの背後で抽象化し、リゾルバーを作成した) に委任する準備が整います。

したがって、必要だと思っていたが、以前は完全に把握できていなかったいくつかの重要なポイントは次のとおりです。

  1. 最終的に、DI コンテナーは、使用するロギング プラットフォームを何らかの方法で認識する必要があります。通常、これは、「ILogger」がロギング プラットフォームに固有の「リゾルバー」によって解決されるように指定することによって行われます (したがって、Castle には log4net、NLog、および System.Diagnostics の「リゾルバー」(とりわけ) があります)。使用するリゾルバーの指定は、構成ファイルまたはプログラムで行うことができます。

  2. リゾルバーは、依存関係 (ILogger) が解決されるコンテキストを知る必要があります。つまり、MyClass が作成され、それが ILogger に依存している場合、リゾルバーが正しい ILogger を作成しようとするとき、リゾルバー (リゾルバー) は現在の型 (MyClass) を認識している必要があります。そうすれば、リゾルバーは基礎となるロギングの実装 (log4net、NLog など) を使用して正しいロガーを取得できます。

これらのポイントは、DI/IoC ユーザーには明らかかもしれませんが、私はちょうど今始めたばかりなので、理解するのに時間がかかりました。

私がまだ理解していないことの 1 つは、MEF でこのようなことが可能かどうか、またはその方法です。オブジェクトをインターフェイスに依存させ、MEF がオブジェクトを作成した後、インターフェイス/依存関係が解決されている間にコードを実行することはできますか? したがって、次のようなクラスがあるとします。

public class MyClass
{
  [Import(ILogger)]
  public ILogger logger;

  public MyClass()
  {
  }

  public void DoSomething()
  {
    logger.Info("Hello World!");
  }
}

MEF が MyClass のインポートを解決しているときに、自分のコードの一部を (属性を介して、ILogger の実装の追加のインターフェイスを介して、どこかで) 実行し、ILogger のインポートを解決することができますか? MyClass は現在コンテキストにあり、YourClass で取得されるものとは (潜在的に) 異なる ILogger インスタンスを返しますか? ある種の MEF プロバイダーを実装する必要がありますか?

この時点で、私はまだ MEF について知りません。

于 2010-08-19T19:25:16.887 に答える
5

私はあなたがあなた自身の答えを考え出したように見えます:)しかし、将来、特定のロギングフレームワークに自分自身を結びつけない方法についてこの質問をする人々のために、このライブラリ: Common.Loggingはまさにそのシナリオに役立ちます.

于 2010-08-19T19:38:37.810 に答える
0

MEFによる依存性注入のためにLog4Netロガーを登録するプロバイダーによって、カスタムServiceExportProviderを作成しました。その結果、さまざまな種類のインジェクションにロガーを使用できます。

注射の例:

[Export]
public class Part
{
    [ImportingConstructor]
    public Part(ILog log)
    {
        Log = log;
    }

    public ILog Log { get; }
}

[Export(typeof(AnotherPart))]
public class AnotherPart
{
    [Import]
    public ILog Log { get; set; }
}

使用例:

class Program
{
    static CompositionContainer CreateContainer()
    {
        var logFactoryProvider = new ServiceExportProvider<ILog>(LogManager.GetLogger);
        var catalog = new AssemblyCatalog(typeof(Program).Assembly);
        return new CompositionContainer(catalog, logFactoryProvider);
    }

    static void Main(string[] args)
    {
        log4net.Config.XmlConfigurator.Configure();
        var container = CreateContainer();
        var part = container.GetExport<Part>().Value;
        part.Log.Info("Hello, world! - 1");
        var anotherPart = container.GetExport<AnotherPart>().Value;
        anotherPart.Log.Fatal("Hello, world! - 2");
    }
}

コンソールの結果:

2016-11-21 13:55:16,152 INFO  Log4Mef.Part - Hello, world! - 1
2016-11-21 13:55:16,572 FATAL Log4Mef.AnotherPart - Hello, world! - 2

ServiceExportProvider の実装:

public class ServiceExportProvider<TContract> : ExportProvider
{
    private readonly Func<string, TContract> _factoryMethod;

    public ServiceExportProvider(Func<string, TContract> factoryMethod)
    {
        _factoryMethod = factoryMethod;
    }

    protected override IEnumerable<Export> GetExportsCore(ImportDefinition definition, AtomicComposition atomicComposition)
    {
        var cb = definition as ContractBasedImportDefinition;
        if (cb?.RequiredTypeIdentity == typeof(TContract).FullName)
        {
            var ce = definition as ICompositionElement;
            var displayName = ce?.Origin?.DisplayName;
            yield return new Export(definition.ContractName, () => _factoryMethod(displayName));
        }
    }
}
于 2016-11-21T10:58:37.020 に答える