sh1’s diary

プログラミング、読んだ本、資格試験、ゲームとか私を記録するところ

C# NLog の基本的な使い方2(Getting Start)

この記事は、NLog(ログを管理する機能)の使い方(基本)について調べたことを記事にしています。

2022 年 12 月現在、NLog バージョン v5.1.0 について、以下は記述しています。

記事一覧

GettingStart

まず、基本となる Getting started for .NET framework の内容からはじめることにします。というのも、このリンクは github に飛ぶのですが、URL は以下のようになっていて、チュートリアルのトップです。これこそチュートリアルの第一歩で間違いない。

出力先を設定する NLog "targets" の概念

NLog はログの出力先を "target" という概念で管理していて、ひとつ以上の "target" を設定することができます。これは NLog の特徴のひとつ Extensible に紹介がありました。

さっそくコードを書いていきますが、最初に nlog.config ファイルをプロジェクトに追加します。(この xmlチュートリアルで内容が違います)C# コードで同じ内容を記述することもできますが、XML の設定ファイルを利用するほうが一般的みたいです。

<?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" />
        <target name="logconsole" xsi:type="Console" />
    </targets>

    <rules>
        <logger name="*" minlevel="Info" writeTo="logconsole" />
        <logger name="*" minlevel="Debug" writeTo="logfile" />
    </rules>
</nlog>
var config = new NLog.Config.LoggingConfiguration();

var logfile = new NLog.Targets.FileTarget("logfile") { FileName = "file.txt" };
var logconsole = new NLog.Targets.ConsoleTarget("logconsole");

config.AddRule(LogLevel.Info, LogLevel.Fatal, logconsole);
config.AddRule(LogLevel.Debug, LogLevel.Fatal, logfile);

NLog.LogManager.Configuration = config;

これに加えて、NLog 5.0 からは C# のコードの書き方に Fluent-Configuration というものが増えています。全部、コードを実行すると同じ設定を構築しているだけです。

NLog.LogManager.Setup().LoadConfiguration(builder => {
   builder.ForLogger().FilterMinLevel(LogLevel.Info).WriteToConsole();
   builder.ForLogger().FilterMinLevel(LogLevel.Debug).WriteToFile(fileName: "file.txt");
});

この設定(ファイル or コード)でプロジェクトに target を作成し、target に対して rule を設定しています。rule の中で logger を作成して、logger が target にリダイレクトする仕組みです。

補足:logger は出力に関するフィルター (minlevel) を設定できます。また、logger の名前は同じものでも設定できます。この意味を説明できるようになりましょう。

name」は後々重要です。後述の NLog.LogManager.GetCurrentClassLogger() を呼び出したときに取得できる logger を決定する仕組みが name です。(呼び出すクラスごとに取得する logger を自動的に使い分けることができる)

よくわからないときは、とりあえず logger の name は * にしておけば、GetCurrentClassLogger() を呼べば、名前が * の logger は必ず取得できるので安心。

  • * … 0~n 文字のマッチング
  • ? … 1 文字のマッチング

writeto="xxx" は target を指定しています。

<logger name="Name.Space.Class1" writeTo="target2" />
<logger name="Name.Space.*" writeTo="target1" />

ログを出力する

デバッグ時にログが出力される流れは設定の逆の流れをするイメージです。rule を通じて logger を取得して logger にログの書き込みを指示する。すると、logger に設定した書き込み先 (writeto) の target に対してテキストを流します。

using NLog;
using System;

namespace Nlog.Practice1;

public static class Program
{
    private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();

    public static void Main()
    {
        try
        {
           Logger.Info("Hello world");
           System.Console.ReadKey();
        }
        catch (Exception ex)
        {
           Logger.Error(ex, "Goodbye cruel world");
        }
    }
}  

たとえば、rules で Program クラス専用で呼び出す logger を作成するならこうする。

<rules>
    <logger name="Nlog.Practice1.Program" minlevel="Info" writeTo="logconsole" />
</rules>

サンプルの logger は Info メソッドを呼び出していますが、logger はいくつかのレベルのログを記録することができます。

これは、 logging-rules (minlevel を設定したところ) でも使用されている区分で、必要とされるログのメッセージだけが target にリダイレクトするような仕組みになっています。

ログの設定例は次のテーブルのとおり:

レベル 並番号 重要度 一般的な使用例
Trace 0 開発時に使用し、本番時は記録しないテキスト 例:メソッドの開始、終了、送受信データのペイロードなど
Debug 1 内部イベントやアプリケーションの挙動に関するテキスト 例:クエリの実行、ユーザー認証、セッション終了
Info 2 進捗状況やアプリケーションのライフタイムなどのハイライトする情報
Warn 3 バリデーションチェックや一時的な不具合、回復可能な警告
Error 4 失敗したエラー、例外を検出したエラー
Fatal 5 アプリケーションが停止するエラー

logger のポイントのひとつとして、logger はひとつ以上の target を呼び出すことができる仕組みなので、特定の target に縛られていません。rule の設定によって、複数の target に書き込んでいることもあります。

