dockerのnginxのコンテナのログをfluentdでパースして送りたい

最近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\""}