Posted at

アクセスログから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
;

一部冗長な表現があると思いますが、わかりやすく見せるために簡単に書いてます。

利用される機会があればぜひ。