File Coverage

blib/lib/Plack/Middleware/StackTrace/RethrowFriendly.pm
Criterion Covered Total %
statement 58 71 81.6
branch 12 16 75.0
condition 8 12 66.6
subroutine 16 18 88.8
pod 1 5 20.0
total 95 122 77.8


line stmt bran cond sub pod time code
1             package Plack::Middleware::StackTrace::RethrowFriendly;
2 7     7   3879 use strict;
  7         9  
  7         259  
3 7     7   36 use warnings;
  7         10  
  7         196  
4              
5             # core
6 7     7   40 use Scalar::Util 'refaddr';
  7         8  
  7         834  
7 7     7   4298 use MIME::Base64 qw(encode_base64);
  7         4888  
  7         486  
8              
9             # cpan
10 7     7   3906 use parent qw(Plack::Middleware::StackTrace);
  7         2057  
  7         37  
11 7     7   303786 use Try::Tiny;
  7         12  
  7         5168  
12              
13             our $VERSION = "0.02";
14              
15             sub call {
16 4     4 1 47567 my($self, $env) = @_;
17              
18 4         10 my $last_key = '';
19 4         5 my %seen;
20             local $SIG{__DIE__} = sub {
21 4     4   538 my $key = _make_key($_[0]);
22 4   50     27 my $list = $seen{$key} || [];
23              
24             # If we get the same keys, the exception may be rethrown and
25             # we keep the original stacktrace.
26 4         20 push @$list, $Plack::Middleware::StackTrace::StackTraceClass->new(
27             indent => 1,
28             message => munge_error($_[0], [ caller ]),
29             ignore_package => __PACKAGE__,
30             );
31 4         1648 $seen{$key} = $list;
32 4         8 $last_key = $key;
33              
34 4         22 die @_;
35 4         27 };
36              
37 4         6 my $caught;
38             my $res = try {
39 4     4   120 $self->app->($env);
40             } catch {
41 1     1   8 $caught = $_;
42 1         4 _error('text/plain', $caught, 'no_trace');
43 4         24 };
44              
45 4   50     77 my $trace = $seen{$last_key} || [];
46 4 100 66     26 if (scalar @$trace && $self->should_show_trace($caught, $last_key, $res)) {
47 2         21 my $text = $trace->[0]->as_string;
48 2 50       1801 my $html = @$trace > 1
49             ? _multi_trace_html(@$trace) : $trace->[0]->as_html;
50 2         10255 $env->{'plack.stacktrace.text'} = $text;
51 2         5 $env->{'plack.stacktrace.html'} = $html;
52 2 50       18 $env->{'psgi.errors'}->print($text) unless $self->no_print_errors;
53 2 100 100     29 $res = ($env->{HTTP_ACCEPT} || '*/*') =~ /html/
54             ? _error('text/html', $html)
55             : _error('text/plain', $text);
56             }
57             # break %seen here since $SIG{__DIE__} holds the ref to it, and
58             # $trace has refs to Standalone.pm's args ($conn etc.) and
59             # prevents garbage collection to be happening.
60 4         29 undef %seen;
61 4         8 undef $last_key;
62              
63 4         141 return $res;
64             }
65              
66             sub should_show_trace {
67 4     4 0 6 my ($self, $err, $key, $res) = @_;
68 4 100       9 if ($err) {
69 1         2 return _make_key($err) eq $key;
70             } else {
71 3   66     16 return $self->force && ref $res eq 'ARRAY' && $res->[0] == 500;
72             }
73             }
74              
75 1     1 0 3 sub no_trace_error { Plack::Middleware::StackTrace::no_trace_error(@_) }
76 4     4 0 16 sub munge_error { Plack::Middleware::StackTrace::munge_error(@_) }
77 3     3 0 10 sub utf8_safe { Plack::Middleware::StackTrace::utf8_safe(@_) }
78              
79             sub _make_key {
80 5     5   9 my ($val) = @_;
81 5 50       19 if (!defined($val)) {
    50          
82 0         0 return 'undef';
83             } elsif (ref($val)) {
84 0         0 return 'ref:' . refaddr($val);
85             } else {
86 5         16 return "str:$val";
87             }
88             }
89              
90             sub _error {
91 3     3   7 my ($type, $content, $no_trace) = @_;
92 3         12 $content = utf8_safe($content);
93 3 100       27 $content = no_trace_error($content) if $no_trace;
94 3         20 return [ 500, [ 'Content-Type' => "$type; charset=utf-8" ], [ $content ] ];
95             }
96              
97             sub _multi_trace_html {
98 0     0     my (@trace) = @_;
99              
100 0           require Devel::StackTrace::AsHTML;
101 0           my $msg = Devel::StackTrace::AsHTML::encode_html(
102             $trace[0]->frame(0)->as_string(1),
103             );
104              
105 0           my @data_uris = map { _trace_as_data_uri($_) } @trace;
  0            
106 0           my @links = map {
107 0           sprintf
108             '#%s',
109             $data_uris[$_],
110             $_;
111             } (0..$#data_uris);
112              
113 0           my $css = << '----';
114             body {
115             height: 100%;
116             margin: 0;
117             padding: 0;
118             }
119             div#links {
120             z-index: 100;
121             position: absolute;
122             top: 0;
123             height: 30px;
124             }
125             #content {
126             z-index: 50;
127             position: absolute;
128             top: 0;
129             height: 100%;
130             width: 100%;
131             margin: 0;
132             padding: 30px 0 4px 0;
133             box-sizing: border-box;
134             }
135             iframe#trace {
136             border: none;
137             padding: 0;
138             margin: 0;
139             height: 100%;
140             width: 100%;
141             }
142             a.selected {
143             color: #000;
144             font-weight: bold;
145             text-decoration: none;
146             }
147             ----
148              
149 0           sprintf << '----', $msg, $css, join(' ', @links), $data_uris[0];
150            
151            
152            
153             Error: %s
154            
157            
173            
174            
175            
176             Throws: %s
177            
178            
179            
180            
181            
182            
183             ----
184             }
185              
186             sub _trace_as_data_uri ($) {
187 0     0     my $html = $_[0]->as_html;
188 0           return 'data:text/html;charset=utf-8;base64,'.encode_base64($html);
189             }
190              
191             1;
192             __END__