Featured image of post mtailでnginxのaccess.logをメトリクス化する

mtailでnginxのaccess.logをメトリクス化する

正規表現でログをメトリクス化

やったことのまとめ

つかうもの

やったこと

mtailのビルド

mtailはアプリケーションのログをメトリクス化するツール。
自身でメトリクスを公開できないアプリの監視なんかに使えそう。

nginxのメトリクスを扱うexporterとしてはnginx-prometheus-exporterがあるんだけど、
stub_statusの設定が必要だったりOSS版nginxだとメトリクスの種類が少なかったりするので今回はmtailを使ってみる。

mtailのビルド方法はいくつかある1が、今回はかんたんに試したいのでGitHub Releasesで配布されているバイナリ2を仕込んだDockerイメージを作成する。
(自前でビルドしようとしたら自分の環境ではテストのエラーが発生してうまくできなかった…)

# Docker imageのビルド
$ ls Dockerfile
Dockerfile
$ docker image build -t mtail .
$ docker container run -it --rm mtail --version
mtail version 3.0.0-rc47 git revision 5e0099f843e4e4f2b7189c21019de18eb49181bf go version go1.16.5 go arch amd64 go os linux

mtail programの作成

続いてログをメトリクスに変換するためのmtail programと呼ばれるスクリプトを作成する。
mtail programpattern(ログの各行に対する条件)とaction(patternを満たしたログに関する処理)から構成されている。3
(awkにちょっと似ている)

patternは主に正規表現で記述するので、まずはメトリクス化したいnginxのアクセスログのフォーマットを確認する。

# nginxのログフォーマットを確認
$ docker container run -it --rm nginx:1.21.1 cat /etc/nginx/nginx.conf
...(省略)
http {
...
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
...
}

このときのログの具体例は次のとおり。

172.20.0.1 - - [18/Aug/2021:14:04:48 +0000] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36" "-"

このフォーマットのログに対して正規表現で名前付きキャプチャグループを設定すると次のような感じになる。
$requestはメソッド、URI、バージョンに分割するようにした。  

^(?P<remote_addr>\S+) - (?P<remote_user>.+) \[(?P<time_local>.+)\] "(?P<request_method>\S+) (?P<request_uri>\S+) (?P<request_version>\S+)" (?P<status>\S+) (?P<body_bytes_sent>\S+) "(?P<http_referer>\S+)" "(?P<http_user_agent>.+)" "(?P<http_x_forwarded_for>\S+)"$

この正規表現をpatternとしたmtail programを次のように作成した。
nginx_requestはリクエスト数を数えるカウンタで、ラベルとしてリクエストメソッドとステータスコードを付与するようにした。
nginx_request_pattern_matching_failedpatternにマッチしなかったログの行数を数えるカウンタとして使用する。
また、どちらもログファイル名としてsourceラベルを付与するようにした。

動作確認

これでmtailを使う準備ができたのでDocker ComposenginxPrometheusと一緒に起動してみる。
mtailの実行時引数などは公式ドキュメント4を参考にした。  

Docker版のnginxはアクセスログ(/var/log/nginx/access.log)とエラーログ(/var/log/nginx/error.log)がそれぞれ標準出力(/dev/stdout)と標準エラー出力(/dev/stderr)へのシンボリックリンクになっていて、
ファイルとして参照するのが難しかったのでログの出力先を変更するようにした。

# ログファイルが標準出力へのシンボリックリンクになっている
$ docker container run -it --rm nginx:1.21.1 ls -l /var/log/nginx
total 0
lrwxrwxrwx 1 root root 11 Aug 17 11:46 access.log -> /dev/stdout
lrwxrwxrwx 1 root root 11 Aug 17 11:46 error.log -> /dev/stderr
# 起動前のディレクトリの状態
$ tree
.
├── Dockerfile
├── README.md
├── docker-compose.yaml
├── mtail
│   └── nginx.mtail
├── nginx
│   └── nginx.conf
└── prometheus
    └── prometheus.yml

# 起動
$ docker compose up -d --force-recreate
$ docker compose ps
NAME                       COMMAND                  SERVICE             STATUS              PORTS
mtail-nginx_mtail_1        "./mtail --progs=/et…"   mtail               running             0.0.0.0:3903->3903/tcp, :::3903->3903/tcp
mtail-nginx_nginx_1        "/docker-entrypoint.…"   nginx               running             0.0.0.0:80->80/tcp, :::80->80/tcp
mtail-nginx_prometheus_1   "/bin/prometheus --c…"   prometheus          running             0.0.0.0:9090->9090/tcp, :::9090->9090/tcp

この状態でhttp://localhost:3903/を開くとmtailが正常に動作していることを確認できる。

mtail

次にnginxhttp://localhost/に対していくつかリクエストを送ってみる。

# nginxにホストOSからリクエストを送る
$ curl -X GET "http://localhost/index.html"
$ curl -X GET "http://localhost/index.html"
$ curl -X GET "http://localhost/hogehoge.html"
$ curl -X POST "http://localhost/index.html"
$ curl -X PUT "http://localhost/index.html"
$ curl -X DELETE "http://localhost/index.html"

# ログファイルの内容
$ docker compose exec nginx cat /var/log/mtail-nginx/access.log
172.22.0.1 - - [19/Aug/2021:11:09:05 +0000] "GET /index.html HTTP/1.1" 200 612 "-" "curl/7.64.1" "-"
172.22.0.1 - - [19/Aug/2021:11:09:06 +0000] "GET /index.html HTTP/1.1" 200 612 "-" "curl/7.64.1" "-"
172.22.0.1 - - [19/Aug/2021:11:09:12 +0000] "GET /hogehoge.html HTTP/1.1" 404 153 "-" "curl/7.64.1" "-"
172.22.0.1 - - [19/Aug/2021:11:09:18 +0000] "POST /index.html HTTP/1.1" 405 157 "-" "curl/7.64.1" "-"
172.22.0.1 - - [19/Aug/2021:11:09:22 +0000] "PUT /index.html HTTP/1.1" 405 157 "-" "curl/7.64.1" "-"
172.22.0.1 - - [19/Aug/2021:11:09:27 +0000] "DELETE /index.html HTTP/1.1" 405 157 "-" "curl/7.64.1" "-"
$ docker compose exec nginx cat /var/log/mtail-nginx/error.log
2021/08/19 11:09:12 [error] 31#31: *3 open() "/usr/share/nginx/html/hogehoge.html" failed (2: No such file or directory), client: 172.22.0.1, server: localhost, request: "GET /hogehoge.html HTTP/1.1", host: "localhost"

mtailのメトリクスはPrometheusのフォーマットに対応していて、
http://localhost:3903/metricsで内容を確認できる。

mtail

nginx.mtailで定義したとおり、
アクセスログの内容は正規表現のpatternにマッチするのでメソッドとステータスがラベル化されてnginx_requestとしてカウントされていて、
エラーログの内容はpatternにマッチしないのでnginx_request_pattern_matching_failedとしてカウントされている。

最後に一応Prometheushttp://localhost:9090/graphでもメトリクスを確認する。

ログのメトリクスが時系列データとして扱える

mtailのメトリクスは通常のexporterと同様に扱えるので、時系列データとしてグラフ化もできている。

やったぜ。🎉

おわり

mtailnginxのログファイルをメトリクス化できた。
nginxに限らず、ログのフォーマットが決まっていてそれにマッチする正規表現が書ければ何でもメトリクス化できるのでかなり便利だと思った。  

おまけ

しっぽがながいねこ