Log::Analyze 書いた

ログ集計のタスクって人によって色々なやり方をしているみたいですね。

結構目にするのが「まずログファイルをすべてDBに格納し、SQLでgroup by使って集計させる」というパターン。

これだとログファイルが巨大な場合に、まずDBに入れるだけでディスクIOのために長時間またされるし、さらにSQLでジャカジャカ読み出すのも、よほど上手にインデックス張っておかないとさらに時間が掛かってしまいますよね。

僕の場合は、ログファイルを上から順にパースしていって、条件によって足し算なりカウントアップなししながら、その集計結果をメモリ内でツリー形状に蓄積していって、最後まで計算が終わったらそのツリーの枝葉の部分(集計結果の部分)をピックアップしてDBに書き込んだりファイルに書き出したりするのが好きです。

もうちょい簡単に言えば、要は多段のハッシュにインクリメントとかしながらデータを格納していって、最後にそのハッシュをレコードにシリアライズしてストレージに書き込んでいく、といったところですね。

で、意外と面倒なのが「多段のハッシュを書く」のと「多段のハッシュツリーをシリアライズする」処理ですね。

$hash{$field[0]}{$field[1]}{$field[3]}{$field[4]}{$field[8]}++;

とか、見ただけで気持ち悪くなるし、

while(my($key, $value1) = %hash){
    while(my($key, $value2) = %$value1){
        while(my($key, $value3) = %$value2){
            while(my($key, $value4) = %$value3){
                 .....
            }
        }
    }
}  

こんなのかわいそう過ぎてみてられないですよね。。


なのでそれをLog::Analyzeというモジュールにまとめました。

Log::Analyze
http://search.cpan.org/~miki/Log-Analyze/


使い方は超簡単。

use Log::Analyze;
  
my $parser = Log::Analyze->new;
  
while(<LOG>){
    chomp $_;
    my @f = split(/\t/, $_);
  
    # 集計軸にしたいフィールド
    my $p = [ $f[0], $f[3], $f[5] ];
    
    $parser->analyze($p, "count");
}
  
my $array_ref = $parser->matrix;
print join("\t", $_), "\n" for(@$array_ref);

これだけでOKです。上の例だと「ログのフィールドの0,3,5番目を集計軸としてcountする」といったタスクがログを1行づつまわしていく中で勝手に実行されます。

最後にmatrixメソッドで2次の配列リファレンス、ようは「行列」が得られるので、これをデリファレンスしてやれば「集計結果」が得られます。

その他の集計

Log::Analyzeではcount以外にsumもできます。この場合は

$parser->analyze( [集計軸フィールド], "sum" => 足し算したいフィールド );

のように書きます。

もっと複雑なことをしたい場合にはcoderefも置けます。こんなかんじ。

$parser->analyze([集計軸フィールド], $coderef => $argsref);

まとめ

はい。まとめです。

日々ログデータなどを扱ってるエンジニアの人たちって、しばしば「○○別の××別の△△なデータって出せないですか?」とかよく言われますよね。しかも「急ぎで」とか。

そんなことを言われた時に、こいつを使ってやると、さっくりと涼しげにデータが出せるようになります。モテます。

そんなあなたの横顔をみて「まぁなんて仕事ができる人」と勘違いしてくれる人があわられることを祈りつつ。

この夏、おすすめです。