このメリットは、コード内での logging に関するコードを、書込先がひとつでも複数の場合でも同じにしておくことができます。これによって、設定ファイルを書き変えるだけでログ出力を制御できます。

ログの layout と layoutrenderer について

NLog は、ログメッセージを target に書き込む際のテキストのフォーマットをカスタマイズすることができます。

このログテキストのフォーマットのことを layout と呼んでいます。layout は target に対して、以下のように設定することができます。

<target name="logfile" xsi:type="File" 
            fileName="file.txt"
            layout="`${longdate}|${level:uppercase=true}|${logger}|${message:withexception=true}`" />

出力例:

$2022-12-26 16:04:28.1650|INFO|Nlog.Practice1.Program|test.

では、layoutrenderer とはなにかというと、layout で使用される特殊文字 (template macro) です。例えば、${message}${level} のようなものですね。

これは、NLog の layout renderer どんな語がありますか、というだけなので少しずつ調べよう。

たとえば、${threadid}GUI や async/await 関係のデバッグ時についていると嬉しい layout renderer ではないでしょうか。

高度な layout について (CSV, JSON など)

layout の概念はログテキストをどのように出力するか、ということなので、ログのフォーマットは CSVJSON, XML のような形式で出力することも可能です。

たとえば、CSV の場合は target に以下を追加します。

<!-- CSV の設定 -->
<target xsi:type="File" name="csvFileExample" fileName="./CsvLogExample.csv">
    <layout xsi:type="CsvLayout" delimiter="Tab" withHeader="false">
        <column name="time" layout="${longdate}" />
        <column name="level" layout="${level:upperCase=true}"/>
        <column name="message" layout="${message}" />
        <column name="callsite" layout="${callsite:includeSourcePath=true}" />
        <column name="stacktrace" layout="${stacktrace:topFrames=10}" />
        <column name="exception" layout="${exception:format=ToString}"/>
        <column name="property1" layout="${event-properties:property1}"/>
    </layout>
</target>

rules にこの target を追加します。

 <rules>
        <logger name="*" minlevel="Trace" writeTo="csvFileExample" />
    </rules>

target wrappers の概念

target wrapper(ラッパー)は、logger の動作を調整する機能です。たとえば、AsyncWrapper を使うと、target への書き込みが非同期になります。

拡張のような概念で、ちなみに、async は以下のオプションで簡単に非同期にすることができます。

    <targets async="true">
        <target ... />
    </targets>

これで、ファイルの書き込みを非同期にすることができるので、スレッドの応答性を向上させることができます。

ベストプラクティス

どのように NLog を利用するとよいのかプラクティスを示しています。

logger は各クラスで static 変数にする

LogManager.GetCurrentClassLogger を使う場合、logger は静的変数にしましょう。新しい logger を作成すると、lock の取得やオブジェクトの割り当てといったオーバーヘッドが発生します。

namespace MyNamespace
{
  public class MyClass
  {
    private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
  }
}

logger の文字列フォーマットで処理させる

文字列の割り当てや文字列の連結は NLog の書式設定の機能でやろう。これは、テキストのフォーマットを後の処理に遅延させることで本来のプログラムのパフォーマンスを低下させないためです。

logger.Info("Hello {0}", "Earth");

logger に例外を与えよう

例外を書式設定パラメーターとして指定することは避け、パラメーターとして指定します。

try
{
}
catch (Exception ex)
{
    logger.Error(ex, "Something bad happened");
}

設定ファイル nlog.config (xml) の検証

NLog はデフォルトですべての例外をカバーしてしまいます。そのため、logging に問題があっても、アプリケーションが壊れなくなります。そのため、nlog の初期設定に誤りがあると致命的なことになります。

  • throwConfigExceptions="true"

上記のプロパティを追加すると、NLog は設定に問題があったときに config の exception を投げるようになります。

<?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"
      throwConfigExceptions="true">
</nlog>
  • throwExceptions="true"

上記のプロパティは本番環境では設定してはいけません。これは NLog の例外処理を止めて、アプリケーションの例外処理になります。なので、属性値は、問題解決や開発時に有用なオプションです。

throwConfigExceptions 属性値は、構成の例外だけ処理を分離することができます。とくに、logging 構成の初期設定をするタイミング (production environment) に設定することを推奨します。 )

アプリケーションの終了と NLog の終了時処理 (Flush)

NLog は、デフォルトでアプリケーションのシャットダウンのタイミングで自動的に Flush をします。Microsoft Windows は、.NET アプリケーションの終了する前に(通常2秒)ありますが、マニュアルで停止したほうがよいです。

NLog.LogManager.Shutdown();

Mono/Linux で動作する .NET アプリケーションの場合は、シャットダウンの前に flush しておく必要があります。サボると、例外などの予測不能な動作を発生します。

サンプル

github にサンプルコードを公開しています。(基本的な使い方3で例示したプログラムの実行コードです)

参考