読者です 読者をやめる 読者になる 読者になる

fluentdで複数箇所から同一のファイルに出力する

このエントリは「ウィークリーFluentdユースケースエントリリレー」への参加記事です。

ウィークリーFluentdユースケースエントリリレーまとめ(現在12本まで。) - iをgに変えるとorangeになることに気づいたoranieの日記

fluentd で一番手軽な出力 plugin といえば out_file ですね。path を指定するだけでファイルに出力できますが、使い方に気をつけないと落とし穴があるよ、という話です。

簡単な使いかた

一番単純な使いかたはこのような記述で、

<match app.**>
  type file
  path /path/to/logs/app.log
</match>

これで /path/to/logs/app.log にJSON形式で出力されます。

2012-10-27T17:55:00+09:00	app.info	{"message":"info message"}
2012-10-27T17:55:00+09:00	app.error	{"message":"error message"}

out_file の仕様については tnmt さんが詳しく解説されていますので、そちらをご参照ください。fluentdのout_fileプラグインの仕様について - hack in 3 minites

よくあるユースケース

ところでよくあるユースケースとして、特定のレベルのみ (error, warn, info, debug のうち error のみとか)、特別な処理をしたいことがありますよね。

  • app.error は out_mail でメール送信
  • それ以外の app.** は、「app.errorも含めて」ファイルに出力

という処理をしたいとして、以下のように素直に記述してみるとどうなるか。

<match app.error>
  type    mail
  host    localhost
  from    app@example.com
  to      app-error@example.com
  subject app error!
  outkeys message
</match>
<match app.**>
  type file
  path /path/to/logs/app.log
</match>

この記述では、app.error のタグにマッチしたログが out_mail に送られますが、後ろの app.** には到達しないため、ファイルには app.error は記録されません。残念。

複数箇所で同一 path を指定すると…

同一のログに対して複数回処理したい場合の定番パターンとして、copy plugin を使う方法があります。ではこのように書いてみます。

<match app.error>
  type copy
  <store>
    type file
    path /path/to/logs/app.log
  </store>
  <store>
    type    mail
    host    localhost
    from    app@example.com
    to      app-error@example.com
    subject app error!
    outkeys message
  </store>
</match>
<match app.**>
  type file
  path /path/to/logs/app.log
</match>

app.error は copy で複製され、ファイルへの出力と mail 送信が行われるのを意図した記述です。が、これで出力ディレクトリを見てみると…

$ ls -l
-rw-rw-r-- 1 fujiwara fujiwara 12586 Oct 27 18:09 app.log.20121027.b4cd06c6c03a45069
-rw-rw-r-- 1 fujiwara fujiwara 12992 Oct 27 18:09 app.log.20121027.b4cd06c6c03be61ae

なぜか2ファイル生成されています。out_file は記述ごとにファイル出力を行うため、同一の path を指定しても別ファイルとして出力されるのです。これでは、app.error とそれ以外の内容が別ファイルに分かれてしまいます。

また、ファイル末尾の .b4cd06c6... の部分はローテートされるときに .0 .1 .2 のように rename されるのですが、同一のファイル名での出力記述がある場合、ごく稀に以下のようなエラーが発生することがあります。

error on output thread error="No such file or directory
- (/path/to/logs/app.log.20121027.b4cce432670682745,
/path/to/logs/app.log.20121027.b4cce432670682745)"

『out_file で 同一の path を複数箇所で指定することはできない』

一見普通にできそうなのですが問題が発生するパターンなので、覚えておきましょう。

過去のバージョンでの経験で現在の最新版では再現できなかったのですが、同一pathを指定していた場合のローテート処理のエラーで、fluentd が処理を停止してしまった経験もあります。

ではどうすれば?

標準のプラグインではありませんが、fluent-plugin-rewrite を使用してタグを書き換えてやることで可能になります。

fluent-plugin-rewriteというプラグインを作成した #fluentd - delirious thoughts

<match app.error>
  type copy
  <store>
    type       rewrite
    add_prefix filtered
  </store>
  <store>
    type mail
    # 略
  </store>
</match>
<match app.**>
  type       rewrite
  add_prefix filtered
</match>
<match filtered.app.**>
  type file
  path /path/to/logs/app.log
</match>
  • app.error は copy で複製され、
    • rewrite によって filtered.app.error にタグが書き換えられる
    • mail によってメール送信
  • それ以外の app.** は rewrite によって filtered.app.error にタグが書き換えられる
  • filtered.app.** をファイルに出力

という流れになります。

まとめ

『out_file で 同一の path を複数箇所で指定することはできません』

同一ファイルに複数箇所からの記録を行いたい場合は、rewrite plugin などを利用してタグを書き換えましょう。別のもっと簡単な方法をご存じのかたは、是非教えていただければと思います!