File Coverage

lib/Catalyst/Plugin/ErrorCatcher.pm
Criterion Covered Total %
statement 218 244 89.3
branch 64 102 62.7
condition 34 52 65.3
subroutine 22 22 100.0
pod 9 9 100.0
total 347 429 80.8


line stmt bran cond sub pod time code
1             package Catalyst::Plugin::ErrorCatcher;
2             $Catalyst::Plugin::ErrorCatcher::VERSION = '0.0.8.18';
3             {
4             $Catalyst::Plugin::ErrorCatcher::DIST = 'Catalyst-Plugin-ErrorCatcher';
5             }
6             # ABSTRACT: Catch application errors and emit them somewhere
7 12     12   20581126 use Moose;
  12         1570755  
  12         113  
8             with 'Catalyst::ClassData';
9 12     12   90764 use 5.008004;
  12         46  
  12         560  
10 12     12   10887 use File::Type;
  12         158836  
  12         1179  
11 12     12   162 use IO::File;
  12         27  
  12         2562  
12 12     12   2722 use Module::Pluggable::Object;
  12         25996  
  12         1856  
13              
14             __PACKAGE__->mk_classdata('_errorcatcher');
15             __PACKAGE__->mk_classdata('_errorcatcher_msg');
16             __PACKAGE__->mk_classdata('_errorcatcher_cfg');
17             __PACKAGE__->mk_classdata('_errorcatcher_c_cfg');
18             __PACKAGE__->mk_classdata('_errorcatcher_first_frame');
19             __PACKAGE__->mk_classdata('_errorcatcher_emitter_of');
20              
21             __PACKAGE__->meta()->make_immutable();
22 12     12   101 no Moose;
  12         23  
  12         125  
