如何让Perl Carp
通过跳过(或返回)caller
的某个级别来报告来电者位置?
在MY_Package
中给出我自己的本地子:
package MY_package;
sub logme
{
# some stuff I need logme for but the stack this example suffices
carp shift;
}
现在,每当我使用MY_package::logme
时,如何(或者我可以?)让carp
跳过logme
位置,而是显示MY_package::logme
被调用的位置?< / p>
更新基于@Joshua的回答,我为了清楚起见添加了这个:
实际上,案例/问题是当我使用MY_package::logme
与另一个模块(或脚本)的另一个帮助子时。
所以,给定:
# MY_package.pm
3|
4| sub logme {
5| my $arg = shift or croak('Not enough arguments supplied');
6| print "Yay, got $arg\n";
7| }
8|
我会这样使用它:
# test_script.pl
5|
6| use MY_package;
7|
8| sub helper {
9| MY_package::logme(@_);
10| }
11|
12| helper ("balh blah blah");
...
80| helper ($foo, $bar, $baz);
...
99| helper ("I hope my question is more clear now");
鉴于上述情况以及第12,80和99行的调用,来自logme
的{{1}}和croak
(或carp
)将始终在第9行报告作为错误!我想报告从{...}调用MY_package
子的位置。
答案 0 :(得分:3)
Carp和caller都会返回调用堆栈,并且可以解析其输出以获取报告。 Carp
和carp
等cluck
例程打印到STDERR
,因此它们不能直接适用于此问题,但Carp::longmess
会返回一个字符串。调用
Carp
例程在调用它们时打印消息,不能要求它们转到上面的级别,就像caller($frameno)
一样。 (文档建议反对使用变量$Carp::CarpLevel
与Carp
如何使用调用框架混淆。)但是,我们可以解析longmess
的字符串输出
此处发布的代码使用Carp::Longmess()
,但caller
的另一个示例(也可用于此目的)除外。有关其基础知识,请查看
use Carp qw(longmess);
my @lm = split '\n', longmess();
请注意,某些调用可能会“优化掉”,因此Carp
无法找到它们。
另一方面,@call = caller($fno)
提供了一个关于框架$fno
的11长列表。如何报告子和线的细微之处。例如,这总结了对其子
my $caller = (caller(1))[3] . ' l.' . (caller)[2];
这允许人们通过遍历帧编号来“遍历堆栈”,也显示在下面的代码中。
错误跟踪很难,并且在我认为错误之后添加另一个调用会增加麻烦。这篇文章的目的只是展示一个查看调用堆栈的基本方法,并让子解析跟踪以便省略它自己。有相关的模块,但我不熟悉他们发表评论。
在下面的代码中,在main::
中调用以触发将记录错误的子,通过两个包和main传播。他们在有或没有eval
的情况下'死',尝试一个错误的open
,在一种情况下只发送一条消息(没有错误)。
使用Carp::longmess
打印这些调用以及记录器的输出。报告省略了记录器本身,就好像它来自上面的级别。还有单独的示例如何通过caller
构建完整跟踪,并通过堆栈框架级别缩进输出强>分子。
#!/usr/bin/perl
use strict;
use warnings;
$| = 1;
package LogPack;
use Carp qw(longmess);
sub logme {
my ($err, @other) = @_;
chomp($err);
my @lm = split '\n', longmess();
# longmess() return:
# first line: message passed if any, then "at pack::sub line num."
# all others: "pack::sub(args) called at sub line num"
print "Message: \"$err\":\n";
# Print first line if error message doesn't show where it was issued
# (If it comes from eval it does, otherwise it may not)
if ( $err =~ m/line \d+\./ ) {
print "$_\n" for ( map { s/\(.*\)/()/; $_ } @lm[1..$#lm] );
} else {
print "$_\n" for ( map { s/\(.*\)/()/; $_ } @lm );
}
return 1;
}
package ThrowPack;
sub throw_die_pack {
# First a separate example: get stack-trace; indent by frame number
my $rstack = main::get_call_stack();
my $tno = "\t" x @$rstack;
print $tno . "In ThrowPack::throw_die_pack()\n";
print $tno . "Example: Show stack trace. Indented by frame number.\n";
print $tno . "--- stack\n";
print $tno . "$_\n" for @$rstack;
print $tno . "---------\n";
# Now throw; it bubbles up, but handler call is outside!
print $tno . "Will cook div by zero now (1/0) ... \n";
my $res = 1/0;
}
package main;
sub err_handler { LogPack::logme(@_); return 1 }
sub call_for_msg { LogPack::logme(@_); return 1 }
sub open_nofile {
open my $fh, '<', "no_such_file.$$" or err_handler($!);
return 1;
}
sub call_err { throw_die() }
sub call_err_pack { ThrowPack::throw_die_pack() }
sub throw_die { eval { my $res = 1/0 } or err_handler($@); }
# Not necessary, showing another way of tracing the call stack
sub get_call_stack() {
my $i = 1; # omit this call from trace
my @strace;
while ( my @call = caller($i++) ) {
push @strace, "$call[1] line $call[2] in sub $call[3]";
}
return [ @strace ];
}
print "Starting main.\n";
print "1. Log a message when there is no error.\n";
call_for_msg("A string, no error.");
print "2. Call a sub that attempts to open a non-existent file ...\n";
open_nofile();
print "3. Call subs which throw 'die' and do eval ...\n";
# Need to control returns from code inside eval for 'eval { } or' to work
eval { call_err() } or err_handler($@);
print "4. Call subs which 'die' without eval ...\n";
eval { call_err_pack() } or err_handler($@);
print "Normal end.\n";
输出如下。 向右缩进的打印是通过caller
的跟踪的单独示例,由子的("\t" x
)堆栈帧编号缩进。我发现它当所有调用都以这种方式标记时,有时会有很大的帮助(想到GUI调试)。
Starting main. 1. Log a message when there is no error. Message: "A string, no error.": at rept_err.pl line 45. main::call_for_msg() called at rept_err.pl line 67 2. Call a sub that attempts to open a non-existent file ... Message: "No such file or directory": at rept_err.pl line 44. main::err_handler() called at rept_err.pl line 47 main::open_nofile() called at rept_err.pl line 70 3. Call subs which throw 'die' and do eval ... Message: "Illegal division by zero at rept_err.pl line 52.": main::err_handler() called at rept_err.pl line 52 main::throw_die() called at rept_err.pl line 50 main::call_err() called at rept_err.pl line 72 eval {...} called at rept_err.pl line 72 4. Call subs which 'die' without eval ... In ThrowPack::throw_die_pack() Example: Show stack trace. Indented by frame number. --- stack rept_err.pl line 51 in sub ThrowPack::throw_die_pack rept_err.pl line 74 in sub main::call_err_pack rept_err.pl line 74 in sub (eval) --------- Will cook div by zero now (1/0) ... Message: "Illegal division by zero at rept_err.pl line 38.": main::err_handler() called at rept_err.pl line 74 Normal end.
请注意,对没有eval
ing的die的调用没有显示超出处理程序调用点的任何内容,由eval
中的main::
触发 - 因为该调用从处理程序调用开始。错误消息仍然正确显示错误点,但没有跟踪它。这是一个重要的案例:您使用无法访问的代码(模块,库),以及未处理的die
命中您。
答案 1 :(得分:2)
使用croak
中的Carp
功能。
# TestModule.pm
3|
4| sub this_method {
5| my $arg = shift or croak('Not enough arguments supplied');
6| print "Yay, got $arg\n";
7| }
8|
现在,当您从另一个脚本调用该方法时,它将报告相对于您调用它的位置的错误,例如。这样做......
# test_script.pl
5|
6| use TestModule;
7|
8| TestModule::this_method();
9|
会产生以下错误......
Not enough arguments supplied at test_script.pl line 8.
如果你想要一个完整的堆栈跟踪,你可以选择confess
而不是croak
,这会给你这个错误......
Not enough arguments supplied at TestModule.pm line 5.
TestModule::this_method() called at test_script.pl line 8
<强>更新强>
好的,鉴于您的澄清,您真正需要做的是在助手子上捕获异常。您需要通过返回falsey值(即return;
)来传播错误,而不是在模块中捕获它。在辅助子组件中,我们可以调用Module::method() or die
并触发or
条件,因为Module::method()
返回了一个假值。
# MyPackage.pm
1| package MyPackage;
2|
3| sub logme {
4| my $arg = shift;
5| return if !$arg;
6| print "Yay, got $arg\n";
7| return 1;
8| }
9|
10| 1;
# test_script.pl
5| use MyPackage;
6| use Carp qw( longmess );
7|
8| sub helper {
9| MyPackage::logme(@_) or die(longmess('Not enough args'));
10| }
11|
12| helper();
或者......您可以选择设置全局$EVAL_ERROR
($@
)变量,而不是在脚本文件中定义错误,尽管这有时会令人不悦,因为它会覆盖该变量如果它是由另一个失败的操作设置的值。也就是说,在级联失败的情况下,你真的想知道第一次失败是什么......所以这样做只能在非常简单的函数中接受,你希望能够返回不同的错误消息。
# MyPackage.pm
1| package MyPackage;
2|
3| sub logme {
4| my $arg = shift;
5| if ( !$arg ) {
6| $@ = 'Not enough arguments supplied';
7| return;
8| }
9| print "Yay, got $arg\n";
10| return 1;
11| }
12|
13| 1;
# test_script.pl
5| use MyPackage;
6| use Carp qw( longmess );
7|
8| sub helper {
9| MyPackage::logme(@_) or die(longmess($@));
10| }
11|
12| helper();
上述两种实现都应该为您提供此错误
Not enough args at test_script.pl line 12.
此外,CPAN上还有一些模块可以为抛出和捕获异常提供更好的支持(只需搜索throw
)。我没有使用它们,所以我不能对它们发表评论,但你可能想看看它们。
<强>更新强>
比设置$@
更好的选择是在模块中创建一个变量来存储错误,以及设置/获取该错误的方法。
# MyModule.pm
6| sub error {
7| my $msg = shift;
8| state $error;
9| if ($msg) {
10| $error = $msg;
11| }
12| return $error;
13| }
14|
15| sub logme {
16| my $arg = shift;
17| if ( !$arg ) {
18| error('Not enough arguments supplied');
19| return;
20| }
21| print "Yay, got $arg\n";
22| return 1;
23| }
24|
25| 1;
# test_script.pl
8| sub helper {
9| MyPackage::logme(@_) or die( longmess( MyPackage::error() ) );
10| }
我在这里使用了state
变量,这在perl&gt;中是可用的。 5.10(通过use feature 'state'
)。如果你有一个旧的perl,$error
需要是一个全局包。
答案 2 :(得分:0)
这个问题现在有点老了,但是正确的方法是使用@CARP_NOT
。
package Helper;
use Carp;
our @CARP_NOT;
sub gonna_carp {
carp("Error");
}
1;
package Something;
use Helper;
@Helper::CARP_NOT = ("Something");
sub do_thing {
Helper::gonna_carp();
}
1;
现在,当有人呼叫Something :: do_thing时,鲤鱼将忽略Something.pm并再次向上到达呼叫者。