私の perl スクリプトには多くのサブルーチンがあります。サブルーチンごとにログを作成したいです。つまり、サブルーチンが正常に機能したかどうか、または失敗した場合はどこで失敗したかをログに記録します。ロジック フラグを維持する必要があるため、フラグ値に基づいてサブルーチン ログが作成されます。私はperlを初めて使用するので、誰かが同じ例を教えてくれますか?
2 に答える
あなたがやろうとしていることは、ロギングステートメントを手動で挿入することによって達成できます:
use constant LOG => 1;
sub foo {
debug 'BEFORE', 'main::foo', @_ if LOG; # gets optimized away if LOG is false
do stuff;
debug 'AFTER', 'main::foo', if LOG; # the same
return $things;
}
debug
(ロギングを行う関数であると仮定)
ただし、特定のケースでは自動化できます。特に、名前付きサブルーチンごとにロギング ラッパーを追加できます。これは、シンボル テーブルであるパッケージ stashを介したメタプログラミングとして行います。
stash は、 のような名前の大きなハッシュ%main::
です。末尾の二重コロンに注意してください。これは、キーの固定セットを持つハッシュであるglobを保持します。彼らは*
印章を持っています。CODE
グロブのエントリには、コード参照が保持されます。
次のようなコードエントリを保持するスタッシュのすべてのグロブを選択できます
my $stash = \%main::;
my @interesting_globs = grep *$_{CODE}, values %$stash;
参照をグロブに割り当てることができます。これにより、グロブに正しいスロットが設定されます。例えば、
sub foo { say 1 }
とほぼ同じものです
BEGIN {
*foo = sub { say 1 };
}
これで、ロギングを行うラッパーで元のサブをラップできます。
for my $glob (@interesting_globs) {
my $code = *$glob{CODE}; # store the coderef in a lexical variable
no warnings 'redefine';
*$glob = sub {
debug 'BEFORE', $glob, @_ if LOG;
my @return_value = wantarray ? &$code : scalar &$code;
debug 'AFTER', $glob, @return_value if LOG;
return wantarray ? @return_value : $return_value[0];
}
}
これwantarray
により、内側のサブルーチンが正しいコンテキスト (リスト コンテキスト/スカラー コンテキスト) で呼び出されるようになります。ただし、無効なコンテキストはチェックしません。(かっこがないことに注意してください) は、 or&$code
の派手な言い方です。$code->(@_)
&$code(@_)
すべてのサブルーチンがコンパイルされた後、サブルーチンが装飾されることが重要です。したがって、INIT
メインのコンパイル段階の後、通常の実行が開始される前に実行されるブロック内で実行する必要があります。
このソリューションにはいくつかの欠点があります。
- 名前付きサブスクでのみ機能しますが、匿名サブスクでは機能しません。
- また、デフォルトでインポートされたサブを装飾します。
- それ以上のフィルターがなければ、すべてのサブを飾ります。
- void コンテキストを元のコードに伝播しません。
より良い解決策はsubroutine attributesを使用することですが、セットアップが少し難しいです。属性は、コンパイル時に実行され、メタデータを伝達できるハンドラーです。たとえばsub foo :log_this { ... }
では、log_this
ハンドラが呼び出されます。
完全な例:
$ perl -E'
sub foo {say "@_"};
sub bar { foo(0, @_, "inf") }
INIT{
for my $glob (grep *$_{CODE}, values %main::){
my $orig = *$glob{CODE};
*$glob = sub {
say "BEFORE $glob: @_";
my @ret = $orig->(@_); # this demo misses context handling
say "AFTER $glob: @ret";
@ret;
};
}
}
bar(1,2,3)'
BEFORE *main::bar: 1 2 3
BEFORE *main::foo: 0 1 2 3 inf
0 1 2 3 inf
AFTER *main::foo: 1
AFTER *main::bar: 1