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

2009-07-05

[perl] Danga::Socket::Callback を使ったキャラクタ echo サーバー

Perl で epoll, kqueue などを使った、イベントドリブンで効率的なネットワークアプリケーションを作成できる Danga::Socket を、手軽に扱えるようにした、Danga::Socket::Callback について、日本語の情報が少ないので書いてみました。

TokuLog 改めB日記 の tokuhirom さんの書かれた、 Danga::Socket::Callback がクールな件について
この記事のコードでは、on_write_ready コールバックが、それが不必要になったあと (書きこみバッファが空になった後) も延々と呼び出されてしまい、CPU使用率が 100% にはりつき、無駄に資源を消費するので、地球環境に良くないコードです。

そもそも、on_write_ready などの、書きこみバッファの減少をとらえる仕組みは、普通のアプリケーションには不要だと考えます。

2012/11/25 追記
この説明は不適切で、読み取り側ストリームが書き込み側ストリームが許容する書き込み速度よりゆっくりな場合のみ、書き込みバッファのことを気にすることなく write できるという条件付きの話でした。

例えば上りが速く下りが遅いクライアントがエコーサーバーに上りが飽和するほど入力すると書き込みバッファがあふれる状況も十分考えられます。

use strict;
use warnings;
use Danga::Socket::Callback;
use IO::Socket::INET;

my $LISTENING_PORT = shift || 17000;

my $sock_server = IO::Socket::INET->new(
    Proto     => 'tcp',
    LocalPort => $LISTENING_PORT,
    Listen    => 16,
    ReuseAddr => 1,
) or die "Cannot bind $LISTENING_PORT: $@\n";

print "Start listening on port: $LISTENING_PORT\n";

$sock_server->blocking(0);

Danga::Socket::Callback->new(
    handle => $sock_server,
    on_read_ready => sub {
        my $dsock_server = shift;
        my $sock_client = $dsock_server->sock()->accept() or return;
        $sock_client->blocking(0);

        Danga::Socket::Callback->new(
            handle => $sock_client,
            on_read_ready => sub {
                my $dsock_client = shift;

                my $buf = $dsock_client->read(4096);

                unless ($buf) {
                    print "close\n";
                    $dsock_client->close();
                    return;
                }

                my $str = $$buf;
                $str =~ s/(\W)/'%' . unpack('H2', $1)/ge;
                print "recv: $str\n";

                $dsock_client->write($buf);
            },
        );
    },
);

Danga::Socket->EventLoop();

ポイント
待ち受け用のソケットは最初から Non-Blocking モードで始めるのではなく、Blockingモードで始めたあと、Non-Blocking モードに移行した方が接続時のエラー検出が簡単にできる。

Danga::Socketの->read() が返してくれるものは、文字列の参照なので、その点注意する。