4

I'd like to have more context in error messages in cases some function has to die. In some situations I can provide it, in others I can't find a good way. For example, using the RaiseError option in connection to Postgres I get exceptions on every failure, but I can't provide more context into those errors. I have tried to use die-handler, but I can't figure out a sane way to include arguments for called subroutine, at least:

try {
  x( 'y' );
} catch {
  say "CATCHED error: $_";
};

sub _die_handler {
  my @caller = caller(1);
  say "@caller"; # how to access @_ for this frame 
  die @_;
}

sub x {
 local $SIG{__DIE__} = \&_die_handler;
 die;
}

How could I see in the example above, that x was called with 'y' as an argument?

If I have not modified @_ in my subroutine, could I access it from my _die_handler? Or could I provide some data to the handler beforehand?

Two options I see now:

  • through some global variable, which I always populate with needed data beforehand in case there may occur some error
  • using objects, with die_handler-method in it

Better ways to do it?

w.k
  • 8,218
  • 4
  • 32
  • 55
  • See [this post](https://stackoverflow.com/a/45991317/4653379) for getting stacktraces ... (there is a lot more on that out there) Something like that? – zdim Nov 18 '19 at 01:25
  • See [this post](https://stackoverflow.com/a/42183690/4653379) for a way to walk the stack and get lexicals in the frames – zdim Nov 18 '19 at 01:26

1 Answers1

5

I am not certain what "context" you want, but Carp is our friend here.

For one, by including use Carp::Always; we get the full stack backtrace printed on all errors.

If you'd rather be selective, the simplest thing is to directly use a suitable Carp routine

use warnings;
use strict;
use feature 'say';

use Carp;

eval { my $y = 10;  x($y) };
if ($@) {
    print "eval: $@";
    # print/log else as wanted, recover and proceed or exit
}

say "done";

sub x {
    local $SIG{__DIE__} = \&Carp::confess;
    # ...
    my $bad_math = $_[0] / 0;
}

The Carp::confess dies, with a full stack backtrace, but in your example that die is caught by the eval. By die-ing via confess and catching the exception you get the "call context" from confess but also retain control to proceed as you wish in the eval "catch."

This prints

eval: Illegal division by zero at error_context.pl line 18.
 at error_context.pl line 18.
        main::x(10) called at error_context.pl line 7
        eval {...} called at error_context.pl line 7
done

Without eval the program would terminate (unless there's an eval further up the stack) but we'd still get the full backtrace of the call. There are routines in Carp which don't die, and among them cluck also prints backtrace.

For a more custom handling use the $SIG{__DIE__} hook. A little snag with Carp is that the routine with backtrace which doesn't die, cluck, just prints to STDERR stream; we can't easily get that message to build it up further. The trace used to be in longmess but is not anymore and I don't think that one can get the stacktrace from Carp without die-ing.

Then use confess, which returns the trace, and wrap the call with eval

sub _die_handler {
    my $other_info = '...';
    Carp::confess($other_info . "\n" . $_[0]);
}
...
sub x { 
    local $SIG{__DIE__} = \&_die_handler;
    ...
}
...
eval { x() };
if ($@) { ... }  #-->  confess's trace with $other_info prepended 

so as you handle its die the whole message is then in $@ in your eval. For this to work you still need that eval.

If you'd rather be able to handle the exception completely in the hook see Devel::StackTrace

use Devel::StackTrace;

sub _die_handler {
    my $trace = Devel::StackTrace->new;
    # Add other info to `$trace` object, or build error object/structure 
    # Print/log/etc  $trace (or your new error structure/object), or
    #die $trace;
}

The Devel::StackTrace is of course useful also if you want to re-throw, in which case you can pass its object to die. See docs, in particular the constructor options.

A general warning: careful with $SIG{__DIE__}; it can be tricky. I'd say, better just use Carp.

Finally, if by "context" you mean more detail from the call stack, you can walk the stack by hand using caller and retrieve lexicals from each frame by PadWalker. An example in this post.

zdim
  • 64,580
  • 5
  • 52
  • 81
  • I somehow avoided `Carp` so far, but it seems to be the right tool for my problem, for the biggest part. Still, I don't see, how could I provide dynamic context for `$other_info` in die-handler? Like specific error-message for the function. – w.k Nov 18 '19 at 06:42
  • @w.k `Carp` is definitely useful and _the_ tool for this, in principle. OK -- so "_dynamic context_" and "_specific error-message_" -- what do you mean by that? What kind of things -- can you give me an example? – zdim Nov 18 '19 at 06:55
  • Like, I have initialized some basic data (`$base`) in a function to make a database query, but this database call dies. Now I need to form error-message based on `$base`. – w.k Nov 18 '19 at 07:00
  • If I understand correctly, `Padwalker` makes possible to look any lexical variable existed before `die`? Completely new tool for me. – w.k Nov 18 '19 at 07:06
  • @w.k Ah, OK, so scoop up any old variables you want (so to speak) ... so, once a `die` is thrown, you can look at either 1) whatever is seen in the scope in which the handler is, or 2) go up the call stack and see all lexicals in each frame. (Only _in that call_'s stack.) One example of precisely that is in the post linked at the very end. (You can print things out of an `END` block as well, but that's really global.) – zdim Nov 18 '19 at 07:41
  • @w.k If you really need more than this ... perhaps consider a full blown logging system, so there'd be plenty of info already logged whenever something goes down. – zdim Nov 18 '19 at 07:46
  • @zdim.. hello dear friend.. hope you are doing good!.. happy men's day!.. – stack0114106 Nov 19 '19 at 18:19
  • @stack0114106 hello hello -- very good to "see" you!! :) I hope all is very well with you :) – zdim Nov 19 '19 at 18:20
  • @stack0114106 ( I didn't even know about "men's day"! ha! :) – zdim Nov 19 '19 at 18:22
  • thank you.. now I'm moving towards clouds and containers..not able to spend time on SO.. – stack0114106 Nov 19 '19 at 18:22
  • @stack0114106 oh, OK -- that's interesting. Yeah, SO (quietly) takes time. It's been fun and I learned a lot but ... I reduced activity, and will reduce it further – zdim Nov 19 '19 at 18:24
  • yes.. me too!.. it has been a good learning in the past.. the work keeps me busy all the time.. cloud,containers, spark, etc... – stack0114106 Nov 19 '19 at 18:26