C#のログ出力入門!SerilogとNLogの使い方を徹底解説
生徒
「C#でプログラムを作っているのですが、実行中に裏側で何が起きているのか分からなくなることがあります。これを記録する方法はありますか?」
先生
「それは『ロギング』という技術を使うと解決できますよ。C#にはSerilogやNLogといった、便利なログ出力専用のライブラリがあるんです。」
生徒
「ロギング…初めて聞きました。難しい設定が必要なのでしょうか?」
先生
「基本さえ覚えれば簡単です。プログラムの動作履歴を日記のように保存できるので、エラーが起きた時の原因調査にも役立ちます。一緒に見ていきましょう!」
1. ログ出力(ロギング)とは何か?
プログラミングの世界におけるログ出力(ロギング)とは、プログラムが動いている時の様子を、テキストファイルや画面に「記録」として残すことを指します。例えるなら、飛行機の「ブラックボックス」や、学校で毎日書く「出席簿」や「日誌」のようなものです。
パソコンを触ったことがない方にとって、プログラムは魔法のように勝手に動いているように見えるかもしれません。しかし、実際には一つひとつの命令を順番に実行しています。もし途中でプログラムが止まってしまったとき、どこまで正しく動いて、どこで失敗したのかを後から確認するためにログが必要になります。デバッグ(プログラムの不具合探し)において、ログは最も重要な手がかりとなります。
2. 標準の出力とライブラリの違い
C#には、標準で文字を表示するConsole.WriteLineという命令があります。これも一種のログですが、本格的なアプリ開発では不十分です。なぜなら、標準の命令では「画面に表示するだけ」であり、過去の記録をファイルに保存したり、重要なエラーだけを色分けして目立たせたりすることが難しいからです。
そこで登場するのがライブラリです。ライブラリとは、便利な機能をまとめた「道具箱」のことです。C#で有名なログ用ライブラリには、Serilog(セリログ)とNLog(エヌログ)があります。これらを使うことで、「1週間経ったら古いログファイルを消す」「エラーが発生したときだけメールを送る」といった高度な操作が簡単に行えるようになります。
3. ログの重要度「ログレベル」を理解しよう
ログには「重要度」というものがあります。これをログレベルと呼びます。すべての情報を同じように記録すると、情報が多すぎて本当に大切なエラーを見逃してしまうからです。一般的には以下のような段階に分かれています。
- Information(情報):「アプリが起動しました」といった通常の動作記録。
- Warning(警告):「少し怪しい動きですが、動作は続けています」という注意喚起。
- Error(エラー):「計算に失敗しました」など、何らかの問題が発生した状態。
- Fatal(致命的):「プログラムが強制終了します」という非常に深刻な状態。
このようにレベルを分けることで、後から「エラーだけを抜き出して確認する」といった効率的なチェックが可能になります。
4. Serilogの基本設定と使い方
まずは、現在非常に人気のあるSerilogから見ていきましょう。Serilogの特徴は、設定が非常にシンプルで、最新のC#プログラムと相性が良い点にあります。以下のコードは、画面(コンソール)にログを出すための最も基本的な書き方です。
using Serilog;
// ログの設定(どこに出すか、どのレベルまで出すかを決める)
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.CreateLogger();
// ログを出力してみる
Log.Information("プログラムが開始されました");
Log.Warning("注意!データが空かもしれません");
Log.Error("エラーが発生しました");
// 最後にログを正しく閉じる
Log.CloseAndFlush();
実行結果は以下のようになります。
[20:13:46 INF] プログラムが開始されました
[20:13:46 WRN] 注意!データが空かもしれません
[20:13:46 ERR] エラーが発生しました
このコードでは、WriteTo.Console()という命令を使って、パソコンの黒い画面(コンソール)に結果を表示するように指示しています。初心者の方でも、これだけでログの第一歩を踏み出すことができます。
5. ファイルにログを保存する方法
画面に表示するだけでなく、ファイルとして保存するのが実務では一般的です。これを「シンク(Sink)」と呼びます。Serilogでは保存先を簡単に追加できます。次の例では、日付けごとに新しいファイルを作って保存する設定を紹介します。
using Serilog;
Log.Logger = new LoggerConfiguration()
.WriteTo.File("logs/myapp.txt", rollingInterval: RollingInterval.Day)
.CreateLogger();
Log.Information("このメッセージはファイルに保存されます。");
Log.CloseAndFlush();
ここで使っているrollingInterval: RollingInterval.Dayという指定は、とても便利です。これは「毎日新しいログファイルを作ってください」という意味です。一つのファイルが巨大になりすぎて、メモ帳で開けなくなるのを防いでくれます。プログラムが動いているフォルダの中に「logs」という名前の入れ物が自動で作られ、その中にテキストファイルが出来上がります。
6. NLogの基本と設定ファイルの役割
次に、古くから多くのシステムで使われているNLogについて解説します。NLogの特徴は、プログラム本体の中に設定を書くのではなく、nlog.configという専用の設定ファイル(XML形式)にルールを書く点にあります。これにより、プログラムを書き換えなくても、設定ファイルを書き換えるだけでログの出し方を変えられるというメリットがあります。
設定ファイル(nlog.config)のイメージは以下のようになります。
<nlog>
<targets>
<target name="logconsole" type="Console" />
</targets>
<rules>
<logger name="*" minlevel="Info" writeTo="logconsole" />
</rules>
</nlog>
そして、プログラム側では以下のように呼び出します。
using NLog;
// ログを扱うためのインスタンス(実体)を作る
var logger = LogManager.GetCurrentClassLogger();
logger.Info("NLogを使って情報を出力します");
logger.Error("不具合が発生しました");
XML(エックスエムエル)という形式は、最初は少し難しく感じるかもしれませんが、「どこに(targets)」「どんなルールで(rules)」出すかを整理して書くためのルールだと考えてください。
7. 構造化ロギングのメリット
Serilogの最大の特徴に構造化ロギングがあります。これは、単なる文章としてログを残すのではなく、データとして残す方法です。例えば、「ユーザーAがログインした」というログを残す際、従来は一つの長い文章でしたが、構造化すると「ユーザー名」という項目と「動作」という項目を分けて記録できます。
var user = "田中さん";
var count = 5;
// 文章の中に変数を埋め込む(構造化)
Log.Information("{User}さんが{Count}回目のログインをしました", user, count);
このように書くと、後から「田中さんに関するログだけを検索する」といった作業が非常に高速に行えます。大規模なシステムになればなるほど、この構造化という考え方が威力を発揮します。データの型を意識した記録ができるのは、C#のような厳格な言語において非常に相性が良い機能です。
8. どちらのライブラリを選ぶべきか?
初心者の方が「結局どっちを使えばいいの?」と迷ったときは、まずはSerilogをおすすめします。理由は、設定がC#のコードだけで完結しやすく、直感的に理解できるからです。また、最近のWeb開発(ASP.NET Coreなど)では標準的に使われることが多いため、学習したことが無駄になりにくいです。
一方で、昔からある大規模な業務システムに関わる場合は、NLogに触れる機会が多いでしょう。設定ファイルで細かく制御できるため、複雑な運用ルールが決まっている環境では根強い人気があります。どちらを使っても「ログを取る」という目的は同じですので、まずは一つの方法を完璧にマスターすることを目指しましょう。
9. 実践!エラー処理とログの組み合わせ
最後に、実際のプログラミングでログがどのように使われるか、エラー処理(例外処理)との組み合わせ例を見てみましょう。プログラムが予期せぬ理由で止まってしまったときに、その理由をログに書き出す例です。
using System;
using Serilog;
Log.Logger = new LoggerConfiguration().WriteTo.Console().CreateLogger();
try
{
int a = 10;
int b = 0;
// 0で割り算をするとエラー(例外)が発生する
int result = a / b;
}
catch (Exception ex)
{
// 起きたエラーの内容をログに記録する
Log.Error(ex, "計算中にエラーが発生しました!");
}
finally
{
Log.CloseAndFlush();
}
実行結果は以下のようになります。
[20:13:46 ERR] 計算中にエラーが発生しました!
System.DivideByZeroException: 零で除算しようとしました。
このようにtry-catchという仕組みとログを組み合わせることで、たとえアプリが止まっても「なぜ止まったのか」が明確になります。これができるようになると、初心者から一歩進んだ「プロらしい」プログラムに近づくことができます。ログを制する者は、システム開発を制すると言っても過言ではありません。