最近docker使い始めて、ログの扱いに困り、fluentdで他のサーバに送ることを知り、Elasticsearchで保存し、Kibanaで閲覧することを覚えたばかり。
docker から fluentd でログを送ると、nginx の access.log の行が log フィールドにそのまま入ってきて、パースされていないので扱いにくいので、パースされた状態で送りたいと単純に思ったが、何しろ使い始めたばかりでいろいろ勘違いしているかもしれない。
docker run --rm --name nginx -p 80:80 --log-driver fluentd --log-opt tag="MY_APP_NAME.nginx" nginx
などとして nginx を起動して送り先を fluentd にし、fluentd側から nginx コンテナの出力だとわかるように tag を設定。
これで
curl localhost
とすると、fluentd にはこんなログが記録される。
fluentd_1 | 2020-06-26 13:55:31.000000000 +0900 MY_APP_NAME.nginx: {"container_id":"fcde1017a33b494e537923f46bf89aff68c34d2ac776c2b367d92faae69b6fcc", "container_name":"/nginx", "source":"stdout", "log":"172.17.0.1 - - [26/Jun/2020:04:55:31 +0000] \"GET / HTTP/1.1\" 200 612 \"-\" \"curl/7.55.1\" \"-\""}
この log フィールドをパースしたい。
curl localhsot/hoge
などと、存在しないリソースを要求すると、こう。
fluentd_1 | 2020-06-26 13:55:35.000000000 +0900 MY_APP_NAME.nginx: {"container_id":"fcde1017a33b494e537923f46bf89aff68c34d2ac776c2b367d92faae69b6fcc", "container_name":"/nginx", "source":"stderr", "log":"2020/06/26 04:55:35 [error] 28#28: *11 open() \"/usr/share/nginx/html/hoge\" failed (2: No such file or directory), client: 172.17.0.1, server: localhost, request: \"GET /hoge HTTP/1.1\", host: \"localhost\""}
fluentd_1 | 2020-06-26 13:55:35.000000000 +0900 MY_APP_NAME.nginx: {"container_id":"fcde1017a33b494e537923f46bf89aff68c34d2ac776c2b367d92faae69b6fcc", "container_name":"/nginx", "source":"stdout", "log":"172.17.0.1 - - [26/Jun/2020:04:55:35 +0000] \"GET /hoge HTTP/1.1\" 404 153 \"-\" \"curl/7.55.1\" \"-\""}
nginx イメージのデフォルトでは access.log は /dev/stdout で、error.log は /dev/stderr になっていて、ログの source フィールドで判別できる。
この情報を使って fluent.conf でパースできるように設定したい。
rewrite-tag-filter が必要だったのでインストール。
gem install fluent-plugin-rewrite-tag-filter
fluent.conf で *.nginx のタグのうち source が stdout のものは *.nginx-access に、stderr のものは *.nginx-error にタグをrewriteする。
そして、そのタグを目印に filter で parse する。
<source> @type forward @label @mainstream </source> <label @mainstream> <match *.nginx> @type rewrite_tag_filter <rule> key source pattern /^stdout$/ tag ${tag}-access </rule> <rule> key source pattern /^stderr$/ tag ${tag}-error </rule> </match> <filter *.nginx-access> @type parser key_name log reserve_data true remove_key_name_field true <parse> @type nginx keep_time_key true </parse> </filter> <filter *.nginx-error> @type parser key_name log reserve_data true remove_key_name_field true <parse> @type multiline format_firstline /^\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[\w+\] (?<pid>\d+).(?<tid>\d+): / format1 /^(?<time>\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}) \[(?<log_level>\w+)\] (?<pid>\d+).(?<tid>\d+): (?<message>.*)/ keep_time_key true </parse> </filter> <match> @type stdout </match> </label>
nginx-error の parse で使っている正規表現は fluentd の古いドキュメントに載っていたものをそのまま使用。
この設定でログを処理すると、access.log の内容は nginx-access タグ、error.log の内容は nginx-error タグをつけて、パースした状態のログを転送できた。
fluentd_1 | 2020-06-26 13:36:39.000000000 +0900 MY_APP_NAME.nginx-access: {"source":"stdout", "container_id":"fcde1017a33b494e537923f46bf89aff68c34d2ac776c2b367d92faae69b6fcc", "container_name":"/nginx", "remote":"172.17.0.1", "host":"-", "user":"-", "time":"26/Jun/2020:04:36:39 +0000", "method":"GET", "path":"/", "code":"200", "size":"612", "referer":"-", "agent":"curl/7.55.1", "http_x_forwarded_for":"-"}
fluentd_1 | 2020-06-26 13:37:04.000000000 +0900 MY_APP_NAME.nginx-access: {"container_id":"fcde1017a33b494e537923f46bf89aff68c34d2ac776c2b367d92faae69b6fcc", "container_name":"/nginx", "source":"stdout", "remote":"172.17.0.1", "host":"-", "user":"-", "time":"26/Jun/2020:04:37:04 +0000", "method":"GET", "path":"/hoge", "code":"404", "size":"153", "referer":"-", "agent":"curl/7.55.1", "http_x_forwarded_for":"-"}
fluentd_1 | 2020-06-26 04:37:04.000000000 +0900 MY_APP_NAME.nginx-error: {"container_id":"fcde1017a33b494e537923f46bf89aff68c34d2ac776c2b367d92faae69b6fcc", "container_name":"/nginx", "source":"stderr", "time":"2020/06/26 04:37:04", "log_level":"error", "pid":"28", "tid":"28", "message":"*8 open() \"/usr/share/nginx/html/hoge\" failed (2: No such file or directory), client: 172.17.0.1, server: localhost, request: \"GET /hoge HTTP/1.1\", host: \"localhost\""}