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

2020年4月13日

jq テクノロジー

コマンド・ワン・ライナーにかなりのこだわりがある、ユゲタです。 「サーバーコマンド」とかけまして、 「標準じゃない体格の人の着る服」と、ときます。 そのココロは・・・ "LS"が定番です。

難題は突然やってくる

SQLデータベースはなるべく利用したくないので、json形式でテキストログを追記形式にして溜め込んでいるんですが、linuxの"/var/log/*"と、同じような状態になっている事は、理解しやすいかと思います。 そのjsonデータに、ログ取得した人(サイトにアクセスした人)のタイプが書き込まれてあり、タイプ別にアクセス数を取得したり、そのランキングを集計したいと、アクセス解析をやらなければいけなくなりました。 その際に、jsonデータは、jqコマンドに任せて、整形された集計データを返せばいいと考えていたのですが、思いの外この手の処理が難しい事がわかりました。 タイプ1つ1つの処理をjsonログから探して個数を出す処理はさほど難しくないのですが、ログのボリュームが大きくなった時に、タイプ数分のjqコマンドを実行すると、単純に1回に5秒ぐらいかかってしまうと、タイプが10個あれば、10倍の処理速度が有してしまいます。 できれば、データをなめるのは、1回にとどめたいと思うのが、システムエンジニアであれば誰でもそう考えるはずです。

jqコマンドとの格闘

まず、元データは、こんな感じ。 {"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のみを抜き出してみる。 jq --slurp --compact-output '.[] | .user_type' data.log > 0 > 1 > null > 1 > 2 > 1 こんな書き方もできます。 jq --slurp --compact-output 'reduce .[] as $item([]; . + [$item.user_type] )' data.log > [0,1,null,1,2,1] 配列で返すぐらいでこの2つはさほど差がありません。 ちなみに、nullの値は、ユーザータイプ0とマージしたいという、困難仕様。 分岐処理を使うとnull問題は解決しそうだ。 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コマンドでは、ここに限界があるようだ。 仕方なく、別コマンドと連携してみる。 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を使って整形 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っぽくしてみました。 でも、このままでは、ランキング結果が出せないので、ランキングに対応するには、以下のようにします。 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アドレス別アクセスランキングなどを出すこともできて、不正に大量アクセスが会った場合など、すぐに割り出すことができます。 こうした、コマンド・ワンライナーって、覚えておくと非常に仕事がはかどる仕組みなんですね。

このブログを検索

ごあいさつ

このWebサイトは、独自思考で我が道を行くユゲタの少し尖った思考のTechブログです。 毎日興味がどんどん切り替わるので、テーマはマルチになっています。 もしかしたらアイデアに困っている人の助けになるかもしれません。