
【Rails】監査ログの実装について
作成日 2026-08-02更新日 2026-08-02
はじめに
仕事で監査ログを実装する時があるので、監査ログについて自分の考えをまとめてみた
監査ログとは
いつ、誰が、何を操作したかがわかるように記録するデータ
フォーマットは変更がわかればなんでもいいと思うが大事な事はもれなく記録されていること
「何を」の部分は、操作があった事実だけを残すのか、変更前後の値(diff)まで残すのかで設計が変わってくる
記録したくない情報があるならカラム単位で記録対象から除外する、値をマスキングするといった設計もあわせて必要になる
また、記録した監査ログ自体が後から改ざん・削除できてしまうと意味がないので、通常のログ以上に改ざん・削除されない仕組みになっているかも重要なポイントだと思う
具体的には以下のようなシステム・シーンで使われることが多い
- 金融系システムの入出金・残高変更など、お金に関わる操作の証跡
- 人事・給与システムでの給与額や評価情報の変更履歴
- 管理画面上での権限変更・退会処理・返金処理などの重要操作
- 不正アクセスや問い合わせ対応時に、いつ誰がどのデータを操作したかを調査する場面
いずれも「後から誰が何をしたか説明できる必要がある」という点が共通していて、法令や社内規定で保存が義務付けられているケースも多い
実装方法 アプリケーション側で書く
アプリケーション側で明示的に監査ログを書き込む処理を作る
railsだとModelのコールバックで書き込むことが多いかもしれない。paper_trailやauditedのようなgemを使うと、この仕組みを自前で実装しなくても導入できる
注意点
- Modelのコールバックだと
update_all、update_columns、insert_all、upsert_all、delete_allのようなコールバックを経由しない処理でデータが操作された時に監査ログの作成が漏れる。paper_trailやauditedのようなgemも内部的にはModelのコールバックを使っているので、同じ制約がある
- Modelのコールバックだとレコード単位の作成になると思うのでパフォーマンスに影響する可能性がある
- Modelではなく、個別実装になる時もあるので単純に実装を忘れる時もある
実装方法 DBのトリガーで書く
DBのトリガーをINSERT,UPDATE,DELETEごとに作成して、監査ログのテーブルに書き込む方法
DB側で行うので、アプリケーション側の実装漏れによる監査ログの作成漏れは起きにくい
テーブル設計はテーブル毎に監査ログ用のテーブルを作成する方法もあると思うが、テーブル追加する毎に増えることになるので、PostgreSQLのAudit trigger 91plusのように、全テーブル共通の1つの監査テーブルに集約する設計の方が個人的には良いと思う
- (補足)Audit trigger 91plusは古い情報だが参考にはなると思う
注意点
- 行単位のトリガーでは、1回のクエリで大量の行を操作する場合も、変更された行数分の処理が行われるため、パフォーマンスに影響する可能性がある。
- (補足)PostgreSQLでは、transition tableを使って変更行をまとめて処理する方法もある。
- アプリケーション上の操作者のユーザーIDまで記録したい場合、DBだけではRailsにログインしているユーザーを判別できない。そのため、操作対象テーブルに updated_by や operator_id など、操作者を表す専用カラムを持たせ、その値を監査ログへ記録する方法がある。ただし、物理DELETEでは削除時の操作者を対象レコードへ新たに設定できないため、別途考慮が必要になる。
- (補足)PostgreSQLでは、SET LOCALなどを使ってトランザクション単位で操作者を渡す方法もある。
- DELETE-INSERTみたいに1回の業務操作で複数のクエリを発行する場合に削除と登録の監査ログが発行されるので、ユーザーに監査ログを見せる場合は考慮が必要
- 監査ログへの書き込みも元の操作と同じトランザクション内で行われるので、トランザクションがロールバックされると監査ログも一緒に取り消される
- TRUNCATEはINSERT/UPDATE/DELETEの行単位トリガーでは記録されないので、対象にする場合は別途トリガーが必要
まとめ
- アプリケーション側(Modelのコールバックやgem)で書く方法は実装が楽な反面、コールバックが動かない操作では監査ログの作成が漏れるリスクがある
- DBのトリガーで書く方法は、通常のDML操作であれば経路によらず記録できる反面、トリガー自体の管理コストがかかる
監査ログにとって一番大事なのは「もれなく記録されていること」だと思っているので、通常のDML操作をアプリケーション以外の経路も含めて漏れにくく記録したいなら、DBのトリガーが有効だと思う