⛏️

Splunk Webアクセスログ ステータス別集計表を作る話

2021/02/27に公開

Chartだと痒いところに手が届かない!

Webサイトのセキュリティ周りの設定をいじる際、変更後に微妙にユーザ通信の誤遮断が増えたり、、みたいなことがないかの兆候をみる必要がありまして。
遮断傾向(=返すステータスコード)をSplunkでいい感じに可視化する事になりました。

具体的には、行に接続元IPや+αの情報、列に返したステータスコードをまとめるのがゴール。

そういう可視化は、行に使う情報が接続元IPだけだったり、1つのフィールドだけであればchartを使うだけで良いのですが、、
行に使いたいフィールドが増えるとchartでは集計できなくなるので、どうしたもんか。
ということで、あれこれ試行錯誤しました。

簡単な表示でいい場合

接続元IP単位で集計するだけでいいなら、下記だけでいいのですよ。

<search>
| chart count by address, status

で、以下のようになる、と。

address 200 301 302
192.168.1.1 100 0 0
192.168.1.2 0 100 0

※以下、手元の全然ログフォーマットの違うデータで動作確認した結果を手でうつしているので、タイポしてたりしてたらすみません。。

これが、行に使いたい情報が複数になると困る。
こんな結果が欲しいのだが、、

address useragent 200 301 302
192.168.1.1 Mozilla/5.0... 80 0 0
192.168.1.1 curl ... 20 0 0
192.168.1.2 Mozilla/5.0... 0 100 0

これは、chartコマンドでは(おそらく)実施できない。第1引数に複数のフィールドを取れないので。
こんなケースを何とかする方法を、あれこれ考えてみました。

試行錯誤

1. 行の情報をconcatenateしてしまう

こんな。

<search>
| eval accessname = addess."-".useragent
| chart count by accessname, status

こーなる。

|accessname |200 |301 |302 |
|------------|----------|----|----|----|
|192.168.1.1-Mozilla/5.0... |80 |0 |0 |
|192.168.1.1-curl ... |20 |0 |0 |
|192.168.1.2-Mozilla/5.0... |0 |100 |0 |

これで事足りるだろ?と言われりゃまぁ足りるのですけど、本質的に美しくない気がしないでもない。
この処理をした後にさらにSPLをかける用途が会った時のために、もう少々調べてみる。
(現状思いつく有力なケースは、dashboardでSearchBaseを使って、一回大処理させた結果を保持しておいてパネルごとにさらに再フィルタして出し分ける時くらいでしょうか。)

2. (美しくない方法)concatenateして集計後、再分割。

こんな。

<search>
| eval accessname = addess "-" useragent
| chart count by accessname, status
| rex field=accessname "(?<address>.*)-(?<useragent>.*)"

。。。はい、useragentはかなり好き放題文字列入るわけで、"-"がフィールドの値の中に入ったら。。。。
このケースではうまくいくかもしれませんが、UA-IPの順序にしたり、他の要素が入ってきた日にゃぁ。。
正規表現でフィールド事に抽出方法を細かく指定する方法もできますが、めんどくさいし、うまくいかないケースもいくらでも思いつきますから、筋が悪い。。

3. (ダメっぽい)concatenateの代わりにマルチバリューフィールドで

だめっぽい。

<search>
| eval accessinfo = mvappend(addess, useragent)
| chart count by accessname, status

結果はこうなります。

addressinfo 200 301 302
192.168.1.1 80 0 0
192.168.1.1 20 0 0
192.168.1.2 0 100 0
Mozilla/5.0... 80 0 0
curl ... 20 0 0
Mozilla/5.0... 0 100 0

chartてmultivalueに対して処理する時は、multivalueという固まり単位で集計するのではなく、multivalueに含まれているvalue事に個別で分計してくれてしまうようです。
仮にうまく集計してくれたとしても、一回multivaueにしてしまったfieldsを再分解する良い方法も見つからなかったので、このやり方は筋が悪そうですね。。

joinを解禁

joinて処理を重くする印象があるのであまり使いたくはないのですが、、
一回2Fieldsで集計してしまって、最後に列の情報をjoinで後付けする方法です。

<search>
| eval accessname = addess."-".useragent
| chart count by accessname, status
| join accessname [<search> | eval accessname = address."-".useragent]

。。。これsearchでよっぽどうまく絞り込めてないと、大惨事になる気がする。。
しかも同じサーチ処理を2回実施していることになるので、非常に美しくない。。

chart以外を使ったら? eval {FIELD}

以下を参考にさせていただきました。
https://qiita.com/toshikawa/items/eeb99d25cb4f64f429a1

<search>
| eval S_{status} = 1
| fields address, useragent, S_*
| stats sum(*) as * by address, useragent
| foreach S_* [eval <<FIELD>> = if(isnull(<<FIELD>>), 0, <<FIELD>>)]
| eval taltal = 0 | foreach S_* [eval total = total + <<FIELD>>]

最後の2行は、空欄セルに”0”を入れるための処理と、S_*の合計値を出す処理です。

これで以下のようになる(はず)。

address useragent total S_200 S_301 S_302
192.168.1.1 Mozilla/5.0... 80 80 0 0
192.168.1.1 curl ... 20 20 0 0
192.168.1.2 Mozilla/5.0... 100 0 100 0

join使うよりは筋が良さそうに見えるけど、実行すると若干重いかも。。

脱線 foreach と FIELD

今回の用途の調査をする際、途中「foreach」を使えないかな、、と思った時期がありました。
結局勘違いだったのですが、せっかくなのでメモ。

foreachは構文として、foreach status [eval <<FIELD>> = <<FIELD>>."_column"]みたいに書くのですが、この際eval式の左辺のFIELDは対象フィールド名(status)、右辺のFIELDは対象のフィールドの値(200とか302とか)に展開されるみたいで。。
初め右辺の展開だけ見ていて、左辺も値の場合も値が展開されるのかな、、と勘違いしてました。
('200' = .. というeval式がかけるのだと思い込んでました)。

まとめ

eval {FIELD}= の方法かなぁ。。
chartに頼らんと、自分で1つ1つの手順を追いかけましょうという事で。

ちなみにこの構文の存在をしれたのが最大の成果でした。
他の用途でも使うケースがありそうですし、きちんと覚えておこう。

Discussion