web-dev-qa-db-ja.com

依存性注入と名前付きロガー

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

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

次のようなロギングおよび/または依存性注入に関連する他の投稿を見ました: dependency injection and logging interfaces

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

Log4Net Wrapperクラスはどのように見えますか?

再びlog4netとUnityについてIOC config

私の質問は、「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から始めましょう。ここでは、主にstackoverflowについて、ラップすることをお勧めするかどうかについて多くの記事を読みました。上記の「ベストプラクティス」リンクを参照し、log4netをラップするのが悪い理由についての1つのビューについて、 jeffrey hantin のコメントに移動してください。ラップした場合(および効果的にラップできる場合)、直接依存性の注入/除去を目的として厳密にラップしますか?または、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インスタンスを取得し、名前で(a?)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の知識がなくても、コンテナは名前付きロガーを直接解決できないようです)。

これはすでにかなり長くなっています。首尾一貫していることを望みます。ロギングプラットフォーム(どのようにSystem.Diagnosticsで構築されたlog4net、NLog、または何か(できればシン))をアプリケーションに依存させるかについての特定の情報を自由に共有してください。

「マネージャー」を挿入し、ロガーインスタンスを返しましたか?

ロガーインスタンスを直接インジェクトでき​​るようにする(つまり、依存関係をILogManagerではなくILoggerに設定する)ために、独自の構成セクションまたはDIプラットフォームの構成セクションに独自の構成情報を追加しましたか?.

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

これは明確な答えのある質問のようには見えないので、これをコミュニティWikiとしてマークしています。誰かがそうでないと感じたら、それを変更してください。

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

72
wageoghe

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

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

CastleとNInjectは両方ともロギングの実装を提供します。どちらもlog4netとNLogをサポートしています。 CastleはSystem.Diagnosticsもサポートしています。どちらの場合も、プラットフォームが特定のオブジェクトの依存関係を解決するとき(プラットフォームがMyClassを作成し、MyClassがILoggerに依存するとき)、依存関係(ILogger)の作成をILogger "provider"に委任します。共通用語)。 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について知りません。

13
wageoghe

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=コンテナーでも動作するはずです。

37

Common.Logging facadeまたは 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つは、あるロギングフレームワークまたは他のロギングフレームワークに固有の機能が失われることです。おそらく最も頻繁に使用されるのは、カスタムロギングレベルです。

16
quentin-starin

あなた自身の答えを見つけたと思います:)しかし、将来、特定のロギングフレームワークに縛られない方法についてこの質問がある人々のために、このライブラリ: Common.Logging はまさにそれを助けますシナリオ。

5
CubanX

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));
        }
    }
}
0
R.Titov