File Coverage

lib/Mojolicious/Plugin/MailException.pm
Criterion Covered Total %
statement 73 74 98.6
branch 14 20 70.0
condition 8 15 53.3
subroutine 12 13 92.3
pod 1 1 100.0
total 108 123 87.8


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Mojolicious::Plugin::MailException - Mojolicious plugin to send crash information by email
4              
5             =head1 SYNOPSIS
6              
7             package MyServer;
8             use Mojo::Base 'Mojolicious';
9              
10             sub startup {
11             my ($self) = @_;
12              
13             $self->plugin(MailException => {
14             from => 'robot@my.site.com',
15             to => 'mail1@my.domain.com, mail2@his.domain.com',
16             subject => 'My site crashed!',
17             headers => {
18             'X-MySite' => 'crashed'
19             },
20              
21             stack => 10
22             });
23             }
24              
25             =head1 DESCRIPTION
26              
27             The plugin catches all exceptions, packs them into email and sends
28             them to email.
29              
30             There are some plugin options:
31              
32             =over
33              
34             =item from
35              
36             From-address for email (default B)
37              
38             =item to
39              
40             To-address(es) for email (default B)
41              
42             =item subject
43              
44             Subject for crash email
45              
46             =item headers
47              
48             Hash with headers that have to be added to mail
49              
50             =item stack
51              
52             Stack size for crash mail. Default is C<20>.
53              
54             =item send
55              
56             Subroutine that can be used to send the mail, example:
57              
58             sub startup {
59             my ($self) = @_;
60              
61             $self->plugin(MailException => {
62             send => sub {
63             my ($mail, $exception) = @_;
64              
65             $mail->send; # prepared MIME::Lite object
66             }
67             });
68             }
69              
70             In the function You can send email by yourself and (or) prepare and
71             send Your own mail (sms, etc) message using B<$exception> object.
72             See L.
73              
74             =back
75              
76             The plugin provides additional method (helper) B.
77              
78             $cx->mail_exception('my_error', { 'X-Add-Header' => 'value' });
79              
80             You can use the helper to raise exception with additional mail headers.
81              
82             =head1 VCS
83              
84             The plugin is placed on
85             L.
86              
87             =head1 COPYRIGHT AND LICENCE
88              
89             Copyright (C) 2012 by Dmitry E. Oboukhov
90             Copyright (C) 2012 by Roman V. Nikolaev
91              
92             This library is free software; you can redistribute it and/or modify
93             it under the same terms as Perl itself, either Perl version 5.8.8 or,
94             at your option, any later version of Perl 5 you may have available.
95              
96             =cut
97              
98             package Mojolicious::Plugin::MailException;
99              
100             our $VERSION = '0.21';
101 1     1   444685 use 5.008008;
  1         3  
102 1     1   5 use strict;
  1         3  
  1         17  
103 1     1   5 use warnings;
  1         2  
  1         27  
104              
105 1     1   5 use Mojo::Base 'Mojolicious::Plugin';
  1         2  
  1         10  
106 1     1   840 use Data::Dumper;
  1         2  
  1         45  
107 1     1   5 use Mojo::Exception;
  1         2  
  1         7  
108 1     1   19 use Carp;
  1         2  
  1         39  
109 1     1   5 use MIME::Lite;
  1         2  
  1         33  
110 1     1   5 use MIME::Words ':all';
  1         1  
  1         1328  
