File Coverage

blib/lib/Catalyst/Plugin/StackTrace.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             package Catalyst::Plugin::StackTrace;
2 1     1   934 use 5.008001;
  1         4  
  1         37  
3 1     1   527 use Moose;
  0            
  0            
4             with 'MooseX::Emulate::Class::Accessor::Fast';
5             use Devel::StackTrace;
6             use HTML::Entities;
7             use Scalar::Util qw/blessed/;
8             use MRO::Compat;
9             use namespace::autoclean;
10              
11             our $VERSION = '0.12';
12              
13             __PACKAGE__->mk_accessors('_stacktrace');
14              
15             sub execute {
16             my $c = shift;
17              
18              
19             my $conf = $c->config->{stacktrace};
20              
21             return $c->next::method(@_)
22             unless defined $conf->{enable} && $conf->{enable}
23             || !defined $conf->{enable} && $c->debug;
24              
25             local $SIG{__DIE__} = sub {
26             my $error = shift;
27              
28             # ignore if the error is a Tree::Simple object
29             # because FindByUID uses an internal die several times per request
30             return if ( blessed($error) && $error->isa('Tree::Simple') );
31              
32             my $ignore_package = [ 'Catalyst::Plugin::StackTrace' ];
33             my $ignore_class = [];
34              
35             if ( $c->config->{stacktrace}->{verbose} < 2 ) {
36             $ignore_package = [
37             qw/
38             Catalyst
39             Catalyst::Action
40             Catalyst::Base
41             Catalyst::Dispatcher
42             Catalyst::Plugin::StackTrace
43             Catalyst::Plugin::Static::Simple
44             NEXT
45             Class::C3
46             main
47             /
48             ];
49             $ignore_class = [
50             qw/
51             Catalyst::Engine
52             /
53             ];
54             }
55              
56             # Devel::StackTrace dies sometimes, and dying in $SIG{__DIE__} does bad
57             # things
58             my $trace;
59             {
60             local $@;
61             eval {
62             $trace = Devel::StackTrace->new(
63             ignore_package => $ignore_package,
64             ignore_class => $ignore_class,
65             );
66             };
67             }
68             die $error unless defined $trace;
69              
70             my @frames = $c->config->{stacktrace}->{reverse} ?
71             reverse $trace->frames : $trace->frames;
72              
73             my $keep_frames = [];
74             for my $frame ( @frames ) {
75             # only display frames from the user's app unless verbose
76             if ( !$c->config->{stacktrace}->{verbose} ) {
77             my $app = "$c";
78             $app =~ s/=.*//;
79             next unless $frame->package =~ /^$app/;
80             }
81              
82             push @{$keep_frames}, {
83             pkg => $frame->package,
84             file => $frame->filename,
85             line => $frame->line,
86             };
87             }
88             $c->_stacktrace( $keep_frames );
89              
90             die $error;
91             };
92              
93             return $c->next::method(@_);
94             }
95              
96             sub finalize_error {
97             my $c = shift;
98              
99             $c->next::method(@_);
100              
101             if ( $c->debug ) {
102             return unless ref $c->_stacktrace eq 'ARRAY';
103              
104             # insert the stack trace into the error screen above the "infos" div
105             my $html = qq{
106             <style type="text/css">
107             div.trace {
108             background-color: #eee;
109             border: 1px solid #575;
110             }
111             div#stacktrace table {
112             width: 100%;
113             }
114             div#stacktrace th, td {
115             padding-right: 1.5em;
116             text-align: left;
117             }
118             div#stacktrace .line {
119             color: #000;
120             font-weight: strong;
121             }
122             </style>
123             <div class="trace error">
124             <h2><a href="#" onclick="toggleDump('stacktrace'); return false">Stack Trace</a></h2>
125             <div id="stacktrace">
126             <table>
127             <tr>
128             <th>Package</th>
129             <th>Line </th>
130             <th>File </th>
131             </tr>
132             };
133             for my $frame ( @{$c->_stacktrace} ) {
134              
135             # clean up the common filename of
136             # .../MyApp/script/../lib/...
137             if ( $frame->{file} =~ /../ ) {
138             $frame->{file} =~ s{script/../}{};
139             }
140              
141             my $pkg = encode_entities $frame->{pkg};
142             my $line = encode_entities $frame->{line};
143             my $file = encode_entities $frame->{file};
144             my $code_preview = _print_context(
145             $frame->{file},
146             $frame->{line},
147             $c->config->{stacktrace}->{context}
148             );
149              
150             $html .= qq{
151             <tr>
152             <td>$pkg</td>
153             <td>$line</td>
154             <td>$file</td>
155             </tr>
156             <tr>
157             <td colspan="3"><pre><p><code class="error">$code_preview</code></p></pre></td>
158             </tr>
159             };
160             }
161             $html .= qq{
162             </table>
163             </div>
164             </div>
165             };
166              
167             $c->res->{body} =~ s{<div class="infos">}{$html<div class="infos">};
168             }
169             }
170              
171             sub setup {
172             my $c = shift;
173              
174             $c->next::method(@_);
175              
176             $c->config->{stacktrace}->{context} ||= 3;
177             $c->config->{stacktrace}->{verbose} ||= 0;
178             }
179              
180             sub _print_context {
181             my ( $file, $linenum, $context ) = @_;
182              
183             my $code;
184             if ( -f $file ) {
185             my $start = $linenum - $context;
186             my $end = $linenum + $context;
187             $start = $start < 1 ? 1 : $start;
188             if ( my $fh = IO::File->new( $file, 'r' ) ) {
189             my $cur_line = 0;
190             while ( my $line = <$fh> ) {
191             ++$cur_line;
192             last if $cur_line > $end;
193             next if $cur_line < $start;
194             my @tag = $cur_line == $linenum ? ('<strong class="line">', '</strong>') : (q{}, q{});
195             $code .= sprintf(
196             '%s%5d: %s%s',
197             $tag[0],
198             $cur_line,
199             $line ? encode_entities $line : q{},
200             $tag[1],
201             );
202             }
203             }
204             }
205             return $code;
206             }
207              
208             1;
209             __END__
210              
211             =pod
212              
213             =head1 NAME
214              
215             Catalyst::Plugin::StackTrace - Display a stack trace on the debug screen
216              
217             =head1 SYNOPSIS
218              
219             use Catalyst qw/-Debug StackTrace/;
220              
221             =head1 DESCRIPTION
222              
223             This plugin will enhance the standard Catalyst debug screen by including
224             a stack trace of your appliation up to the point where the error occurred.
225             Each stack frame is displayed along with the package name, line number, file
226             name, and code context surrounding the line number.
227              
228             This plugin is only active in -Debug mode by default, but can be enabled by
229             setting the C<enable> config option.
230              
231             =head1 CONFIGURATION
232              
233             Configuration is optional and is specified in MyApp->config->{stacktrace}.
234              
235             =head2 enable
236              
237             Allows you forcibly enable or disalbe this plugin, ignoring the current
238             debug setting. If this option is defined, its value will be used.
239              
240             =head2 context
241              
242             The number of context lines of code to display on either side of the stack
243             frame line. Defaults to 3.
244              
245             =head2 reverse
246              
247             By default, the stack frames are shown in from "top" to "bottom"
248             (newest to oldest). Enabling this option reverses the stack frames so they will
249             be displayed "bottom" to "top", or from the callers perspective.
250              
251             =head2 verbose
252              
253             This option sets the amount of stack frames you want to see in the stack
254             trace. It defaults to 0, meaning only frames from your application's
255             namespace are shown. You can use levels 1 and 2 for deeper debugging.
256              
257             If set to 1, the stack trace will include frames from packages outside of
258             your application's namespace, but not from most of the Catalyst internals.
259             Packages ignored at this level include:
260              
261             Catalyst
262             Catalyst::Action
263             Catalyst::Base
264             Catalyst::Dispatcher
265             Catalyst::Engine::*
266             Catalyst::Plugin::StackTrace
267             Catalyst::Plugin::Static::Simple
268             NEXT
269             main
270              
271             If set to 2, the stack trace will include frames from everything except this
272             module.
273              
274             =head1 INTERNAL METHODS
275              
276             The following methods are extended by this plugin.
277              
278             =over 4
279              
280             =item execute
281              
282             In execute, we create a local die handler to generate the stack trace.
283              
284             =item finalize_error
285              
286             In finalize_error, we inject the stack trace HTML into the debug screen below
287             the error message.
288              
289             =item setup
290              
291             =back
292              
293             =head1 SEE ALSO
294              
295             L<Catalyst>
296              
297             =head1 AUTHORS
298              
299             Andy Grundman, <andy@hybridized.org>
300              
301             Matt S. Trout, <mst@shadowcatsystems.co.uk>
302              
303             =head1 THANKS
304              
305             The authors of L<CGI::Application::Plugin::DebugScreen>, from which a lot of
306             code was used.
307              
308             =head1 COPYRIGHT
309              
310             Copyright (c) 2005 - 2009
311             the Catalyst::Plugin::StackTrace L</AUTHORS>
312             as listed above.
313              
314             =head1 LICENSE
315              
316             This program is free software, you can redistribute it and/or modify it
317             under the same terms as Perl itself.
318              
319             =cut