これはLAPRASアドベントカレンダーの17日目です(時空が歪んでいる・・・)
今回は、Webアプリケーションのログの設計について、普段思っていることを書きます。
Webサービスを運営する上で、ログを残すことの重要性は言うまでもありません。
システムの一挙手一投足を全てログに残すわけにはいきませんから、どのようなログを残すかは意思を持って設計しなければなりません。
アプリケーションのロジックやデータは修正できても、過去に残さなかったログを後から取得することはできません。たかがログと侮っていると、後で痛い目を見ることもあります(N敗)。
本稿では、ログを設計する上で「何を見るためにログを取るのか」という観点から、ログを設計する上で基本的な事項をまとめました。
なんのためにログを取るのか
さて、そもそも我々はなぜログを残すのでしょうか。
ログを残す理由は様々ですが、例えば、IT mediaさんのこちらの記事では、ログを取る理由を大きく「システムの利用状況やシステム自身の稼働状態を確認するため」と「セキュリティ上の目的」に分け、セキュリティ上の目的の場合はさらに「不正(もしくはその兆候)の発見」と「トレーサビリティの確保」に分類しています。
このようにログの目的を明らかにすることは重要で、目的によって必要なログの量や求められる要件が異なってきます。
一方で、ログの取得目的はキッパリと境界が引かれるものではなく、グラデーションになっているものです。また、1つのログでもセキュリティと運用管理の両方の目的を果たすこともあります。
ログの目的を明らかにすることは非常に重要ですが、どのようなログを残すべきかを考える上では、もう少し指針となる設計思想が欲しいものです。
今回は、「ログによって何を見たいのか」という観点からログを分類することで、ログを設計する上での考え方を示せれば良いかなと思います。
ログで何を見たいのか
ログとは記録ですから「何」の記録を残すのかが当然重要になります。
ここでいう「何」とは、「アクセスログ」「イベントログ」のような取得タイミングや形式による分類や、技術的にどのように記録するかといった話ではありません。
本稿では、ログを残すことによって、ログの先にある「何」の動きを記録したいのかという観点で考えていきます。
Webアプリケーションにおいて監視したい対象は、大きく分けると次の3つに還元されると考えられます。
- A. ユーザーの行動
- B. データの状態
- C. システムの処理
ログを分析することは、ログを通してユーザーやシステムがどのような動作を行ったか、ある時点のデータがどうであったかを調べることに他なりません。
このため、全てのログは、ユーザーかデータかシステムの記録であると考えることができるでしょう。
これを意識してログを設計しておくことで、いざログを使いたいと思った時に役に立つログを残すことができると考えています。
A. ユーザーの行動を記録する
ユーザー行動のログは、ユーザーがいつどのような操作を記録するためのものです。
ユーザーがアプリケーションに触れることでどのような行動をとったかを記録することで、UX上の仮説検証や振り返りを行うことができます。典型的な例はGoogle Analytics などで、どのページが閲覧されたかとか、特定の機能が使われたかを記録します。
また、ユーザーの行動を詳細に記録することで、悪意あるユーザーの行動を検知するなど、発見的なアプローチでのセキュリティ対策に役立てることができます。
この種のログを記録する上での注意としては、なるべくユーザーの近くで取得し、「ボタンを押した」「フォームを送信した」など、ユーザーの行動と1対1に対応していることが重要です。
例えば、「ユーザーが編集を行った」というログを残そうとしたとします。この時2種類の操作が結果的に同じ結果を生じる場合、それらは別々にログを残しておかないと、ユーザーが実際にどのような操作を行ったのかが後でわからなくなってしまいます。また、後でバグの存在が発覚し「ユーザーが行った操作」をもとに影響範囲を絞り込もうと思った時なども困ります。
もう一点、ユーザー行動のログではなるべく該当ユーザーを特定して記録するようにしましょう。
どのユーザーによる操作かがわからなければ、セキュリティやシステム障害における調査目的でログを使おうとしたときに役に立ちません。
例外として、ユーザーの行動を統計的に把握したいだけの場合は匿名のログでも構いません。しかしながら、後から調査・分析する際に利用できるよう、非ログインセッションのように不可能な場合を除いてユーザーIDを記録しておくことをお勧めします。(その際、ユーザーIDは特定の個人を容易に照合できる個人情報に該当する可能性がありますので、取り扱いには注意を要します)
B. データの状態を記録する
過去のある時点でデータがどのような状態にあったのかを再現できるようにするためのログです。
アプリケーションログではないですが、この用途での最も典型的な例は、RDBMSにおけるトランザクションログでしょう。
データ分析をしようとするたびに、DBのバックアップとトランザクションログからある時点の全てのデータを復元していては大変なので、アプリケーションレイヤでも必要に応じてデータのログを残すことが行われます。
任意の時点でのデータを再現できるようにすることで、ユーザーにどのような画面が表示されていたのか、また、ユーザー操作の結果としてどのようなデータが記録されたのかを確認できます。Aのユーザー行動ログと合わせることで、ユーザーの行動とその結果を突き合わせることで、データ不整合を伴う問題に対処する際に非常に役に立つものとなるでしょう。
また、ユーザー状態などのデータを時系列で後から統計をとることにも使えます。
例えば、スカウトサービスにおいて、スカウト受信数をスカウト受信時のユーザー状態ごとに調べたいと思った時、過去にさかのぼってユーザー情報を調べることができると便利です。
データのログを残す場合の注意点としては、ユーザー行動ログとは逆に、ユーザーの操作ではなく、どのようなデータを格納したかに着目します。
同じ操作をしても、現在の状態によって格納される値が変わってしまう場合などは、ユーザー操作で保存していると、データの変化を追うのが大変になってしまいます。ある時点でのデータの状態を取得しようと思ったとき、以下の例のようにその時点より前の最後のレコードを調べれば良い状態を作っておくのが理想です。
-- 2022年末時点でのユーザー状態を取得
SELECT
status
FROM
user_status_log
WHERE
user_id = 1
AND logged_at < '2023-01-01 00:00:00'
ORDER BY
logged_at DESC
LIMIT 1
これを可能にするためには、データの変更があるたびに最新の状態を確実に保存していく必要があります。言い換えれば、データを再現するためのログは完全性が重要ということです。
C. システムの処理を記録する
3つめは、ユーザーでもデータでもなく、システムの処理自体を記録するためのログです。
基本的に、よくテストされたシステム(モジュール)であれば、データ(内部状態)とユーザー操作(外部からの入力)が決まれば、システムの動作は再現することができます。しかしながら、現実的にはシステムの動作を完璧に把握することは不可能で、不測の事態が発生した場合に事後的に調査を可能にするためにこそログを残すことが必要です。
この用途での典型的な例はエラーログです。
また、システムの動作がユーザーの操作に起因しない場合(例えば、バックグラウンド処理の場合)、正常系においてもシステムの動作を記録することの価値が高まります。
ユーザー操作によらずにシステムがデータをいじった場合、前述のユーザー操作ログとデータログとの間に齟齬が生じてしまいます。バックグラウンド処理は正常系でも厚めにログを取る意義があります。
システムの動作のログにおいては、監視対象はコード自体なので、コードのどの部分が実行されてログが出力されたかがわかるようにすることが重要です。
例えばエラーログならスタックトレースを残したり、バックグラウンド処理のログはタスク名や実行時引数などを記録することが重要です。
おわりに
本稿では、Webアプリケーションにおけるログを「何を見たいのか」で分類し、分類ごとにログを設計する上での基本的な注意点をまとめました。
ログ収集は様々なフレームワークやログ収集システムによって自動化されていますが、何を見たいのかを意識せずにログを取得してしまうと、後々分析や障害対応でログを使おうと思った時に使えなかったということになりかねません。
後で泣きを見ないためにも、なにを見たいからログを取るのか、初歩的ながら基本的な部分を意識して適切にログを残していくことを意識していきたいものです。