アクセスログからAPIごとにカウント取ったり成形したり


APIリクエストごとにカウント取ってみた

「ログ見てるけど情報量多すぎて嫌になる」

このような経験、ありませんか?

そんな苦い経験とは金輪際、おさらばしましょう。

本記事では、コマンドを駆使してログを見やすく加工したり、カウントする方法を紹介します。

使用するコマンド

## ファイルを標準出力
cat

## 行数を指定して出力
head

## テキスト処理コマンド
awk

## 繰り返し処理
for

## 特定文字列を除外
sed

## 文字列出力
printf

## 出力のソート
sort

## 出力を一意の結果にする
uniq

まずは普通に出力

cat コマンド使います

cat app.log
[2018-06-07 19:58:15] lumen.DEBUG: -- Startup {"method":"GET","uri":"/api/API/apipipipi/1"} []
[2018-06-07 19:58:15] lumen.DEBUG: -- Shutdown {"time":"234.990[ms]","memory":"10[mb]"} []
~~~100行くらい~~~
[2018-06-07 20:03:56] lumen.DEBUG: -- Shutdown {"time":"67.988[ms]","memory":"2048[kb]"} []

100行か。。。まだ大丈夫な範囲だけど本番のログだと篦棒にある気がする。
そんなの見てたら目が痛くなりますよね。
大量のログを凝視している方を見ていると僕は思わずニヤニャ...ゲフン。胸が痛くなります。

さて次は作業しやすいように数行だけ出力してみましょう。

先頭から数行だけ出力してみる

先頭から3行だけ出力してみましょうか。
catコマンドにパイプとheadコマンドを使いますが、オプションで-nをつけて行数をそのあとに指定しましょう。

$ cat app.log | head -n3
[2018-06-07 19:58:15] lumen.DEBUG: -- Startup {"method":"GET","uri":"/api/API/apipipipi/1"} []
[2018-06-07 19:58:15] lumen.DEBUG: -- Shutdown {"time":"234.990[ms]","memory":"10[mb]"} []
[2018-06-07 19:58:16] lumen.DEBUG: -- Startup {"method":"GET","uri":"/api/API/apipipipi/2"} []

先頭から3行だけ出力されました。

必要部分のみ抽出して出力

お次はカウントに必要な情報。すなわちAPI部分のみ抜き出してみましょう。

必要な情報 → {"method":"GET","uri":"/api/API/apipipipi/x"}

awk コマンド使います。様々な使い方あるので興味ある方は調べて見てください。

$ cat app.log | head -n3 | awk '{print $6}'
{"method":"GET","uri":"/api/API/apipipipi/1"}
{"time":"234.990[ms]","memory":"10[mb]"}
{"method":"GET","uri":"/api/API/apipipipi/2"}

これで「3行のログの中の”半角スペース(タブ)で区切られた6桁目”の文字列」を出力できました。
だいぶショートボディになりましたね。

あれ、でも今回のカウント作業で使わないデータが含まれてますね。

こいつ → {"time":"234.990[ms]","memory":"10[mb]"}

欲しい行だけ抽出しましょう。grepコマンドを使います。
やり方は2パターンありますのでどちらを使っても問題ないですが、一致する行を抽出した方がきっと楽でしょう。

## 条件に一致する行を削除する方法
$ cat app.log | grep -v "memory"| head -n3 | awk '{print $6}'
{"method":"GET","uri":"/api/API/apipipipi/1"}
{"method":"GET","uri":"/api/API/apipipipi/2"}
{"method":"POST","uri":"/api/API/apipipipi/3"} 
## 条件に一致する行を抽出する方法
$ cat app.log | grep "method"| head -n3 | awk '{print $6}'
{"method":"GET","uri":"/api/API/apipipipi/1"}
{"method":"GET","uri":"/api/API/apipipipi/2"}
{"method":"POST","uri":"/api/API/apipipipi/3"}

これで不要な行を除外できました。

さらに見やすいように成形

初期よりは見やすくなりましたが、まだ無駄な情報があったりしますよね。
ぶっちゃけこのままでもカウント取れるのですが、せっかくなので成形してみましょう。

またまたawkを利用しますが、今回は少し特殊です。
特殊なawk部分だけを見てみましょう。

awk -F '/' '{for(i=2;i<NF;i++){printf("%s%s",$i,OFS="/")}print $NF}'
  • -Fオプションで区切り文字を指定
  • for文で「◯桁目〜改行文字まで」を出力
  • printf("%s%s",$i,OFS="/")で対象の桁、”/”を繋げて出力
  • print $NFで改行文字を出力

組み合わせると結果がこうなります。確認のため1行だけ出力します。

$ cat app.log | grep method| head -n1 | awk '{print $6}' |\
    awk -F'/' '{for(i=2;i<NF;i++){printf("%s%s",$i,OFS="/")}print $NF}'

api/API/apipipipi/1"}

お、だんだん良い感じになってきましたね。
※ただfor文を利用する為負荷がかかります。ログが膨大な場合は注意が必要です。

いらない文字列を除外する

末尾の「"}」が邪魔ですね。消してしまいましょう。
sedを使います。

$ cat app.log | grep method| head -n1 | awk '{print $6}' |\
    awk -F'/' '{for(i=2;i<NF;i++){printf("%s%s",$i,OFS="/")}print $NF}' |\
    sed -e "s/\"}//g"

api/API/apipipipi/1

いい感じにソートしよう

sortを使います。また、全行出力するためにheadを除いて実行!

$ cat app.log | grep method| awk '{print $6}' |\
   awk -F'/' '{for(i=2;i<NF;i++){printf("%s%s",$i,OFS="/")}print $NF}' |\
   sed -e "s/\"}//g" | sort

api/API/apipipipi/1
api/API/apipipipi/2
~~100行くらい~~
api/API/apipipipi/1
api/API/apipipipi/3

いい感じいい感じ。

ラスト!ユニークな値ごとにカウントしよう

uniqを使います。
オプション-cでカウントできます。

$ cat app.log | grep method| awk '{print $6}' |\
    awk -F'/' '{for(i=2;i<NF;i++){printf("%s%s",$i,OFS="/")}print $NF}' |\
    sed -e "s/\"}//g" | sort | uniq -c

2 api/API/apipipipi/1
3 api/API/apipipipi/2
2 api/API/apipipipi/3
1 api/API/apipipipi/8
2 api/API/apipipipi/5
20 api/API/apipipipi/11
~~数十行くらい

これでAPIごとのカウントが取得できました。

まとめ

コマンド便利。
今回紹介したコマンドですが、コピペではおそらく期待している結果は出力されないと思います。
サービスによってログの形式が異なるので、ニーズに合わせてカスタマイズしてください。

おまけ

ちょっと見やすくしてみた

$ cat app.log \
    | grep method \
    | awk '{print $6}' \
    | awk -F'/' '{for(i=2;i<NF;i++){ \
        printf("%s%s",$i,OFS="/") \
    } print $NF}' \
    | sed -e "s/\"}//g" \
    | sort \
    | uniq -c
;

一部冗長な表現があると思いますが、わかりやすく見せるために簡単に書いてます。
利用される機会があればぜひ。