たごもりすメモ

コードとかその他の話とか。

カスタマイズ済みのApacheログ書式もパースする Apache::Log::Parser の話

さて、Perlといえばテキスト処理、テキスト処理といえばPerlですね。そしてこのビッグでデータな現代においてテキスト処理といえばログの処理に決まっています。
ログの処理といってもいろいろですが、もちろん強く逞しく生きる現代っ子の我々は以下のようなログを扱います。

  • Apacheのログ
  • Apacheのログなんだけどいくつかの書式が混ざってたりする
  • combined に当然いろいろデータが足してある
  • LTSVってなんですか?

そのような素敵な問題を解決するためのモジュールがCPANにあります。Apache::Log::Parserです。
あっ、そのページはダメ、こっち、こっちな。

Apache::Log::Parser

Apache::Log::Parserは内部に2種類の解析器を備えており、パーサを初期化するときにどちらかを選びます。それぞれ以下のように動作します。

  • fast mode
    • Apache common や combined ベースの書式(それらの後にフィールドを足したもの)に対してマッチ
    • 比較的高速に動作する
    • そのため正しくパースできないケースがごく一部のマイナーケースに存在する*1
  • strict mode
    • どのような書式のログに対してもマッチ
    • ただし少し遅く動作する
    • タブ区切りなどのログでも対応可能
    • チェッカを渡し、正しくパースできたかどうかをパース結果を見て判定させられる

どちらのモードでも複数の書式を設定可能で、パーサは設定されたものの中で最も長くパースに成功するものにマッチしたものとしてパース結果を返します。
使うときの例はだいたいこんな感じですね。

my @customized_fields = qw( rhost logname user datetime request status bytes referer agent vhost usertrack mobileid request_duration );

my $custom_fast = Apache::Log::Parser->new( fast => [\@customized_fields], 'combined', 'common'] );

my $custom_strict = Apache::Log::Parser->new( strict => [
    [" ", \@customized_fields, sub{my $x=shift;defined($x->{vhost}) and defined($x->{usertrack}) and defined($x->{mobileid})}],
    'combined',
    'common',
]);

$custom_fast->parse($log_line); #=> hashref of parse result, keys are @customized_fields
$custom_strict->parse($log_line); # same with $custom_fast

大抵は fast mode でパースできるはずなので、fastで試してダメそうならstrictでもう一度やってみる、くらいでいいと思います。CPANには書式をカスタマイズしたログをパースできるモジュールがほとんどなく、それだけでもこのモジュールの価値はあるかなあ、と思います。

速度

速度は大事です。特に、ちょっと秒間10万行ばかりよろしく、とか言われた時とかに大事ですね。
で、Apache::Log::Parserですが、だいたい以下のような速度になっております。

  • strict は fast の約3〜4倍遅い
  • fast は、各ログ書式専用に書かれた正規表現一発*2の約3〜4倍遅い

こちらが専用の正規表現および fast/strict と ApacheLog::Parser というCPANモジュールでcombinedのログに対してベンチをとった結果です。default_* は combined が試行最長パターンのパーサ設定で custom_* は combined よりも長い書式でのパースも試すパーサ設定です*3

                     Rate custom_strict default_strict custom_fast default_fast apachelog_parser combined_regex
custom_strict      8799/s            --            -1%        -72%         -80%             -80%           -94%
default_strict     8868/s            1%             --        -71%         -79%             -80%           -94%
custom_fast       31012/s          252%           250%          --         -28%             -30%           -80%
default_fast      42986/s          389%           385%         39%           --              -3%           -73%
apachelog_parser  44480/s          406%           402%         43%           3%               --           -72%
combined_regex   157284/s         1687%          1674%        407%         266%             254%             --

まあ遅いじゃないかと言われるかもしれませんが、でも、5種類とかそれ以上ログ書式が混ざっているときにですね、いちいち全部正規表現とか書いてらんない、というかメンテできないんですよ。しょうがないじゃないですか。カスタマイズ可能な機構になっていながら combined 専門にしか対応していない ApacheLog::Parser とほとんど速度が変わらないことを褒めるべき。キリッ。

結論

みんなLTSVでログを吐きましょう。

さいごに

このエントリは Perl Advent Calendar 2013 の 12月9日 (……26時!) の記事でした。
http://qiita.com/advent-calendar/2013/perl

明日がいない!

*1:Apacheを通っていれば存在しないはずのクォートが行われていないログや不正なクォートが行われたログなど

*2:ただしバックトラックなし

*3:つまり毎回パース失敗してフォールバックしてる