@kyanny's blog

My thoughts, my life. Views/opinions are my own.

Regexp::Log::Common の使い方を覚えた

これでもう access_log をパースするときに正規表現をどう書くべきかで悩む必要がなくなった。

普通に LogFormat combined なファイルをパースするときはこれでいい。

my $foo = Regexp::Log::Common->new(
    format  => ':extended',
    capture => [qw(host rfc authuser date request status bytes referer useragent)],
);
my $re = $foo->regexp;
my @fields = $foo->capture;

while (<>) {
    my %data;
    @data{@fields} = /$re/;

    # do something
}

ちょっとカスタマイズされた場合もこんな風にして拡張が可能。

my $format = q[%host %rfc %authuser %date %request %status %bytes %referer %useragent %elapsed];
my $capture = [qw(host rfc authuser date request status bytes referer useragent elapsed)];

$Regexp::Log::Common::REGEXP{'%elapsed'} = '(?#=elapsed)(\d+)(?#!elapsed)';

my $foo = Regexp::Log::Common->new(
    format  => $format,
    capture => $capture,
);
my $re = $foo->regexp;
my @fields = $foo->capture;

while (<>) {
    my %data;
    @data{@fields} = /$re/;

    # do something
}

(?#...) っていうのを Regexp::Log::Common と Regexp::Log のソースで初めて見た。 Perl5 の拡張正規表現の一つで、コメントらしい。 //x で複数行にわかる正規表現を書く場合には直接各行にかいた # より後ろがコメントになるけど、一行で正規表現を書く場合もこの構文でコメントを入れられる。

Regexp::Log はさらにこれを応用して使ってるようで、 (?#=XXX) と(?#!XXX) で囲んだ範囲がマッチすると XXX というキーでもってあとでキャプチャできるようにしているらしい。ちらっとみただけではどこで具体的にそれをやってるかよくわからなかったけど、そこもやっぱり正規表現でどうにかしてるんだろう。

なので、下の例みたいに新しいキャプチャ用フィールドを追加したいときなんかは、 (?#=epalsed)(\d+)(?#!elapsed) と書く。すでに定義済みの %useragent とかを書き換えたいならば $Regexp::Log::Common::REGEXP{'useragent'} = ''; とかすればいいだろう。


Apache の access_log にマッチする正規表現はぐぐるといろいろ出てきて、主に速度の優劣が問題視されていた。 Apache Combined Log を効率的にパースする正規表現メモ, Apache Combined Log 解析正規表現ベンチマークの補足 (2007/10/05), Apache Combied Log を解析する CPAN .. - [ぴ](2007-09-07) あたりの一連のエントリだと結局 (.*?) をなるべく多く使ったほうが早い、という結論だったようだ。

Regexp::Log::Common はどういう正規表現を使っているか、というと、

$ perl -MRegexp::Log::Common -e "print Regexp::Log::Common->new( format => ':extended', capture => [qw(host rfc authuser date request status bytes referer useragent)] )->regexp, qq{\n}"
(?-xism:^(\S+) (.*?) (.*?) (\[(?:\d{2}\/\w{3}\/\d{4}(?::\d{2}){3} [-+]\d{4})\]) (\"(?:.*?)\") (\d+) (-|\d+) (\"(?:.*?)\") (\"(?:.*?)\")$)

という感じで、 \d{2} とかかなり真面目にやっている。 .*? とどっちが早いのかはよくわからない。けど、10倍遅いということもないだろうから、今後はなるべくこのモジュールを利用して、自分で正規表現を書かないようにしていこう。