監査ログは、通常のアプリケーションログと違って「誰が、いつ、何をしたか」を後から追跡するため、欠損の検知と不正な変更の防止が求められるログです。Fluentdは既定値のままでも動きます。ただし既定の retry_timeout は最初の再試行から72時間で、それを過ぎると送信待ちのキューは空にされます。v1.19.0以降のfile bufferならチャンクは退避されて手で戻せますが、退避先を決めていないと再起動で消えることがあります。この記事では、Fluentd 1.19.4(2026年9月28日リリース)で送信側と集約側の2段構成を組み、集約側を止めたときにログがどうなるかを実際に確かめた結果をもとに、監査ログ向けの設定と保管の設計をまとめます。Fluentd自体の仕組みや設定ファイルの基本はFluentdとは?設定ファイルの書き方とfluent-package v6移行の判断で扱っています。
まとめ:Fluentdで監査ログを集めるときの設計の要点
- バッファはfileにし、
<system>のroot_dirと各プラグインの@idを必ず決める。リトライ上限で捨てられたチャンクは v1.19.0 以降root_dir配下に退避されますが、未設定だと退避先は/tmp/fluentです(実測)。 - 監査ログの経路は
retry_forever trueを基本にし、ディスク残量とリトライ回数を監視する。上限付きで運用するなら<secondary>を必ず付けます。 - 送信側の
require_ack_response trueは「集約側のバッファに書けた」ことまでしか保証しません。集約側もfile bufferにし、重複に備えてアプリ側でevent_idを振ります。 - 転送路は
transport tlsとshared_keyで暗号化と送信元認証を両方かける。鍵が合わない接続は集約側で拒否されます。 - 改ざん防止はFluentdではなく保管先で担保する。S3ならObject Lockのcomplianceモードとバケットの既定保持期間を使います。
Fluentdで組む監査ログ基盤の構成と、ログが欠ける4か所
監査ログをFluentdで集めるときは、各サーバーに置く送信側(forwarder)と、ログを受けて保管先へ書く集約側(aggregator)の2段に分けるのが基本形です。送信側はローカルのログファイルを in_tail で読み、out_forward で集約側へ送ります。集約側は in_forward で受け、S3やOpenSearchなどへ書き出します。収集・転送・保存の一般的な分担はログ集約の仕組みとエージェント選定を参照してください。
ここでは、Fluentdの収集・転送経路で代表的な欠損箇所を4つに分けます。アプリケーションでの記録漏れ、パース失敗、保管後の削除などは別途確認が必要です。
| 欠ける場所 | 欠ける条件 | 塞ぐ設定 |
|---|---|---|
| 送信元のログファイル | ローテーション後、読み切る前に旧ファイルが消える | pos_file・rotate_wait |
| 送信側のバッファ | memory bufferのままプロセスが落ちる | @type file |
| 送信側から集約側への転送 | 受信直後に集約側が落ちる | require_ack_response |
| 集約側から保管先への書き込み | 保管先の障害がリトライ期限を超える | retry_forever・secondary |
送信元のファイルについては、in_tail は出力側のバッファが満杯で書き込めないとき、読み取り位置(pos_file)を進めずに同じ行を読み直します(v1.19.4のソースで確認)。バッファ溢れそのものでは欠けませんが、詰まっている間にローテーションされた旧ファイルが削除されると読み残しが失われます。ローテーションの保持量は、想定する最長停止時間と復旧後の追い付き時間に、ピーク時のログ生成量を掛けて決めます。世代保持だけでなく、ローテーション後の旧ファイルを実際に読み切れることも障害試験で確認します。
監査ログのフォーマット設計|必須フィールドとevent_idによる重複排除
Fluentdはレコードの中身を問わないので、監査ログとして使える形にするのはアプリケーション側の責任です。1行1イベントのJSONで出力しておけば、送信側は <parse> @type json</parse> で読むだけで済みます。JSONでログを出す実装は構造化ログとは|JSONでログを機械可読にする仕組みで扱っています。
{"event_id":"0f8b2c4e-7d1a-4c55-9a3e-2b6f1e8d9c01","ts":"2026-10-05T04:00:01.123Z","actor":{"type":"user","id":"u-1042"},"action":"user.role.update","target":{"type":"user","id":"u-2210"},"result":"success","source_ip":"203.0.113.24","request_id":"req-7c1e9"}
| フィールド | 役割 | 決め方 |
|---|---|---|
| event_id | 重複排除のキー | アプリでUUIDを採番 |
| ts | 発生時刻 | UTCのISO 8601・ミリ秒まで |
| actor | 誰が | ユーザーIDかサービスアカウント |
| action | 何をしたか | 対象.操作 の固定語彙 |
| target | 何に対して | 種別とIDの組 |
| result | 成否 | success / failure / denied |
| source_ip・request_id | どこから・どの要求か | アプリログと突き合わせる鍵 |
event_id は必須です。後述の require_ack_response を有効にすると転送は at-least-once になり、Fluentdの公式ドキュメントも障害時には同じメッセージが2回届く場合があると明記しています。Fluentd側で重複を消す仕組みは無いので、保管先で event_id をキーに重複を除くか、検索時に除きます。通常の出力リトライではバッファ済みのレコードがIDごと再送されるので、送信側の record_transformer で採番しても再送分のIDは変わりません。ただし、pos_fileを失って元ファイルを読み直したときはフィルターを再通過して別のIDが付きます。どの経路で重複しても同じIDになるよう、アプリで採番してください。何を監査ログに残すかの判断は、API認証の文脈でAPI認証の設計|方式選定マトリクスとトークン寿命・監査ログの決め方にまとめています。
送信側の設定|ack付き転送とfile bufferによる欠損対策
各サーバーに置く送信側の設定例です。2台の集約側のうち1台を standby にして、主系が応答しないときだけ副系へ送ります。
<system>
root_dir /var/lib/fluent
</system>
<source>
@type tail
@id in_tail_app_audit
read_from_head true
path /var/log/myapp/audit.log
pos_file /var/lib/fluent/app_audit.pos
tag audit.app
<parse>
@type json
time_key ts
time_type string
time_format %iso8601
keep_time_key true
</parse>
</source>
<match audit.**>
@type forward
@id out_forward_audit
transport tls
tls_cert_path /etc/fluent/tls/ca.crt
require_ack_response true
<security>
self_hostname app01.example.internal
shared_key "#{ENV['FLUENT_SHARED_KEY']}"
</security>
<server>
host aggregator01.example.internal
port 24224
</server>
<server>
host aggregator02.example.internal
port 24224
standby
</server>
<buffer>
@type file
flush_interval 5s
retry_forever true
retry_max_interval 1m
total_limit_size 4g
</buffer>
</match>
既定値から変えているのは、監査ログで欠損に直結する4点です。
@type file:memory bufferの既定はchunk_limit_size8MB・total_limit_size512MBで、プロセスが落ちると中身が消えます。file bufferはそれぞれ256MB・64GBが既定値で、再起動後に続きから送ります。retry_forever true:既定のretry_timeout72時間を無効にします。連休中に集約側が止まっても捨てません。total_limit_size 4g:既定の64GBはディスクが溢れるまで溜めかねないので、ログ領域の容量に合わせて絞ります。上限に達するとoverflow_actionの既定throw_exceptionが働き、in_tailは読み取りを止めて待ちます。require_ack_response true:既定はfalseです。trueにすると集約側からackが返るまで送信済みにしません。ackを待つ時間ack_response_timeoutの既定は190秒です。
require_ack_responseが保証する範囲は集約側のバッファまで
Fluentd 1.19.4のソースを読むと、in_forward がackを返すのは、受け取ったイベントを自分のルーティング(集約側の出力プラグインのバッファ)へ渡し終えた直後です。S3などの保管先へ書けたことは保証しません。集約側がmemory bufferのままだと、ackを返した後に集約側が落ちた時点でそのログは消えます。送信側でackを有効にしたら、集約側も必ずfile bufferにしてください。
in_forward は <transport tls> を書かない限り平文のTCPで受けます。監査ログには利用者IDや接続元IPが入るので、社内ネットワークでもTLSにします。加えて <security> の shared_key を設定すると、鍵を知らない送信元からの書き込みを拒否できます。self-signed証明書と shared_key を設定した集約側へ、鍵を変えた送信側から送ったときの実際のログが次です。
# 送信側(shared_key が一致しない)
[warn]: connection refused to localhost: authentication failed: shared_key mismatch
# 集約側
[warn]: Shared key mismatch address="::1" hostname="app01"
鍵が合う送信側からは毎秒のイベントがすべて届き、合わない送信側からは1件も届きませんでした。送信側の tls_verify_hostname は既定でtrueなので、<server> の host には証明書のSAN(Subject Alternative Name)に入れたホスト名を書きます。集約側で private_key_passphrase を省くと起動時に設定を勧める警告が出るため、秘密鍵はパスフレーズ付きにして環境変数から渡します。
リトライ上限に達した監査ログの行方|Fluentd 1.19.4での実測
監査ログの設計で最も確かめておくべきなのは、保管先が長く止まったときに何が起きるかです。送信側(in_tail→out_forward、file buffer)に5件のログを読ませ、集約側を止めたまま、次の3つの設定で挙動を比べました(Fluentd 1.19.4・gem版)。
| 設定 | 結果 | 出たログ |
|---|---|---|
| retry_max_times 3 | 5件をキューから破棄 | Hit limit for retries |
| retry_max_times 3 + secondary_file | 5件をファイルに保存 | retry succeeded by secondary |
| retry_forever true | 集約側の起動後に5件到達 | retry succeeded. |
上限付きの1行目では、3回目のリトライ失敗で次のエラーが出ました。
[warn]: failed to flush the buffer. retry_times=2 next_retry_time=2026-10-05 13:07:01 +0900 chunk="65d10031712035e5ecab0dcb12c1fa94" error_class=Errno::ECONNREFUSED
[error]: Hit limit for retries. dropping all chunks in the buffer queue. retry_times=3 records=5 error_class=Errno::ECONNREFUSED error="Connection refused - connect(2) for \"127.0.0.1\" port 24225"
[warn]: chunk files are evacuated to /tmp/fluent/buffer/object_528. chunk_id="65d10031712035e5ecab0dcb12c1fa94"
「dropping all chunks」とありますが、ファイルは消えていません。v1.19.0(2025年7月30日リリース)で入った退避機能により、file bufferのチャンクは ${root_dir}/buffer/${plugin_id}/ へ移されます。問題は退避先で、root_dir も @id も書いていなかったこの検証では /tmp/fluent/buffer/object_528 に置かれました。/tmp を起動時に消すOSでは、再起動した時点で監査ログが失われます。root_dir を永続ディスクに、@id を人が読める名前にしておけば、退避先は /var/lib/fluent/buffer/out_forward_audit/ のように特定できます。復旧は、障害が直った後に退避ファイルを元のバッファディレクトリへ戻してFluentdを再起動する手順で、公式ドキュメントのBuffer Pluginsのページに記載があります。memory bufferはこの退避に対応していません。
retry_foreverとretry_max_timesの選び方
監査ログの経路は retry_forever true を選びます。上限付きにする理由は「溜まり続けるとディスクが溢れる」ことですが、監査ログでは溢れる前に気づいて保管先を直す運用のほうが、一定時間で捨てる設計より筋が通ります。retry_forever がtrueのとき、<secondary> はリトライの切り替え先としては使われず、後述の回復不能なエラーのチャンクだけを受け取ります(起動時に only unrecoverable errors are moved to secondary と警告が出ます)。溢れ対策は total_limit_size と後述の監視で行います。
保管先の性質上どうしても上限を付けたい場合(たとえば受け手のAPIが恒久的なエラーを返し続けうる場合)は、secondary_file を付けて捨てる代わりにローカルへ書き出します。
<match audit.**>
@type forward
@id out_forward_audit
# (server・buffer は前掲と同じ。retry_forever の代わりに retry_max_times を使う構成)
<secondary>
@type secondary_file
directory /var/lib/fluent/failed
</secondary>
</match>
secondary_file はMessagePack形式で書くので、中身はFluentdに同梱の fluent-binlog-reader で確認できます。実測でも5件すべてが元のレコードのまま残っていました。
$ fluent-binlog-reader cat /var/lib/fluent/failed/dump.bin.0
2026-10-05T13:06:52+09:00 /var/lib/fluent/failed/dump.bin.0 {"ts":"2026-10-05T04:00:01Z","actor":"user1","action":"login","result":"success"}
再試行しても直らない失敗もあります。出力プラグインが TypeError・NoMethodError・MessagePack::UnpackError などの回復不能な例外を投げると、Fluentdはリトライを経ずにチャンクを <secondary> へ、それが無ければ root_dir 配下の backup ディレクトリへ移します(v1.19.4のソースの UNRECOVERABLE_ERRORS)。検証中にも送信側で例外が起き、チャンクが /tmp/fluent/backup/worker0/ 配下へ移されました。retry_forever true の経路にも secondary_file を付けておくと、この種のチャンクを決めた場所に集められます。root_dir の指定は、退避と backup の両方の行き先を決める設定です。
集約側の設定とS3 Object Lockによる改ざん防止
次は、集約側で受けたログを1時間の時間区分でまとめてS3へ書き出す設定例です。このS3設定はdry-runによる設定検証のみで、S3への実書き込みとObject Lockの動作は検証していません。プラグインは fluent-plugin-s3(2026年9月4日公開の1.8.6を確認)です。
<system>
root_dir /var/lib/fluent
</system>
<source>
@type forward
port 24224
<transport tls>
cert_path /etc/fluent/tls/aggregator01.crt
private_key_path /etc/fluent/tls/aggregator01.key
private_key_passphrase "#{ENV['FLUENT_TLS_KEY_PASSPHRASE']}"
</transport>
<security>
self_hostname aggregator01.example.internal
shared_key "#{ENV['FLUENT_SHARED_KEY']}"
</security>
</source>
<match audit.**>
@type s3
@id out_s3_audit
s3_bucket example-audit-logs
s3_region ap-northeast-1
path audit/%Y/%m/%d/
s3_object_key_format "%{path}%{time_slice}_#{Socket.gethostname}_${chunk_id}_%{index}.%{file_extension}"
store_as gzip
<format>
@type json
</format>
<buffer time>
@type file
timekey 3600
timekey_wait 10m
chunk_limit_size 64m
total_limit_size 20g
overflow_action block
retry_forever true
retry_max_interval 5m
</buffer>
</match>
<source>
@type monitor_agent
bind 127.0.0.1
port 24220
</source>
fluent-plugin-s3の timekey の既定は1日(ソースで86,400秒)なので、1時間単位で書きたいときは明示します。キーにホスト名を入れているのは、集約側を2台にしたときに同じ時間帯のオブジェクト名がぶつからないようにするためです(%{hostname} は将来削除予定と警告が出るので Socket.gethostname を使います)。#{...} の埋め込みは値全体をダブルクォートで囲んだときだけ展開され、囲まないと文字列のままキーに入ることを設定パーサーで確認しました。後述の flush_thread_count を上げると同じホストの複数スレッドが同時に書くため、チャンクごとに一意な ${chunk_id} もキーに含めています。集約側は保管先の障害をすべて受け止める位置にあるので、total_limit_size は送信側より大きく取り、overflow_action block で溢れたら受信を止めます。受信が止まると送信側はackを受け取れずに自分のバッファへ溜めるので、ログは送信側に残ります。
complianceモードとgovernanceモードの選び方
Fluentdには書いたログの改ざんを防ぐ仕組みはありません。改ざん防止は保管先のWORM(Write Once Read Many)機能で担保します。S3 Object Lockはバージョニングを有効にしたバケットでだけ使え、バケットに既定の保持期間を設定すれば、Fluentdが書いたオブジェクトに個別の指定なしでロックがかかります。
| モード | 保持期間中の削除・上書き | 向く用途 |
|---|---|---|
| compliance | rootユーザーを含め誰もできない | 規制対応の本番保管 |
| governance | s3:BypassGovernanceRetention権限があれば可能 | 保持設定の試験運用 |
本番の監査ログはcomplianceモードにします。governanceモードでは、必要な削除権限と保持期間を迂回する権限を持つ主体が、明示的に迂回を指定すると削除できます。監査対象の管理者にもこの権限を認める構成では、保持期間中の削除を防げません。complianceモードは保持期間を短くすることもできず、期限前に消す方法はAWSアカウントの削除しかないと公式ドキュメントにあります。最初はgovernanceモードで保持期間とコストを確かめ、確定してからcomplianceへ移る順番が安全です。バージョンIDを指定しない削除リクエストは成功したように見えますが、削除マーカーが付くだけでロックされた元のバージョンは残ります。
監査ログの保持期間の決め方
保持期間は準拠する基準と社内規程から逆算します。クレジットカード情報を扱うシステムなら、PCI DSSの要件10.5.1が「監査ログの履歴を少なくとも12か月保持し、直近3か月分はすぐに分析できる状態にする」ことを求めています。この要件に合わせるなら、直近3か月はOpenSearchなどの検索可能な場所に置き、S3には12か月以上のロックをかけて、検索用の側は期限で消す二層構成が一案です(要件が求めるのは期間と即時性で、保管の製品や構成は指定していません)。検索側への取り込みとインデックスの保持期間の設計はElasticsearchのログ収集|ELKスタックの収集経路とインデックス分割・保持期間の設計が参考になります。
監査ログ基盤の監視|monitor_agentで見る値とflush_thread_countの調整
retry_forever を選んだ場合、止まっていることに気づけないのが最大のリスクになります。Fluentd標準の monitor_agent を有効にすると、出力プラグインごとのバッファとリトライの状態がHTTPで取れます。後掲の集約側の設定例にある monitor_agent の source を送信側にも置き、集約側を止めた状態で送信側の値を取ったのが次です。
$ curl -s http://127.0.0.1:24220/api/plugins.json
(out_forward_audit の行を抜粋)
"buffer_queue_length": 1, "buffer_total_queued_size": 192, "retry_count": 4, "emit_records": 3, "write_count": 4, "rollback_count": 4
retry_count:書き出し失敗の累積回数で、復旧しても0には戻りません。一定時間内の増分をアラート条件にし、バッファの増加やエラーログと合わせて原因を調べます。buffer_total_queued_size:total_limit_sizeに対する比率で使用率を確認します。残り時間は、残容量を観測期間中のバッファ純増速度で割って見積もり、ディスク空き容量やチャンク数の制限も別途監視します。buffer_queue_length:送信待ちのチャンク数。保管先が正常なのに増え続けるなら、書き出しが流入に追いついていません。
monitor_agent の bind の既定は 0.0.0.0 なので、設定例のように 127.0.0.1 に絞り、外部の監視からはエージェント経由で取ります。
保管先が正常なのにキューが伸びる場合に上げるのが flush_thread_count です。既定は1で、1本のスレッドがチャンクを1つずつ書き出します。S3のように1回の書き込みに時間がかかる保管先では、2〜4に上げると並行して書き出せます。転送路の遅延が原因なら効きますが、保管先側のスロットリングで詰まっているときに上げるとエラーが増えるだけなので、retry_count が観測期間中に増えず、buffer_queue_length が増え続ける場合に、書き込み先の制限やCPU・ディスクの状態を確認して段階的に上げてください。
Fluentdで監査ログを集めないほうがよい場面
Fluentdを挟まないほうが監査ログとして強い構成もあります。
- クラウドの操作ログ:AWSの管理操作はCloudTrailが記録し、証跡をS3へ直接書きます。Fluentdで中継すると、改ざんされうる経路を自分で1段増やすことになります。設計はCloudTrailとは?証跡設計と料金実額・Lake新規受付終了後の構成をCLIで解説を参照してください。
- コンテナ環境の各ノード:送信側はメモリ消費の小さいFluent Bitにし、Fluentdは集約側だけに置く構成が一般的です。Fluent BitからFluentdへ送るときの設定の注意はFluent Bitとは?読み方・Fluentdとの違い・AWSでの設定例【v5.1対応】にまとめています。
- SIEMで相関分析までするとき:SIEM製品が専用エージェントや直接取り込みを持つなら、そちらに合わせたほうが取り込み時の正規化が効きます。SIEMの役割分担はSIEMとは?仕組みとIPS・SOC・EDRとの違いをわかりやすく解説【2026年】で整理しています。
Fluentdを使う場合も、パッケージは更新を続けてください。2026年9月29日公開のfluent-package LTS 6.0.5は、同梱のFluentdを脆弱性修正済みの1.19.4へ上げたメンテナンス版で、6.0.4以前は影響を受けると公式ブログが告知しています。v6 LTSのサポートは2027年12月までです。
よくある質問
Fluentdで監査ログが欠けるのはどんなときですか?
多いのは、memory bufferのままプロセスが再起動したときと、保管先の障害が retry_timeout の既定72時間を超えたときです。後者はv1.19.0以降ならfile bufferのチャンクが root_dir 配下へ退避されますが、root_dir 未設定だと /tmp/fluent に置かれ、再起動で消える場合があります。
require_ack_responseを有効にすると監査ログは重複しますか?
重複する場合があります。ackが届く前に接続が切れると送信側は同じチャンクを送り直すため、公式ドキュメントも障害時に2回届きうると説明しています。アプリで event_id を採番し、保管先か検索時に重複を除いてください。
flush_thread_countはいくつに設定すべきですか?
既定の1から始め、保管先が正常で、観測期間中に retry_count の増加がないのに buffer_queue_length が増え続けるときだけ2〜4へ上げます。保管先がスロットリングしている状態で増やしても書き込みエラーが増えるだけです。
監査ログは何年保存すればよいですか?
準拠する基準と社内規程で決まります。PCI DSSの要件10.5.1は12か月以上の保持と、直近3か月分をすぐ分析できる状態を求めています。法令や契約でより長い期間が定められていれば、長いほうに合わせます。
Fluentdで監査ログの解析まではできますか?
Fluentdは収集・加工・転送の役割で、検索や集計の機能は持ちません。解析はOpenSearch・Elasticsearch、SIEM、S3上のログならAmazon Athenaなど、保管先側のツールで行います。