ILogger でログ出力を行う際、変数の値をメッセージに埋め込みたい場合、C# の文字列補間式($"")を使って結合してしまいがちです。しかし、ログのパフォーマンスや可読性、将来的な分析のしやすさを考えると、「メッセージテンプレート(Message Templates)」 を使用するのが推奨されます。
ここでは、変数を {Placeholder} 形式で埋め込む構造化ログ(Structured Logging)の書き方を解説します。
実装サンプル:決済処理のログ
題材を「決済システム」とし、取引IDと金額をログに残すコードです。文字列結合ではなく、引数として変数を渡している点に注目してください。
1. PaymentWorker.cs (ログ出力クラス)
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace PaymentApp
{
public class PaymentWorker : BackgroundService
{
private readonly ILogger<PaymentWorker> _logger;
public PaymentWorker(ILogger<PaymentWorker> logger)
{
_logger = logger;
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
// ログに出力したい変数
var transactionId = "TX-998877";
decimal amount = 5000m;
// 【推奨される書き方】
// メッセージ内に {名前} のプレースホルダーを置き、第2引数以降に変数を渡す
_logger.LogInformation("決済処理を開始します。取引ID: {TransactionId}, 金額: {Amount}円", transactionId, amount);
// 【非推奨の書き方】(文字列補間)
// _logger.LogInformation($"決済処理を開始します。取引ID: {transactionId}, 金額: {amount}円");
return Task.CompletedTask;
}
}
}
2. appsettings.json (ログレベル設定)
PaymentApp 名前空間の Information レベル以上のログを表示するように設定します。
{
"Logging": {
"LogLevel": {
"Default": "Warning",
"System": "Warning",
"Microsoft": "Warning",
"PaymentApp": "Information"
}
}
}
実行結果
info: PaymentApp.PaymentWorker[0]
決済処理を開始します。取引ID: TX-998877, 金額: 5000円
解説:なぜ $ (文字列補間) を使ってはいけないのか?
1. 構造化ログ (Structured Logging) への対応
テンプレート構文 "{Key}", value を使うと、ログ基盤(Application Insights, Elasticsearch, Seqなど)に送られた際、単なる「文章」としてだけでなく、「プロパティ(キーと値)」として保存されます。 これにより、後から「Amount が 10000 以上のログだけを検索する」といったクエリが可能になります。文字列補間($"{var}")を使ってしまうと、ただの文字列になってしまい、このメリットが失われます。
2. パフォーマンスの向上
文字列補間式($"")は、ログレベルに関係なくその場で文字列結合処理が走ります。 一方、テンプレート構文を使えば、もしそのログレベルが無効(例:Traceログだが設定はWarning以上)の場合、文字列のフォーマット処理自体がスキップされるため、無駄なCPU処理を抑えることができます。
3. 書き方のルール
- プレースホルダーは
{Name}のように名前を付けます。数字({0})も使えますが、名前付きの方が後で検索しやすくなります。 - 引数の順番は、プレースホルダーの出現順に合わせます。
