#whoami
Fluentd Advent Calendar三日目は、Fluentdコミッタの@kiyototamuraがお届けします。トレジャーデータという会社で、日本国外でのFluentdの啓蒙活動を主にやっております。おそらく日本ではもっとも知名度の低いコミッタです。
追記:@repeatedly先生のフィードバックを受けて、コードがシンプルになりました。
2014/01/07 更新: v0.10.58、v0.12.0以降で新APIが生えた[1][2]のでそちらを使うようにサンプルコードを更新
#で、パーサプラグインって何よ
一言で言うと、インプットプラグインのformat
パラメータを拡張できる機能です。インプットに入ってくるデータは必ずしも構造化されていないため、ログをJSONとして持つ設計のFluentdでは、インプットプラグインがパースをする必要があります。この役割を担うのがパーサプラグインです。プラグイン化されたのはv0.10.46ですが、内部実装的には、最初から存在していて、例えばtailプラグインで
<source>
type tail
format json
path /path/to/file
tag aoi.yu
</source>
と書くとJSONパーサが走りますし、tcpプラグインで
<source>
type tcp
format none
tag akai.yu
</source>
と書くとNoneパーサ(まったくパースせず、一行そのまま"message"というキーにぶち込むパーサです)が走ります。
これらの実装に関しては/lib/fluent/parser.rb
を読むと幸せになれるかもしれません。
ちなみにデフォルトでは以下のパーサが入っています:
-
format apache_error
: Apacheエラーログパーサ -
format apache2
: Apache2アクセスログパーサ。apache
ってのもあるが違いがわからず… -
format syslog
: いわゆるsyslogをよしなにパース。 -
format json
: インプットをそのままJSONとみなしてパース。 -
format tsv
: いわゆるtsv。keysを指定することでJSONに変換。 -
format ltsv
: みんな大好きltsv。 -
format csv
: みんな大嫌いcsv。やはりkeysを指定。 -
format nginx
: nginxのアクセスログをパース。 -
format none
: 何もパースしないでインプットをそのままmessageフィールドにぶっこむ。message_key
パラメータで他のフィールドに入れられる。 -
format multiline
: 複数行のログをパースするのに便利。(追記:in_tail専用です)
そしてもちろんformat /^(?<foo>\w+)$/
みたいに名前キャプチャを使った正規表現を書くことで、キャプチャの名前がキーとなったJSONが吐き出されます。これは正規表現パーサと呼ばれ、apache_errorパーサやnginxパーサはこれで実装されています。
これだけデフォルトで入っていると、自分でパーサを書く必要なぞないように思えます。必要ならFluentularを使ってコツコツと正規表現を作ればいいし。
#しかし世の中甘くない
ところがどっこい、デフォルトのパーサではパースできないフォーマットもあります。例えばこんなやつ:
key1=val1|key2=val2|key3=val3...
これそのものは正規文法で表現できるのですが(=正規表現でマッチできる)、keyの数が任意数あるため、名前キャプチャを使った正規表現では書けません。
また、世の中には正規文法ではないフォーマットも存在しないとは限りません。そうなると、デフォのパーサ群ではお手上げ、ということになります。
#パーサプラグインの書き方
ということで、パーサプラグインの登場です。パーサプラグインの実装方法は以下の通りです:
-
parser_
で始まるファイル名で、プラグインファイルを作る。 -
以下のひな形を作る。ちなみにこのパーサーは、何もパースせずにデータを葬りさってくれるので、DevNullParserと名付けてあります。
module Fluent class TextParser #パーサープラグインの名前 class DevNullParser < Parser # このプラグインをパーサプラグインとして登録する Plugin.register_parser('dev_null', self) def initialize super # いろいろとここで初期化 end def configure(conf={}) super # 設定ファイルによるものはここで end def parse(text) # これが肝となるメソッドで、textをパースする。 # parser.parse(text) {|time, record| ... } みたいに使う。 yield Engine.now, {} end end end end
-
Fluentdのpluginディレクトリの中に
parser_dev_null.rb
(と名付けました)を配置。 -
format
パラメータのあるinputプラグインで使う。例えばこんな風に<source> type tcp format dev_null port 13337 tag nothing </source> <match nothing> type stdout </match>
実際に使ってみましょう。私の場合はこんな感じです。
vagrant@precise64:~$ cat t.conf
<source>
type tcp
format dev_null
port 13337
tag nothing
</source>
<match nothing>
type stdout
</match>
vagrant@precise64:~$ ls my_plugins/
parser_dev_null.rb
vagrant@precise64:~$ fluentd -c t.conf -p my_plugins/
2014-12-01 02:46:36 +0000 [info]: starting fluentd-0.10.57
2014-12-01 02:46:36 +0000 [info]: reading config file path="t.conf"
2014-12-01 02:46:37 +0000 [info]: gem 'fluent-plugin-record-reformer' version '0.4.0'
2014-12-01 02:46:37 +0000 [info]: gem 'fluentd' version '0.10.57'
2014-12-01 02:46:37 +0000 [info]: using configuration file: <ROOT>
<source>
type tcp
format dev_null
port 13337
tag nothing
</source>
<match nothing>
type stdout
</match>
</ROOT>
2014-12-01 02:46:37 +0000 [info]: adding source type="tcp"
2014-12-01 02:46:37 +0000 [info]: adding match pattern="nothing" type="stdout"
そして実際にデータを送ってみると…
vagrant@precise64:~$ echo 'this is a loooooooooooooooooooooooooooooooooooong message' | nc localhost 13337
な、なんと!
2014-12-01 02:46:43 +0000 nothing: {}
きちんと全く何もパースせず空のイベントを吐き出してきました。これぞdevnullismの鑑。ちなみにもっとまともな例は、こちらの記事をご参考に。
#こんなメタなこともできるよ:Parse Me Maybe
inputプラグインに流れ込んでくる文字列をパースするためにあるパーサプラグインですが、メタな挙動をさせることもできます。その一例として、MaybeParserを実装してみたいと思います。
いわゆる非構造テキストログをFluentdで集める際ににありがちな話として、
- よしなに正規表現を書いて各行をパースしたつもりが
- あとでログ見て、予想だにしない行が結構あってFluentdがエラー投げてた
なんてことがあります。エラー処理をきちんとしていないとログの取りこぼしなどもしてしまうことも。
MaybeParserはこれを解決します。挙動としては、
- 既存パーサでパースできなかったら
- データをパースせずに
format none
と同じ挙動で吐き出す。
というものです。
では実装を見てみましょう。MaybeParserという名前は、某モナドの影響を受けています。
module Fluent
class TextParser
#パーサープラグインの名前
class MaybeParser < Parser
Plugin.register_parser('maybe', self)
config_param :parser, :string
# フォールバックするNoneParserでのキーの名前。
config_param :fallback_message_field_key, :string, :default => nil
def initialize
super
# いろいろとここで初期化
end
def configure(conf={})
super
conf['format'] = conf.delete('parser') # parserフィールドがformatの値なので、書き換えてパーサを初期化
@parser = TextParser.new
@parser.configure(conf)
@fallback_parser = NoneParser.new
@fallback_parser.configure("message_key" => @fallback_message_field_key)
end
def parse(text, &block)
# パースに失敗したらfallback_parserを呼ぶだけ
@parser.parse(text) do |time, record|
if time.nil? or record.nil?
@fallback_parser.call(text, &block)
else
yield time, record
end
end
end
end
end
end
さて、これを実行するとこんな感じになります。
vagrant@precise64:~$ cat t.conf
<source>
type tcp
format maybe
parser json
port 13337
tag something
</source>
<match something>
type stdout
</match>
vagrant@precise64:~$ ls my_plugins/
parser_devnull.rb parser_maybe.rb
vagrant@precise64:~$ fluentd -c t.conf -p my_plugins/
2014-12-01 03:30:59 +0000 [info]: starting fluentd-0.10.57
2014-12-01 03:30:59 +0000 [info]: reading config file path="t.conf"
2014-12-01 03:30:59 +0000 [info]: gem 'fluent-plugin-record-reformer' version '0.4.0'
2014-12-01 03:30:59 +0000 [info]: gem 'fluentd' version '0.10.57'
2014-12-01 03:30:59 +0000 [info]: using configuration file: <ROOT>
<source>
type tcp
format maybe
parser json
port 13337
tag something
</source>
<match something>
type stdout
</match>
</ROOT>
2014-12-01 03:30:59 +0000 [info]: adding source type="tcp"
2014-12-01 03:30:59 +0000 [info]: adding match pattern="something" type="stdout"
まず普通のJSONを送ると…
vagrant@precise64:~$ echo '{"hello":"world"}' | nc localhost 13337
以下の様に、JSONParserで、ちゃんとパースされました。
2014-12-01 03:31:44 +0000 something: {"hello":"world"}
今度はJSONじゃないものを送ると…
vagrant@precise64:~$ echo 'this is not JSON' | nc localhost 13337
NoneParserで処理されました!
2014-12-01 03:33:49 +0000 something: {"message":"this is not JSON"}
ここでは実装のシンプルさからNoneParserをデフォルトとしましたが、もう少しコードを書けば、任意のパーサにフォールバック…みたいなこともできます。
#最後に宣伝
今年は海外でもFluentdの認知度があがり、Googleさんの各種クラウドサービスにも導入され、それ相応に流行ってきたかなという感じです。2015年はさらにFluentdをデファクトスタンダードにしていきたいと考えていますので、世界中を飛び回ってFluentdを流行らせる仕事をやってみたい方、Fluentdと関連プロジェクトをどっぷり開発したいという方は、一言ご連絡ください。