Fluentdの設定を考えるときはこんなかんじで考えると便利

Fluentdはデータを流すのに非常に便利なツールでそこら中で使われている(個人調べ)。そのため、なんかいろんなところで設定を見るのであるが、タグに情報が付いていたりフィールドに情報がついていたりして、あれ、これどうなってるんだっけ感に襲われることがよくある。

このあたり自分でも混乱しがちなので、普段どのように考えているかだいたいまとまった気がしたところで書いておくことにした。

## Fluentdのデータ構造

まずはFluentdのデータ構造を知っておいた方が良い。Fluentdの内部データはMessagePackで符号化されているが、Fluentdのデータ構造は単なるハッシュではなく、時刻(time)とタグ(tag)という属性を持っている。次のような感じだ。

```
[time, tag, record = {key: "value", key: "value"}]
```

### レコード

レコード(record)は入力されたデータそのものであり、tailプラグインであれば、tailした1行のデータに相当する。重要なことは、時刻、タグはレコードそのものに含まれている訳ではなく、あくまでも付与されている属性のようなものと言うことだ。

実際の例を見てみよう。例えばtailプラグインでapacheログを入力する場合、次のように記述する。

```apache:例:tailによる入力
<source>
  type tail
  path /var/log/apache2/access.log
  pos_file /var/tmp/apache2-access.log.pos
  format apache2
  tag log.apache
</source>

<match log.**>
  type stdout
</match>
```

このとき、`access.log`の1行はパースされてレコードの内容に含まれる。Fluentdのログを見ると時刻はレコードの内容と別に出力され、`log.apache`というタグが付与されているはずだ。これは`format apache2`の設定がApacheのアクセスログから時刻として扱うフィールドを指定しているためだ。

### 時刻(time)

timeはレコードに付与されている時刻で主に`TimeSlicedOutput`を使用しているプラグインが出力する際に使用する。timeの付け方はInputプラグインにより異なるが、レコードを生成した時刻だったり、`tail`プラグインのようにtimeとして入力するものを指定出来たりする。`tail`プラグインの場合、正規表現やJSON、LTSV、TSVまたはCSVの場合、`time_key`属性で指定出来る。

```apache:例:jsonファイルを入力する場合にtimeとして扱うキーを指定する
<source>
  type tail
  tag app.log.event
  path /var/log/app/event.log
  pos_file /var/tmp/app.log.event.pos
  format json
  time_key created_at
</source>
```

### タグ(tag)

タグはFluentdにおいて主要な特徴の一つでデータのルーティングに使用する。それぞれのInputプラグインのスレッドは全てのレコードにタグをつけて出力する。Fluentdはmatchする`<match>`をさがし、対応するOutputプラグインのスレッドにレコードを渡す。

`<match>`にはワイルドカードも使用できるため、これによって複雑なルーティングが可能になっている。例えば、送信元ホストでタグを付けておいて、1台の集約サーバに送信し、そこで複数の出力先に振り分けることも出来る。

```apache:送信元ホスト(192.168.0.1)
<source>
  type tail
  tag log.a
  ...
</source>

<source>
  type tail
  tag log.b
  ...
</source>

# log.aもlog.bも192.168.0.2にfowardする
<match log.**> 
  type forward
  <server>
    host 192.168.0.2
  </server>
</match>
```

```apache:集約サーバ(192.168.0.2)
<source>
  type forward
</source>

# log.aとlog.bでは出力先を変える
<match log.a>
  ...
</match>
<match log.b>
  ...
</match>
```

### OutputFilter

