fluent-plugin-rewriteというプラグインを作成した #fluentd
fluent-plugin-rewriteというfluentdのプラグインを作成した。以下、このプラグインの解決する問題について述べる。
問題
あるサービスのレスポンスタイム改善をしていて、まずは状況の可視化のために、fluentdを用いることにした。その際、たとえば
- トップページ
- ユーザページ
- 書籍検索ページ
- 書籍詳細ページ
- ...
- その他
といったグループにわけて、レスポンスタイムの各種統計を取りたい。また、ログを全部集計すればいいというものではなく、除外すべきmessageも複数種類あるので、柔軟にフィルタルールを設定したい。
既存のプラグインだとout_exec_filterを使うことによって達成可能。ただし、以下のような問題がある。
パフォーマンス的に不安- システムを構成するファイルがひとつ増える(filter用のスクリプト)
gem install
するべきプラグインがひとつ増える(+1)より、異なる名前であり得るスクリプトが増える(+n)方が、システムの複雑性は増大する
- 同じような処理を横展開していくにあたって、何度も似たようなスクリプトを書くことになる。設定でできるなら、その方がDRYでよい。
「問題」に対する追記
「パフォーマンス的に不安」と書いたが、実際に大規模環境で試したわけではなく、この点についてはFUDであった。out_exec_filterには、大規模環境下での、以下のような実績があることを教えてもらった。
exec_filterみんな使ってないみたいだけど、自分はバリバリ使ってます。 たとえば『○バゲーのアクセスログにUIDを追加&静的ページを除外するexec_filter』 gist.github.com/af5265fb07295d…
— 池田朋大 (@mikeda) July 1, 2012
秒間2000メッセージぐらいをout_exec_filterかましてたけど気にするような負荷はなかった記憶
— fujiwara (@fujiwara) July 1, 2012
ただし、先に挙げた他2つの問題についてはout_exec_filterでは解決されない。
解決
そこで、fluent-plugin-rewriteを作成した。このプラグインを用いると、ひとつひとつのmessageについて、指定したkeyのvalueが特定のパタンにマッチしたら、
- valueを書き換えて、再emitする
- そのmessageを無視する
- マッチした文字列をtagに追加して、再emitする
ということができる。また、それらのルールを複数指定することも可能。イメージ的にApacheのmod_rewriteみたいな感じなので、fluent-plugin-rewriteという名前にした。
具体例を以下に述べる。
前段
まず、fluent-agent-liteにより、こういうメッセージが投げられてくるとする。
raw.apache.access {"message":"example.com xxx.xxx.xxx.xxx - - [29/Jun/2012:04:02:06 +0900] \"GET /book/38790 HTTP/1.0\" 200 1643 13043 \"\" \"Mozilla/5.0 (Windows NT 5.1; rv:13.0) Gecko/20100101 Firefox/13.0.1\" 161576"}
それを、中間fluentdで受けて、fluent-plugin-parserでバラす。
<source> type forward </source> <match raw.apache.access> type parser remove_prefix raw format /^[^ ]+ [^ ]+ [^ ]+ [^ ]+ \[(?<time>[^\]]+)\] "(?<method>[^ ]+) (?<path>[^ ]+) [^"]+" (?<status>[^ ]+) [^ ]+ [^ ]+ "[^"]+" "[^"]+" (?<response_time>[^ ]+)$/ time_format %d/%b/%Y:%H:%M:%S %z key_name message </match>
これにより、messageがたとえば以下のように整理される。
apache.access: {"method":"GET","path":"/book/38790","status":"200","response_time":"161576"}
fluent-plugin-rewrite
いったんmessageがkey/valueの形になったら、fluent-plugin-rewriteを適用できる。Webアプリのレスポンス改善をしたいのであるから、
- image/css/jsなどの静的ファイルを除外する
必要がある。また、今回は参照されるページの改善をしたいので、あわせて
- ステータスコードが200以外のものを除外する
- リクエストメソッドがGET以外のものを除外する
といったフィルタリングをする必要がある。また、URLをグルーピングして可視化したいのであるから、
- URLを、数種類にグルーピングし、グルーピングから外れたものはothersとしてまとめる
- 上でグルーピングした名前で新たにタグをふりなおして、再emitする
ということもしたい。以下、設定。
<match apache.access> type rewrite remove_prefix apache.access add_prefix filtered <rule> key path pattern ^\/(?:image|css|js) ignore true </rule> <rule> key status pattern ^(?!200)\\d+$ ignore true </rule> <rule> key method pattern ^(?!GET).+$ ignore true </rule> <rule> key path pattern ^/$ replace /top </rule> <rule> key path pattern ^/(top|books|book|admin|users|api|authors|cart) append_to_tag true fallback others </rule> </match>
このように設定しておくと、除外すべきものが除外され、URLごとにグルーピングされたmessageを得られる(以下はbookグループの例)。また、グルーピング対象にならなかったものはothersというグループに分類される。
filtered.book: {"method":"GET","path":"/book/38790","status":"200","response_time":"161576"}
これで問題に挙げていた事柄が解決できる。fluent-plugin-rewriteの説明は以上である。使い方の詳細についてはドキュメントを参照のこと。
集計・グラフ化
集計には、たとえば
のふたつのプラグインを使う。以下、設定。
<match filtered.**> type forest subtype numeric_monitor remove_prefix filtered <template> unit minute tag response_time.__TAG__ aggregate all monitor_key response_time percentiles 90,95 </template> </match>
すると、fluent-plugin-rewriteでグルーピングしたURLの分だけ、以下のような数値を得られる。
response_time.book: {"min":24150.0,"max":41587529.0,"avg":1481909,"percentile_90":98058.0,"percentile_95":614745.0} response_time.users ... response_time.admin ...
あとはこれをfluent-plugin-growthforecastでGrowthForecastに投げれば、いい感じにグラフが作成されるでしょう。
<match response_time.**> type forest subtype growthforecast <template> gfapi_url http://localhost:5125/api/ service a_service name_keys avg,max,min,percentile_90,percentile_95 tag_for section remove_prefix response_time </template> </match>
まとめ
- fluent-plugin-rewrite、けっこう便利な気がする
- ただし、これはこれでパフォーマンスの点で気掛りではある。
- つか、tagomorisさんにお世話になりっぱなし