これは、なにをしたくて書いたもの?
Fluent Bitの機能で、レコードを編集するものを試してみようかなと思いまして。
具体的には、以下の3つのFilterプラグインが該当します。
Modify - Fluent Bit: Official Manual
Record Modifier - Fluent Bit: Official Manual
Lua - Fluent Bit: Official Manual
Parserプラグインもある意味ではこの目的には近い気はしますが、また別の機会に。
Parser - Fluent Bit: Official Manual
それぞれ、簡単な説明を入れつつ試していってみましょう。
環境
今回の環境は、こちらです。
Fluent Bit。
$ /opt/td-agent-bit/bin/td-agent-bit --version Fluent Bit v1.5.3
Ubutu Linux 20.04 LTSに、aptでインストールしています。
$ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 20.04.1 LTS Release: 20.04 Codename: focal $ uname -srvmpio Linux 5.4.0-42-generic #46-Ubuntu SMP Fri Jul 10 00:24:02 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
まずは、デフォルトの設定ファイルを載せておきます。
$ grep -v '^ *#' /etc/td-agent-bit/td-agent-bit.conf [SERVICE] flush 5 daemon Off log_level info parsers_file parsers.conf plugins_file plugins.conf http_server Off http_listen 0.0.0.0 http_port 2020 storage.metrics on [INPUT] name cpu tag cpu.local interval_sec 1 [OUTPUT] name stdout match *
ここから、TCP をInputにして、Outputを標準出力に設定します。
[INPUT] Name tcp Listen 0.0.0.0 Port 5170 Format json ## FILTER定義 [OUTPUT] Name stdout Match * Format json_lines
あとは、Filterプラグインの定義を変えていって、試していきましょう。
Record Modifier
まずは、Record Modifier Filterプラグインから。
Record Modifier - Fluent Bit: Official Manual
これは、フィールドの追加および削除ができるプラグインになります。とてもシンプルです。
Recordで指定のキーと値を追加、Remove_keyで指定のキーを削除することができます。
[FILTER] Name record_modifier Match * Record hostname ${HOSTNAME} Record filter_type record_modifiler Record append_message Hello Fluent Bit!! Remove_key dummy_field
「${}」で環境変数を参照することもできます。
3つのキーを(hostname、filter_type、append_message)を追加し、ひとつのキー(dummy_field)を削除してみます。
hostnameは、環境変数を使用します。
ちなみに、次のModify Filterプラグインと違って、Recordに指定する値には空白を含めることができるみたいです。
※それを見るためのappend_messageです
確認してみましょう。
$ echo '{"message": "Hello World!!", "day": "2020-08-16", "dummy_field": "dummy"}' | nc localhost 5170
結果。
Aug 16 12:00:26 myhost td-agent-bit[1177]: {"date":1597579225.752374,"message":"Hello World!!","day":"2020-08-16","hostname":"myhost","filter_type":"record_modifiler","append_message":"Hello Fluent Bit!!"}
ちなみに、Recordで指定したキーがすでに存在する場合は、そのキーについてはFilterのRecord Modifierの定義が無視される
ようです。
また、Record Modifilerでは「残すキー」をホワイトリスト(Whitelist_key)として定義することもできます。
[FILTER] Name record_modifier Match * Whitelist_key message Whitelist_key day
この場合「dummy_field」は「Whitelist_key」の定義にないので、レコードから削除されます。
$ echo '{"message": "Hello World!!", "day": "2020-08-16", "dummy_field": "dummy"}' | nc localhost 5170 Aug 16 12:08:07 myhost td-agent-bit[1231]: {"date":1597579685.989803,"message":"Hello World!!","day":"2020-08-16"}
Modify
Modify Filterプラグインは、ルールを使ってレコードを変更することができます。また、変更を行うかどうかを条件で指定する
こともできます。
Modify - Fluent Bit: Official Manual
ルールというのは、こちらですね。レコードに対して、特定のキーに対する値の変更、キーと値の追加、キーのリネーム、
コピー、そして様々な指定方法でキーの削除を行うことができます。
いくつかルールを使ってみましょう。Copy(コピー)、Set(キーに対する値の変更)、Add(キーと値の追加)、
Remove(指定のキーを削除)、Remove_wildcard(指定した文字列に前方一致するキーを削除)といった感じで。
[FILTER] Name modify Match * Copy message original_message Set message Hello!! Add filter_type modify Add hostname ${HOSTNAME} Remove dummy_field Remove_wildcard ignore_field
ルールは、以下の特徴を持ちます。
- 先頭から適用され、複数のルールを書いた場合は前のルールの結果を元に適用される
- つまり、ルールを入れ替えると結果が変わることがある、ということですね
- ルールは任意の数を設定できる
- ルールは大文字・小文字が区別されませんが、パラメーターはそうではない
ところで、ここでも環境変数が使えるようです。…そもそも設定ファイルでの書き方として、環境変数が使えるようですね。
Variables - Fluent Bit: Official Manual
では、設定した内容を確認してみます。余計なフィールドを入れたりしている、レコードを送信。
$ echo '{"message": "Hello World!!", "day": "2020-08-16", "filter_apply": "true", "dummy_field": "dummy", "ignore_field1": "dummy", "ignore_field2": "dummy", "bad_ignore_field": "dummy"}' | nc localhost 5170
結果。Remove_wildcardで指定したものは、一気になくなっています。他は、コピーや追加、変更なのでわかりやすいですね。
Aug 16 12:22:37 myhost td-agent-bit[1259]: {"date":1597580552.890862,"original_message":"Hello World!!","day":"2020-08-16","filter_apply":"true","bad_ignore_field":"dummy","message":"Hello!!","filter_type":"modify","hostname":"myhost"}
一方で、「bad_ignore_field」は残ってしまいました。このあたりが、前方一致っぽいと言っている理由ですが。
削除対象をもうちょっと柔軟に設定したい場合は、Remove_regexを使うのでしょう。
今度は、Conditionを使ってみましょう。Conditionを使うと、ルールを適用するかどうかを、レコードのキーや値に基づいて
決めることができます。
今回は、「filter_apply」というキーに対する値が「true」、かつ「editable」というキーが存在していればルールを適用する、
という条件にしました。
[FILTER] Name modify Match * Condition Key_Value_Equals filter_apply true Condition Key_exists editable Copy message original_message Set message Hello!! Add filter_type modify Add hostname ${HOSTNAME} Remove dummy_field Remove_wildcard ignore_field
なお、ここでいう「値」は文字列型である必要があるみたいです。
たとえば、以下は条件に一致しますが
"filter_apply": "true"
こちらは一致しません。
"filter_apply": true
では、確認してみましょう。
ルールが適用されるケース(filter_applyがtrueであり、editableもあるから)。
$ echo '{"message": "Hello World!!", "day": "2020-08-16", "filter_apply": "true", "editable": "", "dummy_field": "dummy", "ignore_field1": "dummy", "ignore_field2": "dummy", "bad_ignore_field": "dummy"}' | nc localhost 5170
結果。
Aug 16 13:01:35 myhost td-agent-bit[1351]: {"date":1597582894.641241,"original_message":"Hello World!!","day":"2020-08-16","filter_apply":"true","editable":"","bad_ignore_field":"dummy","message":"Hello!!","filter_type":"modify","hostname":"myhost"}
ルールが適用されないケース(filter_applyがtrueだが、editableがないから)。
$ echo '{"message": "Hello World!!", "day": "2020-08-16", "filter_apply": "true", "dummy_field": "dummy", "ignore_field1": "dummy", "ignore_field2": "dummy", "bad_ignore_field": "dummy"}' | nc localhost 5170
結果。
Aug 16 13:00:05 myhost td-agent-bit[1351]: {"date":1597582804.096306,"message":"Hello World!!","day":"2020-08-16","filter_apply":"true","dummy_field":"dummy","ignore_field1":"dummy","ignore_field2":"dummy","bad_ignore_field":"dummy"}
ルールが適用されないケース(filter_applyがfalseだから)。
$ echo '{"message": "Hello World!!", "day": "2020-08-16", "filter_apply": "false", "dummy_field": "dummy", "ignore_field1": "dummy", "ignore_field2": "dummy", "bad_ignore_field": "dummy"}' | nc localhost 5170
結果
Aug 16 13:00:55 myhost td-agent-bit[1351]: {"date":1597582852.588941,"message":"Hello World!!","day":"2020-08-16","filter_apply":"false","dummy_field":"dummy","ignore_field1":"dummy","ignore_field2":"dummy","bad_ignore_field":"dummy"}
Lua
最後は、Lua Filterプラグインです。Lua Filterプラグインを使うことで、独自に作成したLuaスクリプト内でレコードを変更することが
できるようになります。
Lua - Fluent Bit: Official Manual
Luaスクリプトを用意し、Filterプラグインの設定としてはスクリプトファイルのパスと、コールバックする関数を指定する
ことになります。
ところで、Lua Filterプラグインがどんな環境で動くのか?というのが気になるところなのですが、LuaJITがFluent Bitに
組み込まれているようです。
https://github.com/fluent/fluent-bit/tree/master/lib/LuaJIT-2.1.0-beta3
LuaJITは、こちら。
Frequently Asked Questions (FAQ)
ドキュメントやFAQを読んでいると、LuaJITはLua 5.1と互換性があるようですね。
LuaJIT is compatible to the Lua 5.1 language standard.
Lua Filterプラグインを作成するときは、こちらを覚えておきましょう。
で、どんな感じでLuaスクリプトを書けばよいかですが、GitHubのFluent Bitリポジトリにサンプルがあるので、こちらを
参考にするとよいでしょう。
https://github.com/fluent/fluent-bit/blob/v1.5.3/scripts/test.lua
https://github.com/fluent/fluent-bit/blob/v1.5.3/scripts/append_tag.lua
https://github.com/fluent/fluent-bit/blob/v1.5.3/scripts/override_time.lua
各サンプルを見るとだいたい雰囲気がわかるのですが、ドキュメントも読んでおきましょう。
Luaスクリプトで作成するコールバック関数には、以下の3つの引数が渡ってきます。
- レコードに関連付けられているタグ
- タイムスタンプ
- レコード
また、関数の戻り値としては、以下の3つを返す必要があります。
- コード(整数)
- タイムスタンプ
- レコード
このコードの値がポイントで、返す値で戻り値に対する振る舞いが変わります。
- -1 … レコードをドロップする(無視するレコードになる)
- 0 … レコードを変更しない
- 1 … 関数の戻り値を使って、タイムスタンプとレコードを変更できる
- 2 … 関数の戻り値を使って、レコードを変更できる
つまり、関数の戻り値のうち、2つ目と3つ目が使われるのは、コードが1または2の時だけだということですね(2は、3つ目しか
使いませんが。
戻り値のコードで変更可能な値以外を、戻り値で変更しても結果には反映されません。
たとえば、コードが0でレコードを変更しても、レコードの変更分は無視されるということですね。
また、デバッグに関してはprint関数を使って標準出力に書き出せば良さそうです(サンプルスクリプト、test.luaがその方法を
とっています)。
では、Luaスクリプトを用意してみましょう。
/etc/td-agent-bit/scripts/my_script.lua
function cb_record_filter(tag, timestamp, record) local record_type = record["record_type"] if record_type == "drop" then -- -1 = レコードを破棄する return -1, 0, 0 elseif record_type == "keep" then record["filter_type"] = "lua" -- 0 = レコードを変更しない return 0, 0, record elseif record_type == "modify" then local new_record = {} -- コピー for key, value in ipairs(record) do table.insert(new_record, key, value) end new_record["filter_type"] = "lua" new_record["original_message"] = record["message"] new_record["message"] = "Hello Lua Script!!" new_record["dummy_field"] = nil -- 1 = レコードとタイムスタンプを変更する -- 2 = レコードを変更し、タイムスタンプはそのまま return 1, timestamp, new_record else return 0, 0, 0 end end
「record_type」というキーの値で、レコードのドロップ、そのまま、変更の3種類を表現することにしました。
このLuaスクリプトは、/etc/td-agent-bitディレクトリ配下に置いています。
[FILTER] Name lua Match * Script /etc/td-agent-bit/scripts/my_script.lua # Script scripts/my_script.lua Call cb_record_filter
Luaスクリプトのパスは、絶対パスでも、コメントアウトにしていますが相対パスでもOKでした…。
確認してみましょう。
レコードを変更する場合。
$ echo '{"message": "Hello World!!", "day": "2020-08-21", "record_type": "modify", "dummy_field": "dummy"}' | nc localhost 5170
結果。レコードが変更されたことが確認できます。
Aug 16 13:45:06 myhost td-agent-bit[1765]: {"date":1597585504.882447,"day":"2020-08-21","record_type":"modify","original_message":"Hello World!!","message":"Hello Lua Script!!","filter_type":"lua"}
レコードを変更しない場合。
$ echo '{"message": "Hello World!!", "day": "2020-08-21", "record_type": "keep", "dummy_field": "dummy"}' | nc localhost 5170
結果。レコードの変更が無視されたことが確認できます。
Aug 16 13:47:55 myhost td-agent-bit[1792]: {"date":1597585671.897845,"message":"Hello World!!","day":"2020-08-21","record_type":"keep","dummy_field":"dummy"}
レコードをドロップする場合。
$ echo '{"message": "Hello World!!", "day": "2020-08-21", "record_type": "drop", "dummy_field": "dummy"}' | nc localhost 5170
結果は出力されません(ドロップされるので)。
これで、ざっくりと確認できたのではないかな、と思います。