Outputプラグインのいくつかはレコードの内容を変換し、タグを書き換えてもう一度Fluentdに渡す動作を行うものがある。これらのOutputプラグインはOutputFilterプラグインと呼ばれていることがある。[fluent-plugin-numeric-monitor](https://github.com/tagomoris/fluent-plugin-numeric-monitor)などはこのようなプラグインの1つだ。

```apache:numeric-monitorプラグイン
<source>
  type tail
  path /var/log/apache2/access.log
  pos_file /var/tmp/apache2-access.log.pos
  format apache2
  tag log.apache
</source>

# log.apacheのログを集計し、結果をmonitor.durationというタグで出力する
<match log.apache>
  type numeric_monitor
  unit minute
  tag monitor.duration
  aggregate all
  input_tag_remove_prefix log.apache
  monitor_key duration
</match>

<match monitor.**>
  type stdout
</match>
```

なお、Fluentdは近い将来、Filter機能をサポートする予定で、このFilter機能を用いればタグを変更せずにフィルター処理が可能になる。

## どんな感じで設定を書くか

基本的には次の方針で設定を書くとそれほど悩まずに出来る。

- レコードの振り分け(ルーティング)に使用する情報: **タグに付与する**
- 振り分けに使用せず、DBなどにそのまま出力する情報: **レコードに含める**

例えば、同じ`ホスト名`という情報であってもタグに付与した方がやりやすいか、レコードに含めた方がやりやすいかは違ってくる。例えば、ホスト毎にGrowthForecastの異なるグラフに出力する、ホスト毎にmongoDBの異なるコレクションに含める場合はタグにホスト名を付与しておく。逆にRDBやファイルにそのまま出力したい場合は、レコードに含めていると余計な設定をせずに済む。

### タグによる振り分け

タグによる振り分け機能を持っているOutputプラグインは多い。例えば、[fluent-plugin-growthforecast](https://github.com/tagomoris/fluent-plugin-growthforecast)や[fluent-plugin-mongo](https://github.com/fluent/fluent-plugin-mongo)は標準でタグ毎に振り分ける機能を持っているので、別々のグラフやコレクションにできる。

```
<match monitor.**>
  type growthforecast
  gfapi_url http://growthforecast.local/api/
  service service1
  tag_for section
  remove_prefix monitor
</match>

<match log.**>
  type mongo
  database fluent
  tag_mapped
</match>
```

[fluent-plugin-growthforecast](https://github.com/tagomoris/fluent-plugin-growthforecast)では`tag_for`パラメータを使用することで、タグによってセクションを振り分けたり、グラフ名のprefixに指定したりできる。上記設定では、`monitor.**`のレコードは、GrowthForecastでは別々のセクションに振り分けられて出力される。[fluent-plugin-mongo](https://github.com/fluent/fluent-plugin-mongo)では`tag_mapped`パラメータを指定することで、タグ毎に異なるコレクションに出力されるようになる。

これらはそれぞれのプラグインがそのような機能を持っているから実現できている訳で、フィールドによって振り分けられる機能を持っていればいい話でもある。しかし、Fluentdはそもそもタグのマッチングによりデータをルーティングするため、このつくりは自然なことだと思う。

### タグでの振り分けに対応していない場合

プラグインがタグによる振り分けに対応していない場合、[fluent-plugin-forest](https://github.com/tagomoris/fluent-plugin-forest)が便利だ。[fluent-plugin-forest](https://github.com/tagomoris/fluent-plugin-forest)はタグ毎にOutputプラグインのスレッドを自動的に生成してくれる。

```
<match log.*>
  type forest
  subtype file
  remove_prefix log
  <template>
    path /tmp/log/${tag}.*.log
  </template>
</match>
```

[fluent-plugin-forest](https://github.com/tagomoris/fluent-plugin-forest)では、`${tag}`(または`__TAG__`)プレースホルダを使用することで、タグ毎に異なるスレッドを動的に生成する。これによって、タグ毎に設定を書かなくても、テンプレートを書くだけで動的に設定が生成される。タグの一部分だけを使いたい場合、`${tag_parts[n]}`(または`__TAG_PARTS[n]__`)プレースホルダを使用すれば良い。詳しい説明は下記ブログが詳しい。

- [Fluentdのタグ書き換えが捗る「tag_parts」プレースホルダを使ってみよう - Y-Ken Studio](http://y-ken.hatenablog.com/entry/fluentd-how-to-use-tag_parts-placeholder)

## タグとレコードの内容を変換する

ここまで、タグとレコードのフィールドの値どちらに情報を持たせるか書いたが、実際には、タグの情報とレコードの内容を相互に変換する必要が生じる。何故かというとtailなどのプラグインで入力した場合レコードにしか情報が含まれず、動的にタグを付ける方法がないからだ。

このタグとレコード内容の相互変換を行うことが出来るプラグインが存在する。[fluent-plugin-rewrite-tag-filter](https://github.com/fluent/fluent-plugin-rewrite-tag-filter)と[fluent-plugin-record-reformer](https://github.com/sonots/fluent-plugin-record-reformer)だ。

### fluent-plugin-rewrite-tag-filter - レコードの内容によりタグを変更する

[fluent-plugin-rewrite-tag-filter](https://github.com/fluent/fluent-plugin-rewrite-tag-filter)は正規表現マッチにより、レコードの内容に応じてタグを変更することが出来る。例えば、アクセスログにおけるステータスコードに応じてログを振り分ける場合は次のように記述する。

```
<source>
  type tail
  path /var/log/apache2/access.log
  pos_file /var/tmp/apache2-access.log.pos
  format apache2
  tag log.apache
</source>

<match log.apache>
  type rewrite_tag_filter
  rewriterule1 status ^2\d\d$ log.apache.status_2XX
  rewriterule2 status ^3\d\d$ log.apache.status_3XX
  rewriterule3 status ^4\d\d$ log.apache.status_4XX
  rewriterule4 status ^5\d\d$ log.apache.status_5XX
  rewriterule5 status .+      log.apache.status_other
</match>

<match log.apache.*>
  type stdout
</match>
```

[fluent-plugin-rewrite-tag-filter](https://github.com/fluent/fluent-plugin-rewrite-tag-filter)では`rewriterule#{n}`というパラメータで正規表現と変換後のタグを指定する。`rewriterule#{n}`パラメータは数字が被らないように連番にする必要がある。

変換後のタグにプレースホルダを使用することも出来る。例えば、すべてのステータスコード毎にタグを振り分ける場合は次の設定で良い。

```
<match log.apache>
  type rewrite_tag_filter
  rewriterule1 status ^(\d+)$ log.apache.status_$1
  rewriterule2 status .+      log.apache.status_other
</match>
```

この場合、ステータスコードの数分タグが振り分けられるので、大量にグラフが出来ないように気をつける必要がある。

### fluent-plugin-record-reformer - タグの情報をレコードの内容に変換する

タグの情報をレコードに含める等、レコードを書き換える操作を行うには、[fluent-plugin-record-reformer](https://github.com/sonots/fluent-plugin-record-reformer)が便利だ。例えば、タグの一番最後の部分をレコードに含める場合は次のようになる。

```
<source>
  type tail
  path /var/log/apache2/access.log
  pos_file /var/tmp/apache2-access.log.pos
  format apache2
  tag log.apache
</source>

<match log.*>
  type record_reformer
  tag log
  <record>
    type ${tag_parts[-1]}
  </record>
</match>

<match log>
  type stdout
</match>
```

[fluent-plugin-record-reformer](https://github.com/sonots/fluent-plugin-record-reformer)は他にも様々な書き換えに使えて便利だ。

## まとめ

Fluentdは、タグと時刻によって複雑なルーティングを可能にしている。タグによる振り分けをサポートしているOutputプラグインは多いし、[fluent-plugin-forest](https://github.com/tagomoris/fluent-plugin-forest)を用いればすべてのOutputプラグインをタグによる振り分けに対応させることが出来る。

レコードの時刻は特別な理由がない限り、レコードのフィールドにするのではなく、レコードのtimeとして扱われるようにInputプラグインの設定に気をつけよう。fileプラグインの他、Elasticsearchに出力する[fluent-plugin-elasticsearch](https://github.com/uken/fluent-plugin-elasticsearch)もLogstashフォーマットの`@timestamp`フィールドにtimeを使用する。正しいtimeが指定されていない場合、ログの発生時刻とKibanaで扱う時刻がずれてしまって面倒だ。

逆にTimeSlicedOutputを利用するfileプラグインなどは、レコードが遅延して配送された場合、古いfileにも出力されたり、到着順にならなかったりすることに気をつけよう。

タグと時刻の扱いは面倒なところではあるが、うまく使いこなすことでFluentdでできることがぐんと広がる。ここに書いたことは自分なりのやりかたなので、他にもいろいろなやり方があるだろう。タグの設定方法とかはノウハウ次第なので、いろんなノウハウがWeb上に公開されるとうれしい。

ログ収集とデータ解析についてはサーバ/インフラエンジニア養成読本 ログ収集~可視化編の特集2 ログ収集ミドルウェアFluentd徹底攻略もおすすめだ。逆引きプラグインなどまとまっていて非常に便利。Amazonでは品切れになっていることもあるので、是非お近くの書店で買おう(宣伝)。

- [サーバ/インフラエンジニア養成読本 ログ収集~可視化編 [現場主導のデータ分析環境を構築!]] (http://www.amazon.co.jp/dp/4774169838/)