diff --git a/dist.ini b/dist.ini index 582adab15..5ea55c9dc 100644 --- a/dist.ini +++ b/dist.ini @@ -55,6 +55,7 @@ Moo::Role = 0 Role::Tiny = 2.000000 MooX::Types::MooseLike = 0 Carp = 0 +Devel::StackTrace = 0 Digest::SHA = 0 Exporter = 5.57 Encode = 0 diff --git a/lib/Dancer2/Core/App.pm b/lib/Dancer2/Core/App.pm index 46394b4ef..e959853c2 100644 --- a/lib/Dancer2/Core/App.pm +++ b/lib/Dancer2/Core/App.pm @@ -9,6 +9,7 @@ use Return::MultiLevel (); use Safe::Isa; use Sub::Quote; use File::Spec; +use Devel::StackTrace; use Plack::Middleware::FixMissingBodyInRedirect; use Plack::Middleware::Head; @@ -1394,9 +1395,18 @@ sub _dispatch_route { return $self->_prep_response( $response ); } + my $trace; $response = eval { + local $SIG{__DIE__} = sub { + my $end_trace; + $trace = Devel::StackTrace->new( + skip_frames => 1, + frame_filter => sub { $end_trace = 1 if $_[0]{caller}[0] eq 'Dancer2::Core::Route'; !$end_trace }, + ); + die @_; + }; $route->execute($self) - } or return $self->response_internal_error($@); + } or return $self->response_internal_error($@, $trace); return $response; } @@ -1419,7 +1429,7 @@ sub _prep_response { } sub response_internal_error { - my ( $self, $error ) = @_; + my ( $self, $error, $trace ) = @_; $self->log( error => "Route exception: $error" ); $self->execute_hook( 'core.app.route_exception', $self, $error ); @@ -1428,9 +1438,10 @@ sub response_internal_error { local $Dancer2::Core::Route::RESPONSE = $self->response; return Dancer2::Core::Error->new( - app => $self, - status => 500, - exception => $error, + app => $self, + status => 500, + exception => $error, + (stack_trace => $trace)x!! $trace, )->throw; } diff --git a/lib/Dancer2/Core/Error.pm b/lib/Dancer2/Core/Error.pm index 451e1e455..6707f66fa 100644 --- a/lib/Dancer2/Core/Error.pm +++ b/lib/Dancer2/Core/Error.pm @@ -7,6 +7,7 @@ use Dancer2::Core::Types; use Dancer2::Core::HTTP; use Data::Dumper; use Dancer2::FileUtils qw/path open_file/; +use Devel::StackTrace; use Sub::Quote; has app => ( @@ -230,6 +231,13 @@ has content => ( builder => '_build_content', ); +has stack_trace => ( + is => 'ro', + isa => InstanceOf['Devel::StackTrace'], + lazy => 1, + default => sub { Devel::StackTrace->new(ignore_package => __PACKAGE__) }, +); + sub _build_content { my $self = shift; @@ -327,64 +335,37 @@ sub backtrace { } $message ||= 'Wooops, something went wrong'; - $message = '
' . _html_encode($message) . ''; + my $html = '
' . _html_encode($message) . "\n"; # the default perl warning/error pattern - my ( $file, $line ) = ( $message =~ /at (\S+) line (\d+)/ ); - + my ($file, $line) = $message =~ /at (\S+) line (\d+)/; # the Devel::SimpleTrace pattern - ( $file, $line ) = ( $message =~ /at.*\((\S+):(\d+)\)/ ) - unless $file and $line; + ($file, $line) = $message =~ /at.*\((\S+):(\d+)\)/ unless $file and $line; # no file/line found, cannot open a file for context - return $message unless ( $file and $line ); + return $html unless $file and $line; # file and line are located, let's read the source Luke! - my $fh = eval { open_file( '<', $file ) } or return $message; + my $fh = eval { open_file('<', $file) } or return $html; my @lines = <$fh>; close $fh; - my $backtrace = $message; - - $backtrace - .= qq|
|; + $html .= qq|$file around line $line|; - $line--; - my $start = ( ( $line - 3 ) >= 0 ) ? ( $line - 3 ) : 0; - my $stop = - ( ( $line + 3 ) < scalar(@lines) ) ? ( $line + 3 ) : scalar(@lines); + # get 5 lines of context + my $start = $line - 5 > 1 ? $line - 5 : 1; + my $stop = $line + 5 < @lines ? $line + 5 : @lines; - for ( my $l = $start; $l <= $stop; $l++ ) { - chomp $lines[$l]; + $html .= qq|
| $l | " . _html_encode($lines[$l - 1]) . " |
|---|