Prism(WPF)でNLogでもMicrosoft.Extensions.Logging.ILoggerにしたい


記事概要

WPF+PrismでNLogを使うんだけれども、各モジュール(クラス)ではMicrosoft.Extensions.Logging.ILoggerを使いたい、どうすればDIできる?というだけの、DI初心者、Prism初心者の記事です。

経緯

WPF+Prismでアプリを作れるようになりたいと思いサンプルプログラムを作り始めました。
ログはNLogで、と思ったのですが、NLogとlog4netの混在の中でソース共通化に困ったことがあることを思い出し、だったらMicrosoft.Extensions.Logging.ILoggerでいいや、となりました。どうせ器用な使い方をするわけでもなく、ログレベル(INFO/DEBUG/ERROR)がわかればそれで充分なことがほとんどです。

ログ出力を使いたい機能では、クラスのコンストラクタにILoggerを渡し、それを、PrismのDIに解決してもらうことにします。こういう使い方ですね。

// コンストラクタ引数にILoggerを指定し、それをPrismのDIに注入してもらう
public MainWindowViewModel(ILogger logger)
{
    logger.LogInformation("DI logger in MainWindowViewModel.cs");
}

もちろん、PrismのDIコンテナに対し、ILoggerの実装クラスを登録する必要があります。loggerなら実装クラスのインスタンスが1つあればよいので、

containerRegistry.RegisterInstance<Microsoft.Extensions.Logging.ILogger>(logger);

引数に渡すlogger、これはMicrosoft.Extensions.Logging.ILoggerを継承していなければいけません。・・・おや、どうやってNLogからMicrosoft.Extensionis.Logging.ILoggerを作ろうか?

NLogのGettingStartedでは、

  • .NETFramework → DIを使うことなくNLogをそのまま使う例
  • .ASP.NET Core → WebHostに対しUseNLog()している例
  • .NET Coreコンソール → MicrosoftのDIコンテナ(ServiceCollection)にAddNLog()している例

が記載されています。ASP.NET Coreの例ではDIコンテナ名は明示的にでてきませんが、ServiceCollectionが使われているはずです。いずれにしても、PrismのDIコンテナを使う場合はどうすればいいのかはピンときません。

UseNLog()やAddNLog()がNLog → (Microsoft)ILoggerをできているのだから、そういう機能はあるはず、という気持ちで.NET Coreコンソールの例を改めて見てみると、using NLog.Extensions.Logging; されているのに気が付きました。この中に、NLog → (Microsoft)ILoggerしてくれる機能がありそうです。

多くを求めなければ、以下のようなコードで使えることがわかりました。NLog → (Microsoft)ILoggerの変換がわかるよう、あえて using NLog.Extensions.Logging; しないでおきます。
※NuGetでNLog.Extensions.Loggingを取得
※NLog.configを作ってそれを「出力ディレクトリにコピー:新しい場合はコピー」も忘れずに!

App.xaml.cs
using LoggerSample.Views;
using Prism.Ioc;
using Prism.Modularity;
using System.Windows;
using Microsoft.Extensions.Logging;

namespace LoggerSample
{
    public partial class App
    {
        //略

        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
#if false
            // NLog.configの設定からプログラムコードで変更
            var config = new NLog.Config.LoggingConfiguration();
            var logfile = new NLog.Targets.FileTarget("logfile") { FileName = "log/file.txt" };
            config.AddRule(NLog.LogLevel.Debug, NLog.LogLevel.Fatal, logfile);
            NLog.LogManager.Configuration = config;
#endif

            var factory = new NLog.Extensions.Logging.NLogLoggerFactory();
            Microsoft.Extensions.Logging.ILogger logger = factory.CreateLogger("");

            containerRegistry.RegisterInstance<Microsoft.Extensions.Logging.ILogger>(logger);

            // DIコンテナから取り出せるよね?
            var log = Container.Resolve<Microsoft.Extensions.Logging.ILogger>();
            log.LogInformation("Test in RegsiterTypes()");
        }
    }
}
MainWindowViewModel.cs
using Prism.Mvvm;
using Microsoft.Extensions.Logging;

namespace LoggerSample.ViewModels
{
    public class MainWindowViewModel : BindableBase
    {
        //略

        public MainWindowViewModel(ILogger logger)
        {
            logger.LogInformation("DI logger in MainWindowViewModel.cs");
        }
    }
}

これだけだと 2020-08-06 05:34:00.5122|INFO||Test in RegsiterTypes() のようになり、ログ出力した場所(モジュール名)がわかりません。NLogのベタな使い方だと var logger = LogManager.GetCurrentClassLogger(); としているところが上記のコードには無いからです。これはNLogConfigの中でlayoutに${callsite}を入れることでしのぎます。自分の場合は ${callsite:includeNamespace=false} で十分です。

NLog.config
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

    <targets>
        <target name="logfile" xsi:type="File" fileName="file.txt" layout="${longdate}|${level}|${callsite}| ${message}"/>
        <target name="logconsole" xsi:type="Console" />
    </targets>

    <rules>
        <logger name="*" minlevel="Info" writeTo="logconsole" />
        <logger name="*" minlevel="Debug" writeTo="logfile" />
    </rules>
</nlog>
2020-08-06 05:48:48.0891|Info|LoggerSample.App.RegisterTypes| Test in RegsiterTypes()
2020-08-06 05:48:48.1984|Info|LoggerSample.ViewModels.MainWindowViewModel..ctor| DI logger in MainWindowViewModel.cs

以上、誰かの役に立ってくれれば幸いです。