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

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

## 先頭から数行だけ出力してみる
先頭から３行だけ出力してみましょうか。
`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"} []
```
先頭から３行だけ出力されました。

## 必要部分のみ抽出して出力
お次はカウントに必要な情報。すなわち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"}
```

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

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

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

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

```
## 条件に一致する行を削除する方法
$ 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`で改行文字を出力

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

```
$ 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
;
```

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