コールトレースを追うデバッグ用モジュール

Perl スクリプトをトレースするには Perl デバッガを使ってもいいですが,全体的な実行フローを追いたい場合 Devel::Trace が使えます。

たとえば

#!/usr/bin/perl

use strict;
use warnings;

my $i = 0;

sub foo {
    bar();
    baz();
}

sub bar {
    baz();
}

sub baz {
    $i ++;
}

foo();

みたいなスクリプトがあるときに,Devel::Trace でトレースするには下記のようにスクリプトを実行します。

$ perl -d:Trace test.pl

実行結果は,

>> test.pl:6: my $i = 0;
>> test.pl:21: foo();
>> test.pl:9:     bar();
>> test.pl:14:     baz();
>> test.pl:18:     $i ++;
>> test.pl:10:     baz();
>> test.pl:18:     $i ++;

のようになります。


んが,これは実行している行がだぁーっと出るのでみにくい。


Devel::Trace のソースを読んだら,こんな簡単なしくみなのかとびっくりしたので,コールスタックの深さがわかるようなデバッグ用モジュールを書いてみました。

package Devel::Tracer;

use strict;
use warnings;

my $fh;

sub DB::DB {
    my ($p, $f, $l, $s) = caller(0);

    return if $p eq __PACKAGE__;

    # caller(1)[3] is more suitable than caller(0)[3] ('DB::DB')
    (undef, undef, undef, $s) = caller(1);
    $s = q{} if ! defined $s;
    $s =~ s{ \A $p :: }{}xms;

    my $level = 0;
    while (defined (caller($level + 1))) {
        $level ++;
    }

    # my $source = do { no strict 'refs'; \@{"::_<$f"} };
    # my $c = $source->[$l];
    # chomp $c;
    # $c =~ s{"}{\\"}gxmso;

    print {$fh} ">" x ($level + 1) . " $p($l): $s\n";
}

sub import {
    my $package = shift;

    if (@_) {
        my $file = shift;
        open $fh, '>', $file
            or die $!;
    }
    else {
        $fh = \*STDERR;
    }
}

END {
    close $fh;
}

1;
$ perl -d:Tracer test.pl

このように実行すると,

> main(6): 
> main(21): 
>> main(9): foo
>>> main(14): bar
>>>> main(18): baz
>> main(10): foo
>>> main(18): baz

のように左側の > の数が深さをあらわします。

オプションを

$ perl -d:Tracer=trace.out test.pl

のように指定すると,STDERR の代わりに指定したファイルに出力します。


まぁこのモジュール使わなくても,同じようなモジュールが Devel::Trace*名前空間にいくつかあるのですが,Perl-d スイッチに対応しているのが Devel::TraceDevel::TraceCalls くらいなので,難しいしくみでもないですし,自分で書いてみるのもいいんではないでしょうか。HTML で吐くようにするとかね。