[jqコマンド] jsonデータログから発生順位ランキングを取得する方法

こんにちわ。
コマンド・ワン・ライナーにかなりのこだわりがある、下駄です。
「サーバーコマンド」とかけまして、
「標準じゃない体格の人の着る服」と、ときます。
そのココロは・・・
“ls”が定番です。
難題は突然やってくる
SQLデータベースはなるべく利用したくないので、json形式でテキストログを追記形式にして溜め込んでいるんですが、
linuxの”/var/log/*”と、同じような状態になっている事は、理解しやすいかと思います。
そのjsonデータに、ログ取得した人(サイトにアクセスした人)のタイプが書き込まれてあり、タイプ別にアクセス数を取得したり、そのランキングを集計したいと、アクセス解析をやらなければいけなくなりました。
その際に、jsonデータは、jqコマンドに任せて、整形された集計データを返せばいいと考えていたのですが、思いの外この手の処理が難しい事がわかりました。
タイプ1つ1つの処理をjsonログから探して個数を出す処理はさほど難しくないのですが、
ログのボリュームが大きくなった時に、タイプ数分のjqコマンドを実行すると、単純に1回に5秒ぐらいかかってしまうと、タイプが10個あれば、10倍の処理速度が有してしまいます。
できれば、データをなめるのは、1回にとどめたいと思うのが、システムエンジニアであれば誰でもそう考えるはずです。
jqコマンドとの格闘
まず、元データは、こんな感じ。
1 2 3 4 5 6 |
{"user_type":0,"name":"red","date":"20200401"} {"user_type":1,"name":"blue","date":"20200402"} {"user_type":null,"name":"yellow","date":"20200403"} {"user_type":1,"name":"green","date":"20200404"} {"user_type":2,"name":"pink","date":"20200405"} {"user_type":1,"name":"perple","date":"20200405"} |
そして、とりあえず、user_typeのみを抜き出してみる。
1 2 3 4 5 6 7 8 |
jq --slurp --compact-output '.[] | .user_type' data.log > 0 > 1 > null > 1 > 2 > 1 |
こんな書き方もできます。
1 2 3 |
jq --slurp --compact-output 'reduce .[] as $item([]; . + [$item.user_type] )' data.log > [0,1,null,1,2,1] |
配列で返すぐらいでこの2つはさほど差がありません。
ちなみに、nullの値は、ユーザータイプ0とマージしたいという、困難仕様。
分岐処理を使うとnull問題は解決しそうだ。
1 2 3 4 5 6 7 8 |
jq --slurp --compact-output '.[] | if .user_type == 1 then 1 elif .user_type == 2 then 2 else 0 end' data.log > 0 > 1 > 0 > 1 > 2 > 1 |
これをそれぞれの値のlengthを取得して、json形式に整形しようとしたのだが、jqコマンドでは、ここに限界があるようだ。
仕方なく、別コマンドと連携してみる。
1 2 3 4 5 |
jq --slurp --compact-output '.[] | if .user_type == 1 then 1 elif .user_type == 2 then 2 else 0 end' data.log | sort | uniq -c > 2 0 > 3 1 > 1 2 |
左の数値が発生個数で、右の数値が、user_typeです。
レスのスペース文字も気になるし、なんだかjsonでも、csvでもない、この状態が気持ち悪いので、awkを使って整形
1 2 3 4 5 |
jq --slurp --compact-output '.[] | if .user_type == 1 then 1 elif .user_type == 2 then 2 else 0 end' data.log | sort | uniq -c | awk '{print $2","$1}' > 0,2 > 1,3 > 2,1 |
なんちゃってcsv形式での出力に成功です。そして、左側にuser_type、右側に発生数にして、key,valueっぽくしてみました。
でも、このままでは、ランキング結果が出せないので、ランキングに対応するには、以下のようにします。
1 2 3 4 5 |
jq --slurp --compact-output '.[] | if .user_type == 1 then 1 elif .user_type == 2 then 2 else 0 end' data.log | sort | uniq -c | sort -k 1 -nr | awk '{print $2","$1}' > 1,3 > 0,2 > 2,1 |
これで完成!
コマンド・ワンライナーの活用方法
サンプルは6件しかデータがないのですが、数万件ぐらいであれば、結構すぐに結果がでるので、簡単にランキングを集計することができますね。
ちなみに、この方法を活用すると、通常のlinuxログに対して、nginxやapacheのログからIPアドレス別アクセスランキングなどを出すこともできて、不正に大量アクセスが会った場合など、すぐに割り出すことができます。
こうした、コマンド・ワンライナーって、覚えておくと非常に仕事がはかどる仕組みなんですね。