ロギング設計大全
アプリケーション設計者視点で、どうログ設計するか? についてまとめます。
使途別
異なる用途で使われるので、それ毎にファイルやトップカテゴリで分類する。
トレーシング
世の中のロギングライブラリの主な想定用途はこれである。必要な箇所やレベルに応じて出力制御をするために、ログカテゴリやログレベルが存在する。
目的
アプリケーションの性能をモニタリングする
トラブル時に何のメソッドが呼ばれたかを追跡する
必要項目
タイムスタンプ
リクエストID
Microservicesのようにインスタンスをまたいでトレーシングしたい場合は、上流で発行されたリクエストIDを引き渡して、それをログに出力する。
メソッド名
手段
AOPでメソッドのEnter/Exitで自動的にロギングされるように設定する。
モニタリング
メトリクスAPIやヘルスチェック用のエンドポイントを用意するのが当たり前になった現代では、システム監視のためのログ出力少なくなった。が、バッチ処理のような長くかかるプロセスは、モニタリングのためのログを出力するのがお手軽で、まだまだ使われる機会は多い。
目的
システムの監視のため
必要項目
タイムスタンプ
メトリクス(の元となるデータ)
手段
画一的にログ出力することが難しいので、取得したいメトリクス毎にフレームワーク側で実装したり、アプリケーションコードで明示的に出力したりすることになる。
リモート呼び出し
特に開発や運用の主体の異なるサービスを呼び出したり、呼び出されたりする箇所においては、問題発生時の責任範囲切り分けのために、ログを出力しておくことが求められる。
目的
リモートAPIコールの問題発生時の責任範囲分析
必要項目
タイムスタンプ (リクエストとレスポンスそれぞれに)
リクエストID (トレーシングと同じもの)
リクエストのRawデータ
レスポンスのRawデータ
手段
リモート呼び出しのライブラリで、リクエストとレスポンスを自動的に記録する。
生データをそのまま吐き出す。が、カード番号やパスワードなどの機微な情報はマスクする。
監査
目的
セキュリティや内部統制のためにシステムの不正な利用がないかを後で調査するため
必要項目
タイムスタンプ (When)
ユーザID (Who)
IPアドレス/マシンID (Where)
参照データのID、範囲 (What)
手段
アプリケーションコードで明示的にLoggerを使って出力する。
業務イベント
目的
業務上のイベントを記録する。のでログと呼ぶのは憚られる場合も多い。
ただし、以下の条件に合致するものは高価なRDBMSに書き込む必要がないため、ファイルに書くこともある。
定型業務として、記録したデータを読み込むことはない。
統計処理にしか使わない
必要項目
タイムスタンプ
その他、RDBのテーブルを設計するのと同じようにスキーマ設計し、Semantic Loggingで出力する
手段
アプリケーションコードのの中で明示的にロガーを使って出力する。
ストレージが将来的に変わることも想定し、ロガーのAppenderで出力先を切り替えることができるようにしておく。
Appenderの切り替えによって、DynamoDBのような書き込みが高速なストレージにしたり、Kafkaのような分散キューイングを介してDWHに流し込んだりする。
システム利用状況の統計
目的
システムの利用状況を集計し、サービス改善などに利用する。
必要項目
ユーザID (非ログイン機能でも、ユーザ追跡のためのIDを発行する)
タイムスタンプ
手段
画面だけでよければ、Google Analytics等の外部ツールを使う。ので、ログという形で明示的に出力しないことが多い。
サーバサイドでしか記録できないものも、GAでまとめて見たいので、結局GAのAPIを叩くなどの実装にする。
障害追跡
例外を、業務上「予期するもの」「予期しないもの」に分類し、予期しない例外についてログを出力する。
目的
開発チームが対応しなくてはならない障害を解析するため
必要項目
タイムスタンプ
スタックトレース
手段
アプリケーションコードではハンドリングしない。
フィルタやAOPを使って、ロガーの通常の機能を使って出力する。たいていのロギングライブラリではExceptionオブジェクトを渡せばスタックトレースは出力される。
Exceptionではコンテキストが十分でない場合があるので、MDCを使って例外発生時の状況把握に必要な情報も同時に出力されるようにする。
自前でログ出力する以外にも、Sentryに送るなどの実装手段がある。
「予期しない例外」にもレベルがあり、サービス停止レベルのものから、急ぎの対応は不要なものまで分類できる。これを設計して、ログレベルを使い分けておく。
デバッグ
目的
開発時のデバッグの助けになる情報を出力する。
将来メンテする人のことを考慮し、他人が見てもそのログの意図やメッセージの内容が分かるようにする。
必要項目
特になし
手段
アプリケーションコードで、トレーシングと同じロガーを使っても構わない。
どこで出力するかは自由とはいえ、何の指針もないとぐちゃぐちゃになるので、次のような箇所で出力するとよいだろう。
データ変換の前後
データベースからの取得結果/件数
発行されたSQL
Web API呼び出しのRequest/Response
ロギングパターン (WIP)
トレースID
コンテキスト
1つのクライアントリクエストに対してレスポンスするまでに、複数のプロセスが介在する。
ソリューション
リクエストを受け付ける箇所でトレースIDを生成し、それを各プロセスに渡していき、それをログにも出力する。
実装
Jaeger
ZipKin
マスキング
コンテキスト
センシティブなデータをログに出力すると、ログの参照権限は運用者の広範囲に及ぶことが多いので、漏洩リスクが高まる。
ソリューション
ログエントリ間の同一性まで含めて、センシティブなデータ(パスワードや誕生日など)であれば、「****」などの単純な文字列に置き換えるか、エントリ自体から削ってしまう。
そうでなければ、ハッシュ化して出力するでもよい。
実装
MDC
コンテキスト
ログに出力したいデータを持つレイヤと、実際にログを出力するレイヤが異なる。
ソリューション
スレッド毎のグローバルエリアがあればそこにデータをもたせる。
Semantic Logging
ローテーション
その他 (WIP)
参考資料