File Coverage

lib/Mojolicious/Plugin/MailException.pm
Criterion Covered Total %
statement 72 73 98.6
branch 14 20 70.0
condition 7 13 53.8
subroutine 12 13 92.3
pod 1 1 100.0
total 106 120 88.3


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