Fluent Bitでアクセスログをステータスコードやレスポンスタイムでフィルタリングする

こんにちは id:cohalz です。はてなブックマークのチームではWebアプリケーションのアクセスログを取るためにnginxを入れてCloudWatch Logsに送り、障害時などにCloudWatch Logs Insightsを使って分析を行うというフローが主流となっています。

しかし全リクエストのアクセスログを送るにはCloudWatch Logsの費用は高く、特に安定していてリクエストが多いようなサービスに関しては費用対効果を考えCloudWatch Logsに流すこと自体をやめてしまうこともありました。

そういった状況を解決するために、CloudWatch Logsにはエラーや時間の掛かっているリクエストのみが送られるような仕組みを導入し、費用を抑えつつ障害対応に必要なデータを集められるようにしました。

Fluent BitのLuaプラグインを使う

はてなではログのフィルタリングにFluent Bitを利用することが多いです。

Fluent Bitでログのフィルタリングする場合はgrepやrewrite_tagを使いますが、これは正規表現でマッチする関係上特定の閾値を超えたものだけといったルールを作ることが非常に難しいです。

そんな場合に使えるのがLuaプラグインで、数値の比較を始めとした細かいフィルタリングやレコードの追加の仕組みをLuaを使って実現できるようになります。

docs.fluentbit.io

Fluent BitのLuaプラグインの使い方に関しては以下の記事が参考になりました。

nokute.hatenablog.com

このLuaプラグインを使うことでステータスコードやレスポンスタイムを数値で比較して、マッチするログだけにフィルタリングすることができます。 下の例はレスポンスタイムが0.5秒以上もしくはステータスコードが500以上のログだけを残すLuaスクリプトです。

function accesslog_filter(tag, timestamp, record)
  if record['reqtime'] >= 0.5 or record['status'] >= 500 then
    return 0, timestamp, record
  else
    return -1, timestamp, record -- ここにマッチしたログは捨てられる
  end
end

このLuaスクリプトをFluent Bitから読み込むことで利用できます。

[FILTER]
  Name   lua
  Match  *-firelens-*
  script /fluent-bit/etc/accesslog_filter.lua
  call   accesslog_filter

閾値を環境変数で変えられるようにする

この閾値を変えるためには設定ファイルを変える必要があるという問題があります。

閾値を変えたくなった時に毎回ビルドするのでは手間ですし、複数のアプリケーションから利用するといったことがしづらいです。

これを解決する方法として、環境変数を使って変更できるようにならないか調べた結果、ワンライナーでLuaのコードを書くことで可能ということがわかりました。

[FILTER]
  Name   lua
  Match  nginx_access_log
  code function accesslog_filter(tag, timestamp, record) if record["reqtime"] >= ${REQTIME_THRESHOLD} or record["status"] >= ${STATUS_THRESHOLD} then return 0, timestamp, record else return -1, timestamp, record end end
  call   accesslog_filter

上の例では REQTIME_THRESHOLD および STATUS_THRESHOLD を環境変数として指定することで閾値を簡単に変更できるようになっています。

フィルタリングをやめたい場合もREQTIME_THRESHOLD に0や STATUS_THRESHOLD に100など指定することで全てのログを流せるようにできるようになります。

この方法は、Fluent Bitの設定ファイルに環境変数の埋め込みができる機能を利用しています。

docs.fluentbit.io

ワンライナーをやめて見やすい形でLuaのスクリプトに環境変数を埋め込むと言ったことはおそらくできません。

あくまで環境変数を埋め込めるのはFluent Bitの設定ファイルだけであってLua側に環境変数を埋め込むことができませんし、Luaスクリプトの引数の形式も決まっているため追加でパラメータを渡すといったこともできないという理由になっています。

とはいえFluent Bit v2ではYAML形式での設定ファイルが使えるようになるので今後はYAML形式で書くことで埋め込みのコードも複数行で書けるようになるかもしれません。

公式のYAML設定の例にもそういったLuaスクリプトを埋め込む例が存在しています。

github.com

ただし現時点ではaws-for-fluent-bitはv1を利用しているのでYAML設定は使えないことに注意してください。

最後に

検証用にこのNginxとFluent Bitの動作確認をするサンプルのアプリケーションを用意しました。

github.com

docker compose up して curl 127.0.0.1:8080/500 などすると下のように特定のステータスコード以上のログだけfluent-bit-nginx-filter-example-fluent-bit-1に送られるというのが確認できます。

fluent-bit-nginx-filter-example-nginx-1       | {"time":"19/Oct/2023:03:04:07 +0000","uri":"/500","status":500,"reqtime":0.000}
fluent-bit-nginx-filter-example-nginx-1       | {"time":"19/Oct/2023:03:04:10 +0000","uri":"/501","status":501,"reqtime":0.000}
fluent-bit-nginx-filter-example-fluent-bit-1  | [0] nginx_access_log: [[1697684650.000000000, {}], {"uri"=>"/501", "status"=>501, "reqtime"=>0.000000}]
fluent-bit-nginx-filter-example-nginx-1       | {"time":"19/Oct/2023:03:04:13 +0000","uri":"/502","status":502,"reqtime":0.000}
fluent-bit-nginx-filter-example-fluent-bit-1  | [0] nginx_access_log: [[1697684653.000000000, {}], {"uri"=>"/502", "status"=>502, "reqtime"=>0.000000}]

というわけでFluent Bitでアクセスログをフィルタリングする話でした。nginxに限らずJSONでアクセスログを出していて数値でフィルタリングしたいと言った場合には真似できると思います。