111              
112              
113             my $mail_prepare = sub {
114             my ($e, $conf, $self, $from, $to, $headers, $stack_depth) = @_;
115             my $subject = $conf->{subject} || 'Caught exception';
116             $subject .= ' (' . $self->req->method . ': ' .
117             $self->req->url->to_abs->to_string . ')';
118             utf8::encode($subject) if utf8::is_utf8 $subject;
119             $subject = encode_mimeword $subject, 'B', 'utf-8';
120              
121              
122             my $text = '';
123             $text .= "Exception\n";
124             $text .= "~~~~~~~~~\n";
125              
126              
127             $text .= $e->message;
128             $text .= "\n";
129              
130             my $maxl = eval { length $e->lines_after->[-1][0]; };
131             $maxl ||= 5;
132             $text .= sprintf " %*d %s\n", $maxl, @{$_}[0,1] for @{ $e->lines_before };
133             $text .= sprintf " * %*d %s\n", $maxl, @{ $e->line }[0,1] if $e->line->[0];
134             $text .= sprintf " %*d %s\n", $maxl, @{$_}[0,1] for @{ $e->lines_after };
135              
136             if (@{ $e->frames }) {
137             my $no = 0;
138             $text .= "\n";
139             $text .= "Stack\n";
140             $text .= "~~~~~\n";
141             for (@{ $e->frames }) {
142             $no++;
143             if ($no > $stack_depth) {
144             $text .= " ...\n";
145             last;
146             }
147             $text .= sprintf " %s: %d\n", @{$_}[1,2];
148             }
149             }
150              
151              
152             if (eval { $self->session; scalar keys %{ $self->session } }) {
153             local $Data::Dumper::Indent = 1;
154             local $Data::Dumper::Terse = 1;
155             local $Data::Dumper::Useqq = 1;
156             local $Data::Dumper::Deepcopy = 1;
157             local $Data::Dumper::Maxdepth = 0;
158              
159             $text .= "\n";
160             $text .= "Session\n";
161             $text .= "~~~~~~~\n";
162             $text .= Dumper($self->session);
163             }
164              
165             eval { utf8::encode($text) if utf8::is_utf8 $text };
166              
167              
168             my $mail = MIME::Lite->new(
169             From => $from,
170             To => $to,
171             Subject => $subject,
172             Type => 'multipart/mixed',
173             );
174              
175              
176             $mail->attach(
177             Type => 'text/plain; charset=utf-8',
178             Data => $text
179             );
180              
181             $text = "Request\n";
182             $text .= "~~~~~~~\n";
183             my $req = $self->req->to_string;
184             $req =~ s/^/ /gm;
185             $text .= $req;
186              
187             $mail->attach(
188             Type => 'text/plain; charset=utf-8',
189             Filename => 'request.txt',
190             Disposition => 'inline',
191             Data => $text
192             );
193              
194             $mail->add($_ => $headers->{$_}) for keys %$headers;
195             return $mail;
196             };
197              
198              
199             sub register {
200 1     1 1 11418 my ($self, $app, $conf) = @_;
201              
202 1   50     8 my $stack_depth = $conf->{stack} || 20;
203              
204 1   50 0   3 my $cb = $conf->{send} || sub { $_[0]->send };
  0            
205 1 50       5 croak "Usage: app->plugin('ExceptionMail'[, send => sub { ... })'"
206             unless 'CODE' eq ref $cb;
207              
208 1   50     3 my $headers = $conf->{headers} || {};
209 1   50     6 my $from = $conf->{from} || 'root@localhost';
210 1   50     4 my $to = $conf->{to} || 'webmaster@localhost';
211              
212 1 50       3 croak "headers must be a HASHREF" unless 'HASH' eq ref $headers;
213              
214             $app->hook(around_dispatch => sub {
215 4     4   39821 my ($next, $c) = @_;
216              
217 4         9 my $e;
218             {
219 4         5 local $SIG{__DIE__} = sub {
220              
221 3         1348 ($e) = @_;
222              
223 3 100 66     26 unless (ref $e and $e->isa('Mojo::Exception')) {
224 2         7 my @caller = caller;
225              
226 2         16 $e =~ s/at\s+(.+?)\s+line\s+(\d+).*//s;
227              
228 2         19 $e = Mojo::Exception->new(
229             sprintf "%s at %s line %d\n", "$e", @caller[1,2]
230             );
231 2         28 $e->trace(1);
232 2 50       591 $e->inspect if $e->can('inspect');
233             }
234              
235              
236 3         1145 CORE::die $e;
237 4         78 };
238              
239 4         8 eval { $next->() };
  4         13  
240             }
241              
242 4 100       6333 return unless $@;
243              
244 3 100       15 unless ($e) {
245 1         9 $e = Mojo::Exception->new($@);
246 1         14 $e->trace(1);
247 1 50       217 $e->inspect if $e->can('inspect');
248             }
249              
250 3         579 my $hdrs = $headers;
251              
252 1         4 $hdrs = { %$hdrs, %{ $e->{local_headers} } }
253 3 100       12 if ref $e->{local_headers};
254              
255 3         12 my $mail = $mail_prepare->( $e, $conf, $c, $from, $to, $hdrs, $stack_depth );
256              
257 3 50       5 eval {
258 3         58 local $SIG{CHLD} = 'IGNORE';
259 3         13 local $SIG{__DIE__};
260 3         14 $cb->($mail, $e);
261 3         54 1;
262             } or warn $@;
263              
264             # propagate Mojo::Exception
265 3         28 die $e;
266 1         7 });
267              
268             $app->helper(mail_exception => sub {
269 1     1   1695 my ($self, $et, $hdrs) = @_;
270 1         14 my @caller = caller 1;
271 1   50     5 $et ||= 'exception';
272 1         14 my $e = Mojo::Exception->new(
273             sprintf '%s at %s line %d', $et, @caller[1,2]
274             );
275 1         18 $e->trace(2);
276 1 50       321 $e->inspect if $e->can('inspect');
277              
278 1         587 $e->{local_headers} = $hdrs;
279 1         9 CORE::die $e;
280 1         13 });
281             }
282              
283             1;