stefafafan の fa は3つです

"すてにゃん" こと id:stefafafan のブログです

nginxのアクセスログを解析してJSONを返すだけのCLIをRustで作成した

最近RustでCLIを作るのに突然ハマってきたのと、ISUCON が近づいていることもあって、以下のCLIを作ってみました。

github.com

使い方

現状はnginxのlog_formatをJSONとした上で特定のキーを持っていることを想定しています。 (こういう感じの設定をしておく👇)

log_format json escape=json '{'
                            '"time":"$time_iso8601",'
                            '"host":"$remote_addr",'
                            '"method":"$request_method",'
                            '"uri":"$request_uri",'
                            '"status":"$status",'
                            '"body_bytes":"$body_bytes_sent",'
                            '"referer":"$http_referer",'
                            '"ua":"$http_user_agent",'
                            '"request_time":"$request_time",'
                            '"response_time":"$upstream_response_time"'
                            '}';

この状態で吐かれたアクセスログを、 seki にパイプして食わせるのみです。以下のような形のJSONが返ってきます。*1

$ cat access.log | seki
[
  {
    "method": "GET",
    "uri": "/hello",
    "count": 1,
    "status_code": {
      "status_1xx": 0,
      "status_2xx": 0,
      "status_3xx": 1,
      "status_4xx": 0,
      "status_5xx": 0
    },
    "response_time": {
      "min": 0.0,
      "max": 0.113,
      "avg": 0.113,
      "sum": 0.113,
      "p50": 0.113,
      "p75": 0.113,
      "p90": 0.113,
      "p95": 0.113,
      "p99": 0.113
    }
  }
]

このJSONをどう使うかはユーザに委ねる、という感じです。

思想

tkuchiki/alpmatsuu/kataribe など便利ツールが世の中にあるので新たに作る必要は特にないですが*2、思想としてはJSONとして返しておけば色々と加工したり独自のスクリプトに組み込んであれこれしやすそうではないか?と思って作ってみています。

そのため、 seki 自体が人間にみやすいフォーマットへの出力オプションを持つ予定はないです。

private-isu でちょっと使ってみた様子

実際に catatsuy/private-isu でも使ってみました。AMIを利用してEC2サーバを建てた上で実施しています。

まず seki をインストールします。今回は Release v0.1.2 · stefafafan/seki · GitHub に載っている curl を使う手順を使います。

$ curl --proto '=https' --tlsv1.2 -LsSf https://github.com/stefafafan/seki/releases/download/v0.1.2/seki-installer.sh | sh

PATHを通したかったら source してねと言われるのでします。

$ source $HOME/.cargo/env

nginx.conf の log_format を書き換えた上でベンチマークを回します。終わったら早速 seki を使ってみます。

$ sudo cat /var/log/nginx/access.log | seki

これを何気なく実行すると結果が長すぎることがわかります。 /posts/12667 みたいなエンドポイントが沢山個別にならんでいるのでグルーピングします。

一旦エンドポイントの一覧をざっとみてみましょう。 jq コマンドで .uri のみ抽出します。

$ sudo cat /var/log/nginx/access.log | seki | jq '.[] | .uri'
"/posts/12667"
"/posts/2005"
"/image/6908.jpg"
"/image/9785.jpg"
"/image/9228.jpg"
"/posts/556"
"/posts/4366"
"/image/1149.jpg"
"/posts/7523"
"/posts/5208"
"/posts/1797"
"/posts/3376"
"/@lindsay"
"/@violet"
"/image/1855.jpg"
"/image/6059.jpg"
"/posts/7627"
...

/posts/\d+/images/.+/@[a-zA-Z0-9]+ みたいなのが沢山ありそうですね。 config.toml にグルーピングの設定を書きましょう。 リポジトリにもサンプルがあります。*3
github.com

$ vim config.toml
$ cat config.toml
[[grouping]]
regexp = '^/posts/\d+$'

[[grouping]]
regexp = '^/@[a-zA-Z0-9]+$'
name = '/@:username'

[[grouping]]
regexp = '^/image/.+'

regexp正規表現を書きます。 name の指定はオプショナルですが、名前をつけたいときに書いておくとURLが並んだ時に見やすくなります。

今一度先ほどのコマンドを実行してみます。 seki は同じ階層にある config.toml をデフォルトで読み込みますが、 --config で明示的にパスを指定することもできます。

$ sudo cat /var/log/nginx/access.log | seki | jq '.[] | .uri'
"/login"
"/posts"
"/"
"/@:username"
"/admin/banned"
"/image/.+"
"/comment"
"/initialize"
"/js/main.js"
"/register"
"/"
"/css/style.css"
"^/posts/\\d+$"
"/favicon.ico"
"/login"
"/js/timeago.min.js"
"/logout"

いい感じにグルーピングができたので、あとは好きな形に加工してベンチの結果を確認してみましょう。今回は response_time.sum 順にしつつ、 column コマンドで整形して出力してみます。

$ sudo cat /var/log/nginx/access.log | seki | \
    jq -r "sort_by(-.response_time.sum) | \
        .[] | \
        [.method, \
        .uri, \
        .status_code.status_2xx, \
        .status_code.status_3xx, \
        .status_code.status_4xx, \
        .status_code.status_5xx, \
        .response_time.sum] | \
        @tsv" | column -t
GET   /                   3939  0      0    0  198.79800000000128
GET   /posts              1590  0      0    0  95.20300000000013
GET   ^/posts/\\d+$       2829  0      0    0  93.72100000000103
POST  /login              0     1872   0    0  78.4420000000002
GET   /@:username         482   0      0    0  40.025
GET   /login              694   0      0    0  19.445999999999984
POST  /                   0     162    162  0  17.529000000000003
GET   /image/.+           2977  90691  0    0  16.31000000000001
POST  /register           0     263    0    0  12.548999999999992
GET   /logout             0     347    0    0  9.674999999999988
POST  /comment            0     244    0    0  7.659999999999998
GET   /admin/banned       0     0      263  0  7.20899999999999
GET   /initialize         1     0      0    0  0.369
GET   /css/style.css      5504  0      0    0  0.0
GET   /js/timeago.min.js  5504  0      0    0  0.0
GET   /favicon.ico        5504  0      0    0  0.0
GET   /js/main.js         5504  0      0    0  0.0

GET / が遅そうなので、ここの改善をしていきましょう。

作ってみての感想

当初思っていたよりもサクッとCLIが作れてよかった、 閉包テーブルによる階層構造をPlantUMLやMermaid形式で可視化するツールを作った - stefafafan の fa は3つです の時と同様に clap-rs/clap がやっぱり便利なのと、それ以外にも各種バイナリの用意やリリースのためのツール群 (crate-ci/cargo-releaseaxodotdev/cargo-dist) もとても助かりました。

そして実装の際に alp のこと改めて調べていましたが、めちゃくちゃ機能充実していてすごいなと思ったりしました。実装にあたってalpとkataribeのインターフェースはとても参考にしました、ありがとうございます🙏 複雑なjqのクエリを書くのは大変なのでこれら既存のツールを使うのが一番手っ取り早いなと改めて思いました(が自分でも作るのは楽しかったです)。

次は pt-query-digest 代わりのCLIも作りたいです (?)。

*1:もっとこういうJSONのインターフェースにしてほしいみたいなのがあったらいつでもissueなりでコメントお待ちしています!

*2:自分でも作ってみたかったので作ったのが大きい

*3:設定ファイルのフォーマットは kataribe と同じような形式を採用しています🙏