はじめに
本記事では.NET や.Netframework で使用できるロギングライブラリのNLogの使い方をまとめていきます。
環境
- OS: Windows11 22H2(22621.1105)
- エディタ: Visual Studio 2022
- フレームワーク: .NET6
NLog をインストール
まずは、Nuget から NLog をインストールします。複数のプロジェクトにインストールしたい場合は、プロジェクトではなくソリューションを右クリックしてパッケージをインストールすると、複数のプロジェクトが選択できます。
次にアプリケーション構成ファイルを作成します。このファイルに NLog に関する設定を記述していきます。
構成ファイル作成後は、ビルド時に構成ファイルがコピーされるように設定します。この設定をしないと、アプリケーションを実行してもログが出力されないので注意が必要です。構成ファイルを右クリック → プロパティを表示して設定します。
設定ファイルを記述する
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"
autoReload="true"
throwConfigExceptions="true"
internalLogLevel="Off"
internalLogFile="./logs/nlog_internal.log">
<targets>
<!--Debug, Info 専用のファイルを出力する設定-->
<target name="logFile"
xsi:type="File"
encoding="UTF-8"
lineEnding="Default"
layout="${longdate} - ${level:uppercase=true} - [スレッドID: ${threadid}] [ロガー: ${logger}] - ${message}"
fileName="${basedir}/logs/log/Log-${shortdate}.log"
archiveFileName="${basedir}/logs/backup/log/Log-${shortdate}.{#}.log"
archiveEvery="Day"
archiveNumbering="Rolling"
archiveAboveSize="100000"
maxArchiveFiles="10"/>
<!--エラーログ専用のファイルを出力する設定-->
<target name="errorLogfile"
xsi:type="File"
encoding="UTF-8"
lineEnding="Default"
layout="${longdate} - ${level:uppercase=true} - [スレッドID: ${threadid}] [ロガー: ${logger}] - ${message} ${exception:format=tostring}"
fileName="${basedir}/logs/error/Error-${shortdate}.log"
archiveFileName="${basedir}/logs/backup/error/Error-${shortdate}.{#}.log"
archiveEvery="Day"
archiveNumbering="Rolling"
archiveAboveSize="100000"
maxArchiveFiles="10"/>
<!-- Visual Studioの出力ウィンドウに表示させる -->
<target name="logConsole"
xsi:type="Debugger"
layout="${longdate} - ${level:uppercase=true:padding=-5} - ${message}" />
</targets>
<rules>
<logger name="*" maxlevel="Info" writeTo="logFile" />
<logger name="*" minlevel="Error" writeTo="errorLogfile" />
<logger name="*" minlevel="Trace" writeTo="logConsole" />
</rules>
</nlog>
XML 名前空間と、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"
autoReload="true"
throwConfigExceptions="true"
internalLogLevel="Off"
internalLogFile="${basedir}/logs/nlog_internal.log">
<!-- ~中略~ -->
xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
という記述は、XML 名前空間を定義しています。プレフィクスを指定しないで URI を宣言すると、その URI の宣言のある要素以下でデフォルトとなり、接頭辞なしのタグ名はこのデフォルト名前空間に属することになります。
NLog.xsd
というのは、XML スキーマを構成するファイルです。XML スキーマとは、XML 文書が準拠すべき構造やルールを定義するファイルのことです。XML スキーマは、XML 文書の構造やデータ型、要素の出現順序、属性、値の制約などを定義しするために使用されます。
つまり、NLog の設定ファイルで使用される要素や属性はhttp://www.nlog-project.org/schemas/NLog.xsd
で定義されたスキーマに基づいているため、この名前空間が指定されていない場合、XML パーサは正しく解釈することができません。
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
は名前空間にプレフィクスを指定して読み込んでいます。xsi
とは XML Schema Instance の略称で、XML 文書で使用される XML 名前空間の 1 つです。XML スキーマに関するルールや情報を提供するために使用されます。
具体的には XML 文書内の要素や属性が XML スキーマに従っていることを確認するために使用されるようです。ここで言う要素とは、<nlog></nlog>
などのタグを示し、属性はautoReload="true"
などを示します。
指定されているスキーマはhttp://www.w3.org/2001/XMLSchema-instance
になります。
オプション | 意味 |
---|---|
xsi:type | 要素や属性のデータ型を指定するた めに使用される |
xsi:nil | 要素の値が存在しないことを示すために使用される |
xsi:schemaLocation | XML 文書で使用される XML Schema ファイルの場所を指定するために使用される |
xsi:noNamespaceSchemaLocation | XML 文書で使用される XML Schema ファイルの場所を指定するために使用されるが、名前空間がない場合に使用 |
その他の設定内容は以下の表にまとめました。
オプション | 意味 |
---|---|
autoReload="true" | NLog の構成ファイルを監視し、変更があった場合に自動で読み込む設定。アプリケーションを再起動することなく NLog の変更を適応することができる |
throwConfigExceptions="true" | 設定に問題があった時にエラーを発生することができる。 ※ throwExceptions="true" という設定は本番環境では使用しない。アプリケーションに致命的な問題を与える可能性があり、これは単体テストとローカルのトラブルシューティングを目的としている。 |
internalLogLevel=Off | NLog ログの動作に関するデバッグを有効化するかの設定。 |
internalLogFile | 指定したファイルに Internal Log に関するメッセージを書き込む。${basedir} は実行ファイル(exe ファイル)のフォルダパスを示す。 |
internalLogLevel は"Off|Trace|Debug|Info|Warn|Error|Fatal"
のレベルを指定することが出来ます。設定がきちんと反映されている確認したいときにはTrace
やDebug
を設定して見ると良いかもしれません。
2023-03-10 17:01:54.0177 Info Message Template Auto Format enabled
2023-03-10 17:01:54.0794 Debug ScanAssembly('NLog, Version=5.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c')
2023-03-10 17:01:54.3397 Debug Setting 'NLog.Targets.FileTarget.name' to 'logFile'
2023-03-10 17:01:54.3552 Debug Setting 'NLog.Targets.FileTarget.encoding' to 'UTF-8'
2023-03-10 17:01:54.3552 Debug Setting 'NLog.Targets.FileTarget.lineEnding' to 'Default'
2023-03-10 17:01:54.3552 Debug Setting 'NLog.Targets.FileTarget.layout' to '${longdate} - ${level:uppercase=true} - [スレッドID: ${threadid}] [ロガー: ${logger}] - ${message}'
2023-03-10 17:01:54.3858 Trace FindReachableObject<NLog.Internal.IRenderable>:
2023-03-10 17:01:54.3858 Trace Scanning LongDateLayoutRenderer 'Layout Renderer: ${longdate}'
2023-03-10 17:01:54.4094 Trace FindReachableObject<NLog.Internal.IRenderable>:
2023-03-10 17:01:54.4094 Trace Scanning LevelLayoutRenderer 'Layout Renderer: ${level}'
2023-03-10 17:01:54.4094 Trace FindReachableObject<NLog.Internal.IRenderable>:
2023-03-10 17:01:54.4147 Trace Scanning ThreadIdLayoutRenderer 'Layout Renderer: ${threadid}'
2023-03-10 17:01:54.4147 Trace FindReachableObject<NLog.Internal.IRenderable>:
2023-03-10 17:01:54.4147 Trace Scanning LoggerNameLayoutRenderer 'Layout Renderer: ${loggername}'
2023-03-10 17:01:54.4147 Trace FindReachableObject<NLog.Internal.IRenderable>:
2023-03-10 17:01:54.4147 Trace Scanning MessageLayoutRenderer 'Layout Renderer: ${message}'
2023-03-10 17:01:54.4147 Debug Setting 'NLog.Targets.FileTarget.fileName' to '${basedir}/logs/log/Log-${shortdate}.log'
2023-03-10 17:01:54.4147 Trace FindReachableObject<NLog.Internal.IRenderable>:
2023-03-10 17:01:54.4147 Trace Scanning BaseDirLayoutRenderer 'Layout Renderer: ${basedir}'
2023-03-10 17:01:54.4329 Trace FindReachableObject<NLog.Internal.IRenderable>:
2023-03-10 17:01:54.4329 Trace Scanning ShortDateLayoutRenderer 'Layout Renderer: ${shortdate}'
2023-03-10 17:01:54.4482 Debug Setting 'NLog.Targets.FileTarget.archiveFileName' to '${basedir}/logs/backup/log/Log-${shortdate}.{#}.log'
2023-03-10 17:01:54.4482 Trace FindReachableObject<NLog.Internal.IRenderable>:
2023-03-10 17:01:54.4482 Trace Scanning BaseDirLayoutRenderer 'Layout Renderer: ${basedir}'
2023-03-10 17:01:54.4482 Trace FindReachableObject<NLog.Internal.IRenderable>:
2023-03-10 17:01:54.4482 Trace Scanning ShortDateLayoutRenderer 'Layout Renderer: ${shortdate}'
2023-03-10 17:01:54.4482 Debug Setting 'NLog.Targets.FileTarget.archiveEvery' to 'Day'
2023-03-10 17:01:54.4618 Debug Setting 'NLog.Targets.FileTarget.archiveNumbering' to 'Rolling'
2023-03-10 17:01:54.4618 Debug Setting 'NLog.Targets.FileTarget.archiveAboveSize' to '100000'
2023-03-10 17:01:54.4822 Debug Setting 'NLog.Targets.FileTarget.maxArchiveFiles' to '10'
2023-03-10 17:01:54.4822 Debug Adding target FileTarget(Name=logFile)
次に、ログを出力させる部分の設定を見ていきます。今回の設定では以下の 3 つのログ出力のためのターゲットを指定しています。
- Debug, Info 専用のファイルを出力する
- エラーログ専用のファイルを出力する設定
- Visual Studio の出力ウィンドウに表示させる
<!-- ~中略~ -->
<!--出力するターゲットを指定する(複数指定可)-->
<targets>
<!--Debug, Info 専用のファイルを出力する設定-->
<target name="logFile"
xsi:type="File"
encoding="UTF-8"
lineEnding="Default"
layout="${longdate} - ${level:uppercase=true} - [スレッドID: ${threadid}] [ロガー: ${logger}] - ${message}"
fileName="${basedir}/logs/log/Log-${shortdate}.log"
archiveFileName="${basedir}/logs/backup/log/Log-${shortdate}.{#}.log"
archiveEvery="Day"
archiveNumbering="Rolling"
archiveAboveSize="100000"
maxArchiveFiles="10"/>
<!--エラーログ専用のファイルを出力する設定-->
<target name="errorLogfile"
xsi:type="File"
encoding="UTF-8"
lineEnding="Default"
layout="${longdate} - ${level:uppercase=true} - [スレッドID: ${threadid}] [ロガー: ${logger}] - ${message} ${exception:format=tostring}"
fileName="${basedir}/logs/error/Error-${shortdate}.log"
archiveFileName="${basedir}/logs/backup/error/Error-${shortdate}.{#}.log"
archiveEvery="Day"
archiveNumbering="Rolling"
archiveAboveSize="100000"
maxArchiveFiles="10"/>
<!-- Visual Studioの出力ウィンドウに表示させる設定 -->
<target name="logConsole"
xsi:type="Debugger"
layout="${longdate} - ${level:uppercase=true:padding=-5} - ${message} ${exception:format=tostring}" />
</targets>
target で指定するパラメータ
オプション | 意味 |
---|---|
name | ターゲットの名前 |
xsi:type | ターゲットをどの種類のタイプで書き出すか指定する(File → 通常のテキストファイル) ※JSON や CSV などのレイアウトでも出力できる |
layout | 出力するログのレイアウトを指定する(必須項目)。その他の出力レイアウトのフォーマットとして、JsonLayout、XmlLayout、CsvLayout などがある |
fileName | 書き込むファイルの名前を指定。${shortdate} を指定することで動的に日付を指定できる(フォーマットはyyyy-MM-dd ) |
archive に関するパラメータ
アーカイブに関するパラメータを指定することで、指定したファイルサイズなど任意の条件を満たしたら、自動的にログファイルがアーカイブされる用にすることが出来ます。
オプション | 意味 |
---|---|
archiveFileName | アーカイブに使用されるファイルの名前。{###} は一連の数字に置き換えられる(例:000, 001 など。{#} とすると、0, 1 と一桁だけ出力される) |
archiveEvery | 指定された時間が経過するごとに、ログファイルを自動的にアーカイブするかどうかを示す。 今回は Day を指定しているので、指定したファイルサイズを超過するか、日付が変わった段階でアーカイブされる。 |
archiveNumbering | アーカイブされたログファイルのナンバリングフォーマットを指定する。 |
archiveAboveSize | アーカイブとして残すログファイルの最大個数 |
maxArchiveFiles | ログのサイズを指定してアーカイブする。単位はバイト ※例: 1000000/1MB, 100000/100KB, 10000/10KB, 5000/5KB |
今回の設定ではarchiveAboveSize="100000"
と指定しているので、ログのサイズが 100KB に到達したら現在のファイルはアーカイブされることになります。
出力するログレベルを指定する
rules
を指定することで、出力ターゲットに 関連するレベルのイベントのみを出力させるようにフィルタリングします。
<!--targetで出力するログレベルを指定する-->
<rules>
<logger name="*" maxlevel="Info" writeTo="logFile" />
<logger name="*" minlevel="Error" writeTo="errorLogfile" />
<logger name="*" minlevel="Trace" writeTo="logConsole" />
</rules>
オプション | 意味 |
---|---|
name | ロガー名を指定する。name="*" と指定すると、ロガー名とは無関係にすべてのロガーオブジェクトにマッチする。 |
maxlevel | ログに記録する最大レベル |
minlevel | ログに記録する最小レベル |
writeTo | ルールを適応 するファイル名を指定する。target タグのname 属性で指定した名前を指定する。 |
https://github.com/NLog/NLog/wiki/Configuration-file#rules
ログを出力する処理を記述する
設定ファイルを作成しましたので、実際にアプリケーションからログを出力してみます。今回は以下のような簡単な WPF アプリケーションを使用します。
MainWindow.xaml
のレイアウトのコードは割愛します。コードビハインドに以下のコードを記述します。
using NLog;
using System;
using System.Threading.Tasks;
using System.Windows;
namespace NLogTutorial
{
public partial class MainWindow : Window
{
/// <summary>
/// Loggerは各クラスでstatic変数で使用すること
/// 新しいLoggerを作成するとオーバーヘッドが発生するので推奨されていない
/// </summary>
private static Logger _logger = LogManager.GetCurrentClassLogger();
/// <summary>
/// ループ処理を停止するフラグ
/// </summary>
private bool _isLogStop = false;
/// <summary>
/// コンストラクタ
/// </summary>
public MainWindow()
{
InitializeComponent();
}
/// <summary>
/// ログ出力のループ処理を開始する
/// </summary>
/// <param name="sender">コントロールオブジェクト</param>
/// <param name="e">イベント引数</param>
private async void LogOutput(object sender, RoutedEventArgs e)
{
await Task.Run(async () =>
{
_isLogStop = true;
while (_isLogStop)
{
// 以下はinfoフォルダに出力される
_logger.Trace("Traceを一回出力します");
_logger.Debug("Debugを一回出力します");
_logger.Info("Infoを一回出力します");
// 以下はerrorフォルダに出力される
_logger.Warn("Warnを一回出力します");
_logger.Error("Errorを一回出力します");
_logger.Fatal("Fatalを一回出力します");
await Task.Delay(100);
}
});
}
/// <summary>
/// 意図的に例外を発生させる
/// </summary>
/// <param name="sender">コントロールオブジェク ト</param>
/// <param name="e">イベント引数</param>
private void LogExceptionOutput(object sender, RoutedEventArgs e)
{
try
{
var num = 0;
// 0による除算で例外を発生させる
var sum = 1 / num;
}
catch (Exception ex)
{
// 以下はerrorフォルダに出力される
_logger.Warn("Warnを一回出力します: {0}", ex.Message);
_logger.Error("Errorを一回出力します: {0}", ex.Message);
_logger.Fatal("Fatalを一回出力します: {0}", ex.Message);
MessageBox.Show("エラーが発生しました。");
}
}
/// <summary>
/// ループ処理を停止
/// </summary>
/// <param name="sender">コントロールオブジェクト</param>
/// <param name="e">イベント引数</param>
private void LogRotationStop(object sender, RoutedEventArgs e)
{
_isLogStop = false;
MessageBox.Show("ループ処理を停止しました。");
}
}
}
アプリケーションを起動して「ログ出力ループ」ボタンをクリックしてログをします。NLog.config
で指定したログの出力先を開きます。
100KB 付近になるとログファイルが新しくなっていることがわかります。アーカイブされたファイルも確認します。
Log-${shortdate}.{#}.log
で指定したフォーマットでログがアーカイブされていることがわかります。
しかし、上記の設定ではサイズが指定のサイズに到達した場合にのみ、ナンバリングされたログのアーカイブファイルが作成されます。
日付が変わると毎日新しいファイルが作成されるので、ある程度溜まってくると手動で削除する必要があります。
ところてん式にログをアーカイブする場合
archiveNumbering="Rolling"
はそのままですが、ファイル名に日付を含めないようにします。
また、サンプルで動かすには 10 個は多いので 5 個に変更しています(maxArchiveFiles
)
<!-- ~中略~ -->
<!--出力するターゲットを指定する(複数指定可)-->
<targets>
<!--Debug, Info 専用のファイルを出力する設定-->
<target name="logFile"
xsi:type="File"
encoding="UTF-8"
lineEnding="Default"
layout="${longdate} - ${level:uppercase=true} - [スレッドID: ${threadid}] [ロガー: ${logger}] - ${message}"
- fileName="${basedir}/logs/log/Log-${shortdate}.log"
+ fileName="${basedir}/logs/archive_rolling.log"
- archiveFileName="${basedir}/logs/backup/log/Log-${shortdate}.{#}.log"
+ archiveFileName="${basedir}/logs/backup/archive_rolling.{#}.log"
archiveEvery="Day"
archiveNumbering="Rolling"
archiveAboveSize="100000"
- maxArchiveFiles="10"
+ maxArchiveFiles="5"
/>
<!-- Visual Studioの出力ウィンドウに表示させる設定 -->
<target name="logConsole"
xsi:type="Debugger"
layout="${longdate} - ${level:uppercase=true:padding=-5} - ${message} ${exception:format=tostring}" />
</targets>
<!--targetで出力するログレベルを指定する-->
<rules>
<logger name="*" maxlevel="Error" writeTo="logFile" />
<logger name="*" minlevel="Trace" writeTo="logConsole" />
</rules>
</nlog>
アーカイブされるファイルの個数が最大 5 個でローテーションされるか確認します。
実装直後はarchive_rolling.log
というファイルが作成され、以下のログが出力されています。
2024-05-09 11:37:50.2200 - TRACE - [スレッドID: 9] [ロガー: NLogTutorial.MainWindow] - Traceを一回出力します
2024-05-09 11:37:50.2637 - DEBUG - [スレッドID: 9] [ロガー: NLogTutorial.MainWindow] - Debugを一回出力します
2024-05-09 11:37:50.2637 - INFO - [スレッドID: 9] [ロガー: NLogTutorial.MainWindow] - Infoを一回出力します
2024-05-09 11:37:50.2637 - WARN - [スレッドID: 9] [ロガー: NLogTutorial.MainWindow] - Warnを一回出力します
2024-05-09 11:37:50.2637 - ERROR - [スレッドID: 9] [ロガー: NLogTutorial.MainWindow] - Errorを一回出力します
2 日目はbackup
フォルダ内にarchive_rolling.0.log
というファイルが作成され、archive_rolling.log
にはその日に実行されたログが出力されています。
2024-05-10 16:00:21.9802 - TRACE - [スレッドID: 14] [ロガー: NLogTutorial.MainWindow] - Traceを一回出力します
2024-05-10 16:00:21.9802 - DEBUG - [スレッドID: 14] [ロガー: NLogTutorial.MainWindow] - Debugを一回出力します
2024-05-10 16:00:21.9802 - INFO - [スレッドID: 14] [ロガー: NLogTutorial.MainWindow] - Infoを一回出力します
2024-05-10 16:00:21.9802 - WARN - [スレッドID: 14] [ロガー: NLogTutorial.MainWindow] - Warnを一回出力します
2024-05-10 16:00:21.9802 - ERROR - [スレッドID: 14] [ロガー: NLogTutorial.MainWindow] - Errorを一回出力します
archive_rolling.0.log
を見ると昨日記録 されたログでした。
約 7 日間アプリケーションを起動して実行しましたが、5 日分のログでローテーションされていることを確認できました。
まとめ
今回は NLog を使用したログの出力方法についてまとめました。その他、やりたいことであったログローテートまで実装しました。その他、メールで通知することも出来るみたいですね。
今回作成したサンプルのリポジトリです。