23              
24             sub setup {
25 9     9 1 3035061 my $c = shift @_;
26              
27             # make sure other modules (e.g. ConfigLoader) work their magic
28 9         88 $c->maybe::next::method(@_);
29              
30             # store the whole config (so plugins have a method to access it)
31 9         1052113 $c->_errorcatcher_c_cfg( $c->config );
32              
33             # get our plugin config
34 9   100     894 my $config = $c->config->{'Plugin::ErrorCatcher'} || {};
35              
36             # set some defaults
37 9   50     789 $config->{context} ||= 4;
38 9   50     107 $config->{verbose} ||= 0;
39 9   50     65 $config->{always_log} ||= 0;
40 9   100     56 $config->{include_session} ||= 0;
41 9   50     69 $config->{user_identified_by} ||= 'id';
42            
43             # start with an empty hash
44 9         128 $c->_errorcatcher_emitter_of({});
45              
46             # store our plugin config
47 9         319 $c->_errorcatcher_cfg( $config );
48              
49             # some annoying emitters want a new() to be called
50 9         256 $c->emitters_init;
51              
52 9         70 return 1;
53             }
54              
55             # implementation borrowed from ABERLIN
56             sub finalize_error {
57 22     22 1 1465809 my $c = shift;
58 22         170 my $conf = $c->_errorcatcher_cfg;
59              
60             # finalize_error is only called when we have $c->error, so no need to test
61             # for it
62              
63             # this should let ::StackTrace do some of our heavy-lifting
64             # and prepare the Devel::StackTrace frames for us to re-use
65 22         601 $c->maybe::next::method(@_);
66              
67             # don't run if user is certain we shouldn't
68 22 50 66     263695 if (
69             # the config file insists we DO NOT run
70             defined $conf->{enable} && not $conf->{enable}
71             ) {
72 0         0 return;
73             }
74              
75             # run if required
76 22 50 66     345 if (
      33        
      66        
77             # the config file insists we run
78             defined $conf->{enable} && $conf->{enable}
79             or
80             # we're in debug mode
81             !defined $conf->{enable} && $c->debug
82             ) {
83 22         348 $c->my_finalize_error;
84             }
85              
86 22         94 return;
87             }
88              
89             sub my_finalize_error {
90 22     22 1 52 my $c = shift;
91 22         147 $c->_keep_frames;
92 22         150 $c->_prepare_message;
93 22         155 $c->_emit_message;
94 22         53 return;
95             }
96              
97             sub emitters_init {
98 9     9 1 25 my $c = shift;
99              
100 9 100       41 if (defined (my $emit_list = $c->_errorcatcher_cfg->{emit_module})) {
101 8         202 my @emit_list;
102             # one item or a list?
103 8 50 33     139 if (defined ref($emit_list) and 'ARRAY' eq ref($emit_list)) {
    50          
104 0         0 @emit_list = @{ $emit_list };
  0         0  
105             }
106             elsif (not ref($emit_list)) {
107 8         34 @emit_list = ( $emit_list );
108             }
109              
110 8         29 foreach my $emitter (@emit_list) {
111 8         103 $c->_require_and_new($emitter);
112             }
113             }
114             }
115              
116             sub _require_and_new {
117 8     8   22 my $c = shift;
118 8         20 my $emitter_name = shift;
119 8         17 my $output = shift;
120 8         38 my $conf = $c->_errorcatcher_cfg;
121              
122             # make sure our emitter loads
123 8         1114 eval "require $emitter_name";
124 8 50       24153 if ($@) {
125 0         0 $c->log->error($@);
126 0         0 return;
127             }
128             # make sure it "can" new()
129 8 100       206 if ($emitter_name->can('new')) {
130 2         5 my ($e, $e_cfg);
131 2   100     26 $e_cfg = $c->config->{$emitter_name} || {};
132              
133 2         219 eval {
134 2         18 $e = $emitter_name->new({c=>$c});
135             };
136 2 50       1632 if ($@) {
137 0         0 $c->log->error($@);
138 0         0 return;
139             }
140             # store the object
141 2         13 $c->_errorcatcher_emitter_of->{$emitter_name} = $e;
142              
143 2 50       53 $c->log->debug(
144             $emitter_name
145             . q{: initialised without errors}
146             ) if $conf->{verbose} > 1;
147              
148             # we are happy when they emitted without incident
149 2         18 return 1;
150             }
151              
152             # default is, "no we didn't mit anything"
153 6         60 return;
154             }
155             sub _emit_message {
156 22     22   45 my $c = shift;
157 22         79 my $conf = $c->_errorcatcher_cfg;
158 22         426 my $emitted_count = 0;
159              
160             return
161 22 50       108 unless defined($c->_errorcatcher_msg);
162              
163             # use a custom emit method?
164 22 100       463 if (defined (my $emit_list = $c->_errorcatcher_cfg->{emit_module})) {
165 7         164 my @emit_list;
166             # one item or a list?
167 7 50 33     90 if (defined ref($emit_list) and 'ARRAY' eq ref($emit_list)) {
    50          
168 0         0 @emit_list = @{ $emit_list };
  0         0  
169             }
170             elsif (not ref($emit_list)) {
171 7         25 @emit_list = ( $emit_list );
172             }
173              
174 7         21 foreach my $emitter (@emit_list) {
175 7 50       45 $c->log->debug(
176             q{Trying to use custom emitter: }
177             . $emitter
178             ) if $conf->{verbose};
179              
180             # require, and call methods
181 7         37 my $emitted_ok = $c->_require_and_emit(
182             $emitter, $c->_errorcatcher_msg
183             );
184 7 50       38 if ($emitted_ok) {
185 7         15 $emitted_count++;
186 7 50       67 $c->log->debug(
187             $emitter
188             . q{: OK}
189             ) if $conf->{verbose};
190             }
191             else {
192 0 0       0 $c->log->debug(
193             $emitter
194             . q{: FAILED}
195             ) if $conf->{verbose};
196             }
197             }
198             }
199              
200             # by default use $c->log
201 22 100 66     381 if (
202             not $emitted_count
203             or
204             $c->_errorcatcher_cfg->{always_log}
205             ) {
206 15         61 $c->log->info(
207             $c->_errorcatcher_msg
208             );
209             }
210              
211 22         818 return;
212             }
213              
214             sub _require_and_emit {
215 7     7   216 my $c = shift;
216 7         18 my $emitter_name = shift;
217 7         17 my $output = shift;
218 7         31 my $conf = $c->_errorcatcher_cfg;
219 7         137 my $emitter;
220              
221             # if we've preloaded an emitter [because it nas new()]
222             # call that object
223 7 100       62 if (defined (my $e=$c->_errorcatcher_emitter_of->{$emitter_name})) {
224             # make sure it's "the right thing"
225 2 50       109 if ($emitter_name eq ref($e)) {
226 2         10 $emitter = $e;
227             }
228             else {
229 0         0 die "$emitter isn't a $emitter";
230             }
231             }
232              
233             # if we haven't set the emitter (from a preloaded object)
234             # require it ...
235 7 100       141 if (not defined $emitter) {
236             # make sure our emitter loads
237 5         541 eval "require $emitter_name";
238 5 50       36 if ($@) {
239 0         0 $c->log->error($@);
240 0         0 return;
241             }
242              
243 5         14 $emitter = $emitter_name;
244             }
245              
246             # make sure it "can" emit
247 7 50       104 if ($emitter->can('emit')) {
248 7         20 eval {
249 7         48 $emitter->emit(
250             $c, $output
251             );
252             };
253 7 50       300 if ($@) {
254 0         0 $c->log->error($@);
255 0         0 return;
256             }
257              
258             $c->log->debug(
259 7 50       39 $emitter_name
260             . q{: emitted without errors}
261             ) if $conf->{verbose} > 1;
262              
263             # we are happy when they emitted without incident
264 7         33 return 1;
265             }
266             else {
267 0 0       0 $c->log->debug(
268             $emitter_name
269             . q{ does not have an emit() method}
270             ) if $conf->{verbose};
271             }
272              
273             # default is, "no we didn't emit anything"
274 0         0 return;
275             }
276              
277             sub _cleaned_error_message {
278 28     28   4319 my $error_message = shift;
279              
280             # load message cleaning plugins
281 28         176 my %opts = (
282             require => 1,
283             search_path => ['Catalyst::Plugin::ErrorCatcher::Plugin'],
284             );
285 28         361 my $finder = Module::Pluggable::Object->new(%opts);
286              
287             # loop through plugins and let them do some message tidying
288 28         328 foreach my $plugin ($finder->plugins) {
289 168 50       260121 $plugin->tidy_message(\$error_message)
290             if $plugin->can('tidy_message');
291             }
292              
293             # get rid of annoying newlines and return a potentially clean error
294             # message
295 28         104 chomp $error_message;
296 28         655 return $error_message;
297             }
298              
299             sub append_feedback {
300 409     409 1 1054 my $fb_ref = shift;
301 409         441 my $data = shift;
302 409   100     889 $$fb_ref ||= q{};
303 409         1049 $$fb_ref .= $data . qq{\n};
304             }
305              
306             sub append_feedback_emptyline {
307 107     107 1 243 append_feedback($_[0], q[]);
308             }
309              
310             sub append_feedback_keyvalue {
311             # don't add undefined values
312             return
313 233 100   233 1 7401 unless defined $_[2];
314 193   100     703 my $padding = $_[3] || 8;
315 193         1083 append_feedback(
316             $_[0],
317             sprintf("%${padding}s: %s", $_[1], $_[2])
318             );
319 193         323 return;
320             }
321              
322             sub sanitise_param {
323 14     14 1 26 my $value = shift;
324              
325             # stolen from Data::Dumper::qquote
326 14         18 my $dumped_value;
327             {
328 14         15 my %esc = (
  14         100  
329             "\a" => "\\a",
330             "\b" => "\\b",
331             "\t" => "\\t",
332             "\n" => "\\n",
333             "\f" => "\\f",
334             "\r" => "\\r",
335             "\e" => "\\e",
336             );
337 14         56 ($dumped_value = $value) =~ s{([\a\b\t\n\f\r\e])}{$esc{$1}}g;
338             }
339              
340             # if it's short, just show it
341 14 50       66 return $dumped_value
342             if (length($value) < 40);
343              
344             # make a guess at a possible filetype
345 0         0 my $ft = File::Type->new();
346 0         0 my $type = $ft->checktype_contents( $value );
347              
348             # if our mimetype isn't application/octet-stream just report what was
349             # submitted
350 0 0       0 if ($type ne 'application/octet-stream') {
351 0         0 return $type;
352             }
353              
354             # getting here means we're 'application/octet-stream'
355             # we could make guesses if we're really text/plain but for now
356             # ... we're long, return a substring of ourseld
357             # (if this gives troublesome results we'll tweak accordingly)
358 0         0 return sprintf(
359             '%s...[truncated]',
360             substr($dumped_value, 0, 40)
361             );
362             }
363              
364             sub append_output_params {
365 44     44 1 2131 my $fb_ref = shift;
366 44         100 my ($label,$params) = @_;
367 44 100       173 return unless keys %$params;
368             # work out the longest key
369             # (http://www.webmasterkb.com/Uwe/Forum.aspx/perl/7596/Maximum-length-of-hash-key)
370 6         10 my $l; $l|=$_ foreach keys %$params; $l=length $l;
  6         61  
  6         18  
371             # give the next set of output a header
372 6         24 append_feedback($fb_ref, "Params ($label):");
373             # output the key-value pairs
374 6         13 foreach my $k (sort keys %{$params}) {
  6         29  
375 14         42 my $processed_value = sanitise_param($params->{$k});
376 14         39 append_feedback_keyvalue($fb_ref, $k, $processed_value, $l+2);
377             }
378 6         19 append_feedback_emptyline($fb_ref);
379             }
380              
381             sub _prepare_message {
382 22     22   42 my $c = shift;
383 22         44 my ($feedback, $full_error, $parsed_error);
384              
385             # get the (list of) error(s)
386 22         40 for my $error (@{ $c->error }) {
  22         107  
387 22         302 $full_error .= qq{$error\n\n};
388             }
389             # trim out some extra fluff from the full message
390 22         95 $parsed_error = _cleaned_error_message($full_error);
391              
392             # A title for the feedback
393 22         101 append_feedback(\$feedback, qq{Exception caught:} );
394 22         89 append_feedback_emptyline(\$feedback);
395              
396             # the (parsed) error
397 22         100 append_feedback_keyvalue(\$feedback, "Error", $parsed_error);
398              
399             # general request information
400             # some of these aren't always defined...
401 22         966 append_feedback_keyvalue(\$feedback, "Time", scalar(localtime));
402              
403             # TODO use append_...() method
404 22 50       1107 $feedback .= " Client: " . $c->request->address
405             if (defined $c->request->address);
406 22 50       3432 if (defined $c->request->hostname) {
407 22         1765 $feedback .= " (" . $c->request->hostname . ")\n"
408             }
409             else {
410 0         0 $feedback .= "\n";
411             }
412              
413 22         1530 append_feedback_keyvalue(\$feedback, 'Agent', $c->request->user_agent);
414 22   50     854 append_feedback_keyvalue(\$feedback, 'URI', ($c->request->uri || q{n/a}));
415 22   50     746 append_feedback_keyvalue(\$feedback, 'Method', ($c->request->method || q{n/a}));
416 22         726 append_feedback_keyvalue(\$feedback, 'Referer', $c->request->referer);
417              
418             # TODO use append_...() method
419 22         162 my $user_identifier_method =
420             $c->_errorcatcher_cfg->{user_identified_by};
421             # if we have a logged-in user, add to the feedback
422 22 100 100     1189 if (
      66        
423             $c->can('user_exists')
424             && $c->user_exists
425             && $c->user->can($user_identifier_method)
426             ) {
427 3         1592 $feedback .= " User: " . $c->user->$user_identifier_method;
428 3         705 $feedback .= " [$user_identifier_method]";
429 3 50       12 if (ref $c->user) {
430 3         583 $feedback .= " (" . ref($c->user) . ")\n";
431             }
432             else {
433 0         0 $feedback .= "\n";
434             }
435             }
436              
437 22         39534 my $params; # share with body-param and query-param output
438 22         91 append_feedback_emptyline(\$feedback);
439             # output any query params
440 22         766 append_output_params(\$feedback, 'QUERY', $c->request->query_parameters);
441              
442             # output any body params
443 22         836 append_output_params(\$feedback, 'BODY', $c->request->body_parameters);
444              
445 22 100       135 if ('ARRAY' eq ref($c->_errorcatcher)) {
446             # push on information and context
447 16         444 for my $frame ( @{$c->_errorcatcher} ) {
  16         60  
448             # clean up the common filename of
449             # .../MyApp/script/../lib/...
450 29 50       464 if ( $frame->{file} =~ /../ ) {
451 29         96 $frame->{file} =~ s{script/../}{};
452             }
453              
454             # if we haven't stored a frame, do so now
455             # this is useful for easy access to the filename, line, etc
456 29 100       214 if (not defined $c->_errorcatcher_first_frame) {
457 7         956 $c->_errorcatcher_first_frame($frame);
458             }
459              
460 29         779 my $pkg = $frame->{pkg};
461 29         62 my $line = $frame->{line};
462 29         61 my $file = $frame->{file};
463 29         125 my $code_preview = _print_context(
464             $frame->{file},
465             $frame->{line},
466             $c->_errorcatcher_cfg->{context}
467             );
468              
469 29         122 append_feedback_keyvalue(\$feedback, 'Package', $pkg);
470 29         74 append_feedback_keyvalue(\$feedback, 'Line', $line);
471 29         74 append_feedback_keyvalue(\$feedback, 'File', $file);
472 29         74 append_feedback_emptyline(\$feedback);
473 29         112 append_feedback(\$feedback, $code_preview);
474             }
475             }
476             else {
477 6         732 append_feedback_emptyline(\$feedback);
478 6         18 append_feedback(\$feedback, "Stack trace unavailable - use and enable Catalyst::Plugin::StackTrace");
479             }
480              
481             # RT-64492 - add session data if requested
482 22 100 66     123 if (
483             $c->_errorcatcher_cfg->{include_session}
484             and defined $c->session
485             ) {
486 1         49769 eval { require Data::Dump };
  1         11  
487 1 50       5 if (my $e=$@) {
488 0         0 append_feedback(\$feedback, 'Session data requested but failed to require Data::Dump:');
489 0         0 append_feedback(\$feedback, " $e");
490             }
491             else {
492 1         6 append_feedback(\$feedback, 'Session Data');
493 1         6 append_feedback(\$feedback, Data::Dump::pp($c->session));
494             }
495             }
496              
497             # in case we bugger up the s/// on the original error message
498 22 50       700 if ($full_error) {
499 22         75 append_feedback(\$feedback, 'Original Error:');
500 22         57 append_feedback_emptyline(\$feedback);
501 22         56 append_feedback(\$feedback, $full_error);
502             }
503              
504             # store it, otherwise we've done the above for mothing
505 22 50       98 if (defined $feedback) {
506 22         188 $c->_errorcatcher_msg($feedback);
507             }
508              
509 22         625 return;
510             }
511              
512             # we don't have to do much here now that we're relying on ::StackTrace to do
513             # the work for us
514             sub _keep_frames {
515 22     22   38 my $c = shift;
516 22         160 my $conf = $c->_errorcatcher_cfg;
517 22         810 my $stacktrace;
518              
519 22         51 eval {
520 22         148 $stacktrace = $c->_stacktrace;
521             };
522              
523 22 100       3523 if (defined $stacktrace) {
524 16         149 $c->_errorcatcher( $stacktrace );
525             }
526             else {
527 6         30 $c->_errorcatcher( undef );
528 6 50       201 $c->log->debug(
529             __PACKAGE__
530             . q{ has no stack-trace information}
531             ) if $conf->{verbose} > 1;
532             }
533 22         543 return;
534             }
535              
536             # borrowed heavily from Catalyst::Plugin::StackTrace
537             sub _print_context {
538 29     29   601 my ( $file, $linenum, $context ) = @_;
539              
540 29         45 my $code;
541 29 50       1145 if ( -f $file ) {
542 29         117 my $start = $linenum - $context;
543 29         56 my $end = $linenum + $context;
544 29 50       106 $start = $start < 1 ? 1 : $start;
545 29 50       275 if ( my $fh = IO::File->new( $file, 'r' ) ) {
546 29         3984 my $cur_line = 0;
547 29         679 while ( my $line = <$fh> ) {
548 1068         790 ++$cur_line;
549 1068 100       1578 last if $cur_line > $end;
550 1043 100       2424 next if $cur_line < $start;
551 257 100       630 my @tag = $cur_line == $linenum ? ('-->', q{}) : (q{ }, q{});
552 257 50       1291 $code .= sprintf(
553             '%s%5d: %s%s',
554             $tag[0],
555             $cur_line,
556             $line ? $line : q{},
557             $tag[1],
558             );
559             }
560             }
561             }
562 29         496 return $code;
563             }
564              
565             1;
566              
567             =pod
568              
569             =encoding UTF-8
570              
571             =head1 NAME
572              
573             Catalyst::Plugin::ErrorCatcher - Catch application errors and emit them somewhere
574              
575             =head1 VERSION
576              
577             version 0.0.8.18
578              
579             =head1 SYNOPSIS
580              
581             use Catalyst qw/-Debug StackTrace ErrorCatcher/;
582              
583             =head1 DESCRIPTION
584              
585             This plugin allows you to do More Stuff with the information that would
586             normally only be seen on the Catalyst Error Screen courtesy of the
587             L<Catalyst::Plugin::StackTrace> plugin.
588              
589             =head2 setup($c, $@)
590              
591             Prepare the plugin for use.
592              
593             =head2 finalize_error($c)
594              
595             If configured, and needed, deal with raised errors.
596              
597             =head2 my_finalize_error($c)
598              
599             This is the method that's called by C<finalize_error> when we do want to use ErrorCatcher
600             to format and emit some information.
601              
602             =head2 emitters_init($c)
603              
604             This routine initialises the emitters enabled in the configuration for the plugin.
605              
606             =head2 append_feedback($stringref, $data)
607              
608             This is a small utility method that simplifies some of the work needed to
609             add some data to a string-reference, including some basic checks and initialisation.
610              
611             =head2 append_feedback_emptyline
612              
613             Add an empty-line to the string-reference of data being built.
614              
615             =head2 append_feedback_keyvalue($ref, $key, $value, $keypadding)
616              
617             Add:
618              
619             {key}: value
620              
621             to the feedback data being prepared.
622              
623             C<$keypadding> is optional. If omitted, defaults to 8.
624              
625             =head2 sanitise_param($value)
626              
627             Local implementation of L<Data::Dumper/qquote> and general sanity checks and
628             transformations of the data in a given piece of data.
629              
630             =head2 append_output_params($ref, $label, $params)
631              
632             Given a hashref of related items, C<$params>, and a C<$label> for the grouping,
633             add sensibly formatted output to the feedback data being constructed.
634              
635             =head1 CONFIGURATION
636              
637             The plugin is configured in a similar manner to other Catalyst plugins:
638              
639             <Plugin::ErrorCatcher>
640             enable 1
641             context 5
642             always_log 0
643             include_session 0
644             user_identified_by username
645              
646             emit_module A::Module
647             </Plugin::ErrorCatcher>
648              
649             =over 4
650              
651             =item B<enable>
652              
653             Setting this to I<true> forces the module to work its voodoo.
654              
655             It's also enabled if the value is unset and you're running Catalyst in
656             debug-mode.
657              
658             =item B<context>
659              
660             When there is stack-trace information to share, how many lines of context to
661             show around the line that caused the error.
662              
663             =item B<emit_module>
664              
665             This specifies which module to use for custom output behaviour.
666              
667             You can chain multiple modules by specifying a line in the config for each
668             module you'd like used:
669              
670             emit_module A::Module
671             emit_module Another::Module
672             emit_module Yet::Another::Module
673              
674             If none are specified, or all that are specified fail, the default behaviour
675             is to log the prepared message at the INFO level via C<$c-E<gt>log()>.
676              
677             For details on how to implement a custom emitter see L</"CUSTOM EMIT CLASSES">
678             in this documentation.
679              
680             =item B<always_log>
681              
682             The default plugin behaviour when using one or more emitter modules is to
683             suppress the I<info> log message if one or more of them succeeded.
684              
685             If you wish to log the information, via C<$c-E<gt>log()> then set this value
686             to 1.
687              
688             =item B<include_session>
689              
690             The default behaviour is to suppress potentially sensitive and revealing
691             session-data in the error report.
692              
693             If you feel that this information is useful in your investigations set the
694             value to I<true>.
695              
696             When set to 1 the report will include a C<Data::Dump::pp()> representation of
697             the request's session.
698              
699             =item B<user_identified_by>
700              
701             If there's a logged-in user use the specified value as the method to identify
702             the user.
703              
704             If the specified value is invalid the module defaults to using I<id>.
705              
706             If unspecified the value defaults to I<id>.
707              
708             =back
709              
710             =head1 STACKTRACE IN REPORTS WHEN NOT RUNNING IN DEBUG MODE
711              
712             It is possible to run your application in non-Debug mode, and still have
713             errors reported with a stack-trace.
714              
715             Include the StackTrace and ErrorCatcher plugins in MyApp.pm:
716              
717             use Catalyst qw<
718             ErrorCatcher
719             StackTrace
720             >;
721              
722             Set up your C<myapp.conf> to include the following:
723              
724             <stacktrace>
725             enable 1
726             </stacktrace>
727              
728             <Plugin::ErrorCatcher>
729             enable 1
730             # include other options here
731             <Plugin::ErrorCatcher>
732              
733             Any exceptions should now show your user the I<"Please come back later">
734             screen whilst still capturing and emitting a report with stack-trace.
735              
736             =head1 PROVIDED EMIT CLASSES
737              
738             =head2 Catalyst::Plugin::ErrorCatcher::Email
739              
740             This module uses L<MIME::Lite> to send the prepared output to a specified
741             email address.
742              
743             See L<Catalyst::Plugin::ErrorCatcher::Email> for usage and configuration
744             details.
745              
746             =head1 CUSTOM EMIT CLASSES
747              
748             A custom emit class takes the following format:
749              
750             package A::Module;
751             # vim: ts=8 sts=4 et sw=4 sr sta
752             use strict;
753             use warnings;
754            
755             sub emit {
756             my ($class, $c, $output) = @_;
757            
758             $c->log->info(
759             'IGNORING OUTPUT FROM Catalyst::Plugin::ErrorCatcher'
760             );
761            
762             return;
763             }
764            
765             1;
766             __END__
767              
768             The only requirement is that you have a sub called C<emit>.
769              
770             C<Catalyst::Plugin::ErrorCatcher> passes the following parameters in the call
771             to C<emit()>:
772              
773             =over 4
774              
775             =item B<$class>
776              
777             The package name
778              
779             =item B<$c>
780              
781             A L<Context|Catalyst::Manual::Intro/"Context"> object
782              
783             =item B<$output>
784              
785             The processed output from C<Catalyst::Plugin::ErrorCatcher>
786              
787             =back
788              
789             If you want to use the original error message you should use:
790              
791             my @error = @{ $c->error };
792              
793             You may use and abuse any Catalyst methods, or other Perl modules as you see
794             fit.
795              
796             =head1 KNOWN ISSUES
797              
798             =over 4
799              
800             =item BODY tests failing (Catalyst >=5.90008)
801              
802             Summary: https://github.com/chiselwright/catalyst-plugin-errorcatcher/pull/1
803              
804             Bug report: https://rt.cpan.org/Public/Bug/Display.html?id=75607
805              
806             =back
807              
808             =head1 SEE ALSO
809              
810             L<Catalyst>,
811             L<Catalyst::Plugin::StackTrace>
812              
813             =head1 THANKS
814              
815             The authors of L<Catalyst::Plugin::StackTrace>, from which a lot of
816             code was used.
817              
818             Ash Berlin for guiding me in the right direction after a known hacky first
819             implementation.
820              
821             =head1 CONTRIBUTORS
822              
823             Fitz Elliot L<https://github.com/felliott/>
824              
825             =head1 AUTHOR
826              
827             Chisel <chisel@chizography.net>
828              
829             =head1 COPYRIGHT AND LICENSE
830              
831             This software is copyright (c) 2015 by Chisel Wright.
832              
833             This is free software; you can redistribute it and/or modify it under
834             the same terms as the Perl 5 programming language system itself.
835              
836             =cut
837              
838             __END__
839              
840              
841             # vim: ts=8 sts=4 et sw=4 sr sta