自作ロガー
既製のログフレームワークを使えない事情がある場合に、アプリケーション自身が実装する障害調査用の診断ログの仕組み。
自作logger
自前実装のロガー
ロガーの結合テスト
文字列整形やJSON化のユニットテストだけでは拾えないI/O・並行性・回転・終了時flush・権限エラーを、実ファイル・実スレッド・実プロセスで確認するテスト。
実ファイル・実スレッド・実プロセスでのテスト
JSON Lines
1行に1個のJSON値を書き、改行(\n)で区切るテキスト形式。文字コードはUTF-8で、BOMを付けてはいけないと定められている。
ログのsessionId
プロセスの1回の起動を1セッションとして採番し、同じ起動のログをまとめて追跡するための、ログレコードの必須項目。
sessionId(ログ)
processId(ログ項目)
sessionIdとは別に、ログレコードが記録するOS上のプロセスIDの項目。
pid(ログ項目)
single writer(単一書き込み口)
呼び出し側が何スレッドあっても、書き込み先(ログファイルやシリアルポートなど)への実際の書き込みは1か所(1スレッド)だけに絞る設計。
書き込み口の一本化
bounded queue(上限付きキュー)
呼び出し側はキューに積むだけですぐ戻り、実際の書き込みはsingle writer側が行う、上限のあるキュー。あふれたときの方針を決める必要がある。
上限のあるキュー
flush(ログの同期書き出し)
メモリ上のバッファを実ファイルへ吐き出す処理。ここを通していないログは異常終了で消える。
同期flush
ログの回転(ローテーション)
ファイルが大きくなったり日付が変わったりしたときに、新しいログファイルへ切り替える仕組み。
ローテーション
ログの保持(リテンション)
古いログファイルを何本・何日まで残すかの上限を定める仕組み。
リテンション
1プロセス1ファイル
ログファイルへの書き込み主体を1プロセスにつき1ファイルへ限定する、自作ロガーの基本方針。
プロセスごとの単一ログファイル
複数プロセスによる同一ログファイルへの追記
複数プロセスが同じログファイルへ追記する設計。排他制御・部分書き込み・回転タイミング・異常終了時の扱いが一気に難しくなる。
複数プロセス共有ログファイル
sessionIdの衝突
起動時刻とプロセスIDだけでsessionIdを作った場合に、OSによるプロセスIDの再利用やローカル時刻の巻き戻りで前回と同じ値になり得ること。
セッションIDの重複
別々の起動のログレコードの混在
sessionIdが衝突しファイル名も同じ値を使う設計のとき、FileMode.Appendで開いた瞬間に別々の起動のログレコードが1つのファイルへ混ざる状態。
セッションの取り違え
ログレコードの機械検証
行数の一致・各行が単独でJSONとしてパースできるか・必須項目が値と型まで揃っているかを機械的に確認する検証方法。
行数・パース可否・必須項目の検証
Microsoft.Extensions.Logging(ILogger)
.NET標準のログAPI。ログレベル・カテゴリ・出力先を差し替えるプロバイダーの仕組みを持ち、多くの.NET SDKに暗黙の参照として含まれる。
.NET標準のILogger API
ILogger / ロギング基盤
障害調査用の診断ログ
監査証跡・分散トレーシング・メトリクス基盤・クラウド集約を対象外とし、アプリケーション障害の切り分けに使うことに絞ったログの用途。
アプリケーション障害の切り分け用ログ
Serilog
構造化イベントを前提にした.NET向け診断ログライブラリ。メッセージテンプレートのパラメーターに名前を付け、値をイベントのプロパティとして保持する。
NLog
構造化ログと従来型ログの両方に対応する.NET向けログライブラリ。JSONレイアウトや自動命名・アーカイブ付きのファイル出力を持つ。
spdlog
C++11以降で使えるログライブラリ。サイズで切り替えるrotatingと日付で切り替えるdailyのファイル出力を持つ。
保存失敗時の明示的な失敗通知
ログ保存先が使えないときに黙って別の場所へ書かず、アプリの通知・イベントログ・標準エラーなど明示的な手段で失敗を表に出す設計方針。
保存先異常時のフォールバック禁止
本記事が要件とする自作ロガーは、UTF-8のJSON Linesを1レコード1行の形式として採用する
自作ロガーの必須項目にはlogSessionIdが含まれ、同じ起動のログをまとめて追跡できるようにする
自作ロガーの必須項目にはprocessIdが含まれ、logSessionIdとは別にOS上の実体を記録する
本記事が要件とする自作ロガーは、呼び出し側のスレッド数によらず、書き込み口を1本(single writer)に絞る
bounded queueを使う設計は、キューから実際に書き込むのがsingle writerであることを前提とする
ログ量が多く同期I/Oが律速になる場合、自作ロガーはsingle writer + bounded queueの構成を採用する
本記事が要件とする自作ロガーは、Error/Criticalとセッション開始・終了のログを同期flushする
本記事が要件とする自作ロガーは、無限に増え続けないよう、v1から回転の仕組みを持つ
本記事が要件とする自作ロガーは、回転とあわせて、古いログファイルを何本残すかの保持上限を持つ
自作ロガーの基本方針として1プロセス1ファイルが推奨される
複数プロセスから同じファイルへ追記させる設計は、排他制御・部分書き込み・回転タイミング・異常終了時の扱いが難しくなるため避けるべきとされる
起動時刻・プロセスID・GUIDを組み合わせて採番する場合に限り、logSessionIdの衝突を防ぐ。起動時刻とプロセスIDだけで採番すると、この衝突がそのまま起き得る
logSessionIdが衝突しファイル名にも同じ値を使う設計だと、FileMode.Appendで開いた瞬間に別々の起動のレコードが1ファイルに混ざる原因になり得る
自作ロガーが運用で信用できるかどうかは、実ファイル・実スレッド・実プロセスでの結合テストで確認する
結合テストでは、行数・各行のパース可否・必須項目の値と型を機械的に検証する方法を用いる
既製のログフレームワークが使える場合、.NET標準のMicrosoft.Extensions.Loggingの利用が推奨される
既製のログフレームワークが使える場合、構造化イベント前提のSerilogが選択肢として推奨される
既製のログフレームワークが使える場合、構造化ログと従来型ログの両対応であるNLogが選択肢として推奨される
C++の既製ログフレームワークとして、rotating/dailyのファイル出力を持つspdlogが選択肢として推奨される
依存を増やせない・実行環境が限られるなど既製フレームワークを使えない事情がある場合に限り、自作ロガーが選択肢になる
ログ保存先が使えないとき黙って別の場所へ逃がさず、明示的な手段で失敗を表に出す設計が自作ロガーには推奨される