Fluentd v0.12 ラベル機能の使い方とプラグインの改修方法

Fluentd v0.12 には [Fluentd v0.12 is Released](http://www.fluentd.org/blog/fluentd-v0.12-is-released) で言及されているように、「ラベル」という機能が追加されています。本記事ではラベル機能の使い方、またラベル機能に対応するためにプラグインを改修する方法について解説します。

## 概説

この**ラベル**という機能は、「内部ルーティングするのに add_tag_prefix したり remove_tag_prefix したり、タグ設計するのめんどくさい！！」という声にお答えして追加されたものです。
ラベル機能を使う事によって、タグを改変することなく、内部ルーティングすることができるようになります。

ラベル機能の導入によって、以下のような利点が生まれていると思います。

1. タグをいじることが(ほとんど)なくなったので、オリジナルのタグ情報を保てるようになった。
2. Fluentd 本体がサポートする機能なので、プラグイン作者が個別に add_tag_prefix, remove_tag_prefix といったオプションをサポートする必要が(ほとんど)なくなった。

また、ラベルという概念が導入されたことによって、

* 「タグ」はデータソースの識別子
* 「ラベル」は内部ルーティングの識別子

と役割を論理的に分離させることができるようになりました。
旧来のタグは、データソースの識別子、内部ルーティングの識別子の２つの役割を担っていたため、タグ設計がややこしかったと思います。
論理的な役割を分離させることでタグ設計が簡単になることを狙っています。

## 旧来の内部ルーティング

比較のために、旧来のタグを使った内部ルーティングについて考えます。

例えば、入力されたメッセージ全てに [flowcounter](https://github.com/tagomoris/fluent-plugin-flowcounter) の適用を追加するようなケースを考えてみます。
次のような conf になるでしょうか。

入力メッセージのタグが全て `raw.` で始まるようにデータ送信側を調整する必要があります。また、タグ改変のためだけに [fluent-plugin-rewrite](https://github.com/kentaro/fluent-plugin-rewrite) をインストールして使用しています。

```apache
<source>
  type forward
</source>

<match raw.**>
  type copy
  <store>
    type flowcounter
    count_keys *
    unit second
    tag flowcounter
  </store>
  <store>
    type rewrite
    remove_prefix raw
    add_prefix rewrite
  </store>
</match>

<match flowcounter>
  type stdout # flowcounter の結果
</match>

<match rewrite.**>
  type stdout # 通常処理
</match>
```

## ラベルを使った内部ルーティング

ラベル機能を使って conf を書き換えるには次のようにします。

1. `@label @ラベル名` でラベルを貼る。
2. 新設された label ディレクティブで、一致したラベルの処理を行う。`<label @ラベル名></label>` のように書く。

このサンプルでは入力メッセージに対して `@raw` ラベルを貼って処理しています。また、ラベルの改変には Fluentd v0.12 組み込みの relabel プラグインを使用しています。

```apache
<source>
  type forward
  @label @raw
</source>

<label @raw>
  <match **>
    type copy
    <store>
      type flowcounter
      count_keys *
      unit second
      @label @flowcounter
    </store>
    <store>
      type relabel
      @label @rewrite
    </store>
  </match>
</label>

<label @flowcounter>
  <match **>
    type stdout # flowcounter の結果
  </match>
</label>

<label @rewrite>
  <match **>
    type stdout # 通常処理
  </match>
</label>
```

ポイントは１度もタグを書き換えていない点、データ送信側で(raw. で始まるように)タグを調整する必要がなくなった点ですね。

また、

```apache
<source>
  type forward
  @label @raw
</source>
```

のように conf 中でルーティングの識別子が明確に定義されているため、conf を読むだけで、データがどのルートを通るのかわかるようになりました。今までは、送信側がどういうタグ名でデータを送ってくるのか把握しないと、どのルートを通るのか識別できませんでした。

外部要因を排除して、内部で定義したラベル情報だけで内部ルーティングが完結しています。

補足：`<match **>` を端折りたいという意見もありそうですが、別のタグでも同じラベルに振る事ができるような設計にしていて、そのときに区別を付けられるような構文になっています。


## ラベル機能をサポートするためのプラグインの改修

前章で「ラベルを使った内部ルーティング」について conf のサンプルを書きましたが、実は 1 つ嘘がありまして、flowcounter プラグインは `@label` にまだ対応していません(2014/12/16)。

プラグインをラベル機能に対応させるには、Fluentd v0.12から 増えた API である、`router.emit` を使用する必要があります。今まで `Engine.emit` と書いていた所を，`router.emit` に書き換えます。

v0.10 もサポートしつつ、v0.12 ではこの新しい `router.emit` を使用するようにするには、次のようにプラグインに改修を加えると良いでしょう。※ 実際にはこのパッチをあてた flowcounter プラグインで動作確認をしました。

```diff
diff --git a/lib/fluent/plugin/out_flowcounter.rb b/lib/fluent/plugin/out_flowcounter.rb
index fb416ff..c74773a 100644
--- a/lib/fluent/plugin/out_flowcounter.rb
+++ b/lib/fluent/plugin/out_flowcounter.rb
@@ -8,6 +8,11 @@ class Fluent::FlowCounterOutput < Fluent::Output
     define_method("log") { $log }
   end

+  # Define `router` method of v0.12 to support v0.10 or earlier
+  unless method_defined?(:router)
+    define_method("router") { Fluent::Engine }
+  end
+
   config_param :unit, :string, :default => 'minute'
   config_param :aggregate, :string, :default => 'tag'
   config_param :output_style, :string, :default => 'joined'
@@ -132,10 +137,10 @@ class Fluent::FlowCounterOutput < Fluent::Output
   def flush_emit(step)
     if @output_style == :tagged
       tagged_flush(step).each do |data|
-        Fluent::Engine.emit(@tag, Fluent::Engine.now, data)
+        router.emit(@tag, Fluent::Engine.now, data)
       end
     else
-      Fluent::Engine.emit(@tag, Fluent::Engine.now, flush(step))
+      router.emit(@tag, Fluent::Engine.now, flush(step))
     end
   end
```

追記: ラベルがあればタグ指定が必須ではなくなるプラグインの例として fluent-plugin-grep を改修したコードがこちらにあります。参考にしてみてください => https://github.com/sonots/fluent-plugin-grep/pull/8/files

<hr />

補足: v0.10.58 には v0.12 とのAPI互換性のために `router` メソッドが生えています。v0.10.58 以上しかサポートしない、または v0.12 しかサポートしない、と割り切る場合は、以下の変更だけですみます。

```diff
- Engine.emit
+ router.emit
```

## MultiOutput プラグインの改修

`out_copy`、`config_expander` のように、内部で(複数の) Output プラグインを作成して利用するプラグインのことを MultiOutput プラグインと呼びます。呼ぶことにします。

こちらのプラグイン達です => https://github.com/search?p=1&q=Plugin.new_output+language%3Aruby&ref=searchresults&type=Code&utf8=%E2%9C%93

MultiOutput プラグインで新しいルーティングをサポートするために改修が必要です。Fluentd v0.12 から増えた API である `Output#router=` を使用して、自身の持つ `router` インスタンスを子プラグインに渡してあげる必要があります。

v0.10 もサポートしつつ対応するには次のようにプラグインに改修を加えると良いでしょう。

```diff
         output = Plugin.new_output(type)
+        output.router = router if respond_to?(:router) and output.respond_to?(:router=)
         output.configure(e)
```

cf. [MultiOutput plugins need to be fixed for v0.12 - gist](https://gist.github.com/sonots/ae23796829b4e392fdce)

<hr />

補足: v0.10.58 には v0.12 とのAPI互換性のために `Output#router=` メソッドが生えています。v0.10.58 以上しかサポートしない、または v0.12 しかサポートしない、と割り切る場合は `if` を削って以下の変更だけですみます。

```diff
         output = Plugin.new_output(type)
+        output.router = router
         output.configure(e)
```

## おわりに

Fluentd v0.12 で導入されたラベル機能について、またラベル機能をサポートするようにプラグインを改修する方法について解説しました。

個々のプラグインでラベル機能を使えるようになると大変捗ると思いますので、Input、 Filter系 Output、MultiOutput プラグイン制作者の皆様につきましては、お手数おかけしますがラベル機能対応をよろしくお願い申し上げます :bow:

自分のもやらないと :sweat_drops:

**参考文献**

1. [Fluentd v0.12でのFilterとLabel - Go ahead!](http://repeatedly.github.io/ja/2014/08/fluentd-filter-and-label/)
2. [Fluentd v0.12 is Released | Fluentd](http://www.fluentd.org/blog/fluentd-v0.12-is-released)