让Carp从子助手

时间:2016-02-15 20:27:57

标签: perl

如何让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子的位置。

3 个答案:

答案 0 :(得分:3)

Carpcaller都会返回调用堆栈,并且可以解析其输出以获取报告。 Carpcarpcluck例程打印到STDERR,因此它们不能直接适用于此问题,但Carp::longmess会返回一个字符串。调用

Carp例程在调用它们时打印消息,不能要求它们转到上面的级别,就像caller($frameno)一样。 (文档建议反对使用变量$Carp::CarpLevelCarp如何使用调用框架混淆。)但是,我们可以解析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并再次向上到达呼叫者。

cpanm link