Reputation: 8386
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:
Better ways to do it?
Upvotes: 4
Views: 208
Reputation: 66964
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.
Upvotes: 5