fluent-bitにLTSV独自エスケープをアンエスケープするパッチ作成
はじめに
fluent-bitにLTSV独自エスケープをアンエスケープするパッチを作成したメモです。
LTSV独自エスケープ
Labeled Tab-separated Values (LTSV)に以下の独自のエスケープを追加したものです。
LTSVはラベルと値を:でつなぎ、それらをTABでつないで、1行の終端はLFで改行します。
ラベルは管理者が決めるので、:、TAB、LFを含まないような文字列のみを使うようにできます。
一方、値は外界からのリクエストに含まれるものなので、TABやLFの文字を含む可能性があります。 例えばUser-AgentにTABが入るようなケースがあったり、悪意の攻撃ではリクエスト行やURLのパスに制御文字が含まれるケースもあり得ます。
そこで、以下の独自のエスケープを行うようにしています。
- 値に含まれるTABは
\t、LFは\nとバックスラッシュでエスケープする - 値に含まれる
\も\\とバックスラッシュでエスケープする
ログを解釈する側では、これらをアンエスケープする必要があります。
\t→TAB\n→LF\\→\
LTSV独自エスケープ対応のfluent-bitのUbuntu用debパッケージ
いつものようにUbuntuの独自debパッケージを作りました。
https://github.com/hnakamur/fluent-bit-deb-docker
LTSV独自エスケープのアンエスケープ対応のパッチはdebian/patches/unescape_ltsv.patchです。
また以下の変更も加えています。
- hnakamur/openresty-luajit-deb-dockerでビルドしたOpenRestyのLuaJITのdebパッケージを使う
- hnakamur/openresty-lua-cjson-deb-dockerでビルドしたOpenRestyのLua CJSONのdebパッケージを使う
- Ubuntu標準パッケージのjemallocを使う
LTSV独自エスケープのアンエスケープの設定
fluent-bit.yaml内のparsersのformat: ltsvの要素に以下のようにunescape: trueと指定するとアンエスケープします。
parsers:
- name: nginx_access_ltsv
format: ltsv
time_key: time
time_format: '%Y-%m-%dT%H:%M:%S%z'
unescape: true
動作確認
https://github.com/hnakamur/fluent-bit-deb-dockerのtestディレクトリにIncusを使った動作確認用のスクリプトを置きました。
実行例
$ ~/ghq/github.com/hnakamur/fluent-bit-deb-docker/test$ ./launch-incus-container-and-test.sh 24.04 fluentbit-noble
…(略)…
+ tail -1 /var/log/nginx/access.ltsv.log
time:2025-10-13T05:17:32+00:00 msec:1760332652.525 host:localhost http_host:localhost status:200 scheme:http request:GET /?a=1 HTTP/1.1 request_id:0201c592df20769c34c3b9c81bd96d51 request_time:0.000 request_length:76 body_bytes_sent:615 bytes_sent:853 remote_addr:127.0.0.1 remote_port:59448 remote_user: pid:1207 referer: x_forwarded_for: user_agent:tab\tinside
…(略)…
+ systemctl status --no-pager -l fluent-bit
…(略)…
Oct 13 05:17:33 fluentbit-noble fluent-bit[1213]: [{"date":1760332652.0,"msec":"1760332652.525","host":"localhost","http_host":"localhost","status":"200","scheme":"http","request":"GET /?a=1 HTTP/1.1","request_id":"0201c592df20769c34c3b9c81bd96d51","request_time":"0.000","request_length":"76","body_bytes_sent":"615","bytes_sent":"853","remote_addr":"127.0.0.1","remote_port":"59448","remote_user":"","pid":"1207","referer":"","x_forwarded_for":"","user_agent":"tab\tinside"}]
nginxのアクセスログではuser_agentの値がtab\tinsideとTABが\tにエスケープされています。
これはhnakamur/nginx-deb-dockerでビルドしたnginxでnginx.conf#L20-L40のようにlog_formatにescape=ltsvという独自拡張した設定を行うことで実現しています。
fluent-bitの出力では"user_agent":"tab\tinside"となっておりTABが維持できていることが確認できます(JSONのエスケープでTABが\tになっています)。
試しに、fluent-bitの設定でunescape: trueをコメントアウトして試すと以下のようになります。
Oct 13 05:20:32 fluentbit-noble2 fluent-bit[1213]: [{"date":1760332831.0,"msec":"1760332831.526","host":"localhost","http_host":"localhost","status":"200","scheme":"http","request":"GET /?a=1 HTTP/1.1","request_id":"195674a1389e61693288ed51d16bd5ef","request_time":"0.000","request_length":"76","body_bytes_sent":"615","bytes_sent":"853","remote_addr":"127.0.0.1","remote_port":"57586","remote_user":"","pid":"1207","referer":"","x_forwarded_for":"","user_agent":"tab\\tinside"}]
アンエスケープなしだと"user_agent"の値が"tab\\tinside"となっています。JSONのエスケープで\が\\とエスケープされており、tabとinsideの実際の間はTABではなく\tのままになってしまっていることがわかります。