2009-07-17

[perl] 標準出力と標準エラー出力をファイルに記録しつつ出力する

必要になったので調べていたのですが、himazu blog さんの、STDOUTとSTDERRをファイルにも出力するようにする では、解決していないようなので自分で考えてみました。

これにより、本文中の print 文には一切触れずに、STDOUT (標準出力) と STDERR (標準エラー出力) をこっそりロギングする仕組みを追加でき、STDERR に出る warning も捕捉できるので、とても便利だと思います。

この機能の実現に必要な、ファイルハンドルを多重化するしくみは、CPAN モジュールで、IO::TeeFile::Tee が見つかったのですが、どうやら、File::Tee が簡単に使えそうです。

ひとつ残念なところは、File::Tee は Windows に対応していない点です。

use File::Tee qw(tee);

open(my $log_fh, '>>', 'output.log');
tee(STDOUT, $log_fh);
tee(STDERR, $log_fh);

print STDERR "stderr\n";
print "stdout\n";
warn "warning";
system("echo system stdout");
system("echo system stderr >&2");

せっかく前処理部分を書けるので、無駄にタイムスタンプ機能などを追加してみました。

use DateTime;
use DateTime::TimeZone;
use File::Tee qw(tee);

my $tzlocal = DateTime::TimeZone->new(name => 'local');

sub get_prepender {
    my $label = shift;

    return sub {
        my $data = shift;
        return join(' ',
            DateTime->now()->set_time_zone($tzlocal)->datetime(),
            $label, $data
        );
    };
}

open(my $log_fh, '>>', 'output.log');
tee(STDOUT, {open => $log_fh, preprocess => get_prepender('STDOUT')});
tee(STDERR, {open => $log_fh, preprocess => get_prepender('STDERR')});

print "stdout\n";
print STDERR "stderr\n";
warn "warning";
system("echo system stdout");
system("echo system stderr >&2");

記録例 (output.log)

2009-07-17T00:12:53 STDOUT stdout
2009-07-17T00:12:53 STDOUT system stdout
2009-07-17T00:12:53 STDERR stderr
2009-07-17T00:12:53 STDERR warning at redirect.pl line 29.
2009-07-17T00:12:53 STDERR system stderr