食べチョク開発者ブログ

食べチョクエンジニアによるプロダクト開発ブログ

監査ログをファイルに記録するためのGemを公開しました

こんにちは。
食べチョクの開発を副業でお手伝いしているプログラマーの花村です。

監査ログをJSONL(JSON Lines)のファイルに記録するためのGemのAuditLoggableを作成してrubygems.orgで公開しました。
ソースコードもGitHubで公開しています。 ​ rubygems.org github.com

なぜ開発したのか

食べチョクでは監査ログを記録するためにAuditedを利用していました。
AuditedはActiveRecordのコールバックを利用してモデルの変更を手軽にRDBに記録してくれる大変便利なGemです。

しかしRDBに記録するためテーブルサイズが肥大化しパフォーマンスに影響を与える場合があるというデメリットもあります。
食べチョクでは注文数の増加に伴ってこの問題に直面しました。

これを解決するにあたり以下のようなアプローチが思いつきます。

  1. 監査ログを定期的に消す
  2. 監査ログを記録するDBを分ける
  3. 監査ログをRDBとは別のストレージに記録する

1では過去分のデータが必要になった際に困りますし、2では延命措置にしかなりません。
ということで今回は3を採用しました。

記録したログファイルの扱い

AuditLoggableで記録したログファイルはCloudWatch Agentを利用してCloudWatch Logsに連携しています。
CloudWatch Logsには1か月分ほど保持する設定にしており、毎日CloudWatch LogsからS3へバックアップしています。
このときにCloudWatch LogsのExportTask機能を使ってエクスポートするとJSONのメッセージに余分な情報(CloudWatch Logsに取り込んだ日時の情報)がついてしまうので、S3のイベント通知を使いLambdaで整形処理を走らせています。

CloudWatch Logsに保持し続けずにS3に移しているのはコスト圧縮のためです。 1カ月より過去分を参照したいときはAthenaで検索できるようにしてあります。

なぜログファイルに記録するのか

監査ログの記録先としてRDB以外を使うのであればDynamoDBなどのNoSQLなDBに記録することも考えられます。
しかし以下の理由で記録先はファイルとしました。

  1. 記録の失敗しにくさ
  2. リアルタイムに参照できる必要はない​

Web API経由での記録では失敗することも多いのでそのためのハンドリングが煩雑になることが予想されます。
また監査ログを記録された瞬間から参照したいことはないので収集にラグがあっても構わないという判断がありました。

使い方

config/initializers/audit_loggable.rb など初期化ファイルで出力先のファイル名などを設定します。

AuditLoggable.configure do |config|
  if Rails.env.test?
    config.auditing_enabled = false
  end
  config.audit_log_path = Rails.root.join("log", "audits.log")
end

ApplicationRecord など必要なモデルで AuditLoggable::Extension でActiveRecodのモデルに機能を追加します。

class ApplicationRecord < ActiveRecord::Base
  extend AuditLoggable::Extension
end

監査ログを記録したいモデルで log_audit を呼び出して記録対象に設定します。

class Post < ApplicationRecord
  log_audit
end

ApplicatoinController などで以下のように情報集する機能を有効化することで「操作したユーザーID(とクラス名)」、「リモートIPアドレス」、「リクエストID」を監査ログに記録できるようになります。

class ApplicationController < ActionController::Base
  around_action AuditLoggable::Sweeper.new(current_user_methods: %i[current_user])
end

Auditedとの違い

Auditedと似た挙動をしますが主に以下の違いがあります。

  • 記録先はJSONLのログファイル
    • RDBではなくJSONLのログファイルに記録します
  • ActiveRecordのモデルへのメソッド追加は最小限
    • Auditedでは audit_xxx のようなインスタンスメソッドが複数追加されますが、AuditLoggableでは1組のaccessorの追加のみです
    • またクラスメソッドは機能の有効化用メソッド1つを追加するのみです
  • Associated Auditsは未サポート
    • 食べチョクでは不要であったことと複雑になってしまうので未サポートとしました
  • 条件付き記録は未サポート
    • 食べチョクでは不要であったことと複雑になってしまうので未サポートとしました

おわりに

似たような課題を抱えている方の解決の一助になれば幸いです。 もしバグを見つけたり機能を追加したいなどありましたらGitHubでPull RequestやIssueをお待ちしています。

github.com