line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
1
|
|
|
1
|
|
13071
|
use strict; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
23
|
|
2
|
1
|
|
|
1
|
|
3
|
use warnings; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
49
|
|
3
|
|
|
|
|
|
|
package Exception::Reporter; |
4
|
|
|
|
|
|
|
# ABSTRACT: a generic exception-reporting object |
5
|
|
|
|
|
|
|
$Exception::Reporter::VERSION = '0.014'; |
6
|
|
|
|
|
|
|
#pod =head1 SYNOPSIS |
7
|
|
|
|
|
|
|
#pod |
8
|
|
|
|
|
|
|
#pod B This is an experimental refactoring of some long-standing internal |
9
|
|
|
|
|
|
|
#pod code. It might get even more refactored. Once I've sent a few hundred |
10
|
|
|
|
|
|
|
#pod thousand exceptions through it, I'll remove this warning... |
11
|
|
|
|
|
|
|
#pod |
12
|
|
|
|
|
|
|
#pod First, you create a reporter. Probably you stick it someplace globally |
13
|
|
|
|
|
|
|
#pod accessible, like MyApp->reporter. |
14
|
|
|
|
|
|
|
#pod |
15
|
|
|
|
|
|
|
#pod my $reporter = Exception::Reporter->new({ |
16
|
|
|
|
|
|
|
#pod always_dump => { env => sub { \%ENV } }, |
17
|
|
|
|
|
|
|
#pod senders => [ |
18
|
|
|
|
|
|
|
#pod Exception::Reporter::Sender::Email->new({ |
19
|
|
|
|
|
|
|
#pod from => 'root', |
20
|
|
|
|
|
|
|
#pod to => 'SysAdmins ', |
21
|
|
|
|
|
|
|
#pod }), |
22
|
|
|
|
|
|
|
#pod ], |
23
|
|
|
|
|
|
|
#pod summarizers => [ |
24
|
|
|
|
|
|
|
#pod Exception::Reporter::Summarizer::Email->new, |
25
|
|
|
|
|
|
|
#pod Exception::Reporter::Summarizer::File->new, |
26
|
|
|
|
|
|
|
#pod Exception::Reporter::Summarizer::ExceptionClass->new, |
27
|
|
|
|
|
|
|
#pod Exception::Reporter::Summarizer::Fallback->new, |
28
|
|
|
|
|
|
|
#pod ], |
29
|
|
|
|
|
|
|
#pod }); |
30
|
|
|
|
|
|
|
#pod |
31
|
|
|
|
|
|
|
#pod Later, some exception has been thrown! Maybe it's an L-based |
32
|
|
|
|
|
|
|
#pod exception, or a string, or a L object or who knows what. |
33
|
|
|
|
|
|
|
#pod |
34
|
|
|
|
|
|
|
#pod try { |
35
|
|
|
|
|
|
|
#pod ... |
36
|
|
|
|
|
|
|
#pod } catch { |
37
|
|
|
|
|
|
|
#pod MyApp->reporter->report_exception( |
38
|
|
|
|
|
|
|
#pod [ |
39
|
|
|
|
|
|
|
#pod [ exception => $_ ], |
40
|
|
|
|
|
|
|
#pod [ request => $current_request ], |
41
|
|
|
|
|
|
|
#pod [ uploading => Exception::Reporter::Dumpable::File->new($filename) ], |
42
|
|
|
|
|
|
|
#pod ], |
43
|
|
|
|
|
|
|
#pod ); |
44
|
|
|
|
|
|
|
#pod }; |
45
|
|
|
|
|
|
|
#pod |
46
|
|
|
|
|
|
|
#pod The sysadmins will get a nice email report with all the dumped data, and |
47
|
|
|
|
|
|
|
#pod reports will thread. Awesome, right? |
48
|
|
|
|
|
|
|
#pod |
49
|
|
|
|
|
|
|
#pod =head1 OVERVIEW |
50
|
|
|
|
|
|
|
#pod |
51
|
|
|
|
|
|
|
#pod Exception::Reporter takes a bunch of input (the I) and tries to |
52
|
|
|
|
|
|
|
#pod figure out how to summarize them and build them into a report to send to |
53
|
|
|
|
|
|
|
#pod somebody. Probably a human being. |
54
|
|
|
|
|
|
|
#pod |
55
|
|
|
|
|
|
|
#pod It does this with two kinds of plugins: summarizers and senders. |
56
|
|
|
|
|
|
|
#pod |
57
|
|
|
|
|
|
|
#pod The summarizers' job is to convert each dumpable into a simple hashref |
58
|
|
|
|
|
|
|
#pod describing it. The senders' job is to take those hashrefs and send them to |
59
|
|
|
|
|
|
|
#pod somebody who cares. |
60
|
|
|
|
|
|
|
#pod |
61
|
|
|
|
|
|
|
#pod =cut |
62
|
|
|
|
|
|
|
|
63
|
1
|
|
|
1
|
|
370
|
use Data::GUID guid_string => { -as => '_guid_string' }; |
|
1
|
|
|
|
|
13133
|
|
|
1
|
|
|
|
|
5
|
|
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
#pod =method new |
66
|
|
|
|
|
|
|
#pod |
67
|
|
|
|
|
|
|
#pod my $reporter = Exception::Reporter->new(\%arg); |
68
|
|
|
|
|
|
|
#pod |
69
|
|
|
|
|
|
|
#pod This returns a new reporter. Valid arguments are: |
70
|
|
|
|
|
|
|
#pod |
71
|
|
|
|
|
|
|
#pod summarizers - an arrayref of summarizer objects; required |
72
|
|
|
|
|
|
|
#pod senders - an arrayref of sender objects; required |
73
|
|
|
|
|
|
|
#pod dumper - a Exception::Reporter::Dumper used for dumping data |
74
|
|
|
|
|
|
|
#pod always_dump - a hashref of coderefs used to generate extra dumpables |
75
|
|
|
|
|
|
|
#pod caller_level - if given, the reporter will look n frames up; see below |
76
|
|
|
|
|
|
|
#pod |
77
|
|
|
|
|
|
|
#pod The C hashref bears a bit more explanation. When |
78
|
|
|
|
|
|
|
#pod C> is called, each entry in C will be |
79
|
|
|
|
|
|
|
#pod evaluated and appended to the list of given dumpables. This lets you make your |
80
|
|
|
|
|
|
|
#pod reporter always include some more useful information. |
81
|
|
|
|
|
|
|
#pod |
82
|
|
|
|
|
|
|
#pod I<...but remember!> The reporter is probably doing its job in a C |
83
|
|
|
|
|
|
|
#pod block, which means that anything that might have been changed C-ly in |
84
|
|
|
|
|
|
|
#pod your C block will I be the same when evaluated as part of the |
85
|
|
|
|
|
|
|
#pod C code. This might not matter often, but keep it in mind when |
86
|
|
|
|
|
|
|
#pod setting up your reporter. |
87
|
|
|
|
|
|
|
#pod |
88
|
|
|
|
|
|
|
#pod In real code, you're likely to create one Exception::Reporter object and make |
89
|
|
|
|
|
|
|
#pod it globally accessible through some method. That method adds a call frame, and |
90
|
|
|
|
|
|
|
#pod Exception::Reporter sometimes looks at C to get a default. If you want |
91
|
|
|
|
|
|
|
#pod to skip those intermedite call frames, pass C. It will be used |
92
|
|
|
|
|
|
|
#pod as the number of frames up the stack to look. It defaults to zero. |
93
|
|
|
|
|
|
|
#pod |
94
|
|
|
|
|
|
|
#pod =cut |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
sub new { |
97
|
3
|
|
|
3
|
1
|
4
|
my ($class, $arg) = @_; |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
my $self = { |
100
|
|
|
|
|
|
|
summarizers => $arg->{summarizers}, |
101
|
|
|
|
|
|
|
senders => $arg->{senders}, |
102
|
|
|
|
|
|
|
dumper => $arg->{dumper}, |
103
|
|
|
|
|
|
|
always_dump => $arg->{always_dump}, |
104
|
3
|
|
100
|
|
|
18
|
caller_level => $arg->{caller_level} || 0, |
105
|
|
|
|
|
|
|
}; |
106
|
|
|
|
|
|
|
|
107
|
3
|
100
|
|
|
|
8
|
if ($self->{always_dump}) { |
108
|
2
|
|
|
|
|
2
|
for my $key (keys %{ $self->{always_dump} }) { |
|
2
|
|
|
|
|
7
|
|
109
|
|
|
|
|
|
|
Carp::confess("non-coderef entry in always_dump: $key") |
110
|
2
|
50
|
|
|
|
7
|
unless ref($self->{always_dump}{$key}) eq 'CODE'; |
111
|
|
|
|
|
|
|
} |
112
|
|
|
|
|
|
|
} |
113
|
|
|
|
|
|
|
|
114
|
3
|
|
66
|
|
|
11
|
$self->{dumper} ||= do { |
115
|
1
|
|
|
|
|
7
|
require Exception::Reporter::Dumper::YAML; |
116
|
1
|
|
|
|
|
7
|
Exception::Reporter::Dumper::YAML->new; |
117
|
|
|
|
|
|
|
}; |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
Carp::confess("entry in dumper is not an Exception::Reporter::Dumper") |
120
|
3
|
50
|
|
|
|
14
|
unless $self->{dumper}->isa('Exception::Reporter::Dumper'); |
121
|
|
|
|
|
|
|
|
122
|
3
|
|
|
|
|
4
|
for my $test (qw(Summarizer Sender)) { |
123
|
6
|
|
|
|
|
9
|
my $class = "Exception::Reporter::$test"; |
124
|
6
|
|
|
|
|
10
|
my $key = "\L${test}s"; |
125
|
|
|
|
|
|
|
|
126
|
6
|
50
|
33
|
|
|
11
|
Carp::confess("no $key given") unless $arg->{$key} and @{ $arg->{$key} }; |
|
6
|
|
|
|
|
15
|
|
127
|
|
|
|
|
|
|
Carp::confess("entry in $key is not an $class") |
128
|
6
|
50
|
|
|
|
6
|
if grep { ! $_->isa($class) } @{ $arg->{$key} }; |
|
14
|
|
|
|
|
48
|
|
|
6
|
|
|
|
|
9
|
|
129
|
|
|
|
|
|
|
} |
130
|
|
|
|
|
|
|
|
131
|
3
|
|
|
|
|
5
|
bless $self => $class; |
132
|
|
|
|
|
|
|
|
133
|
3
|
|
|
|
|
4
|
$_->register_reporter($self) for $self->_summarizers; |
134
|
|
|
|
|
|
|
|
135
|
3
|
|
|
|
|
6
|
return $self; |
136
|
|
|
|
|
|
|
} |
137
|
|
|
|
|
|
|
|
138
|
7
|
|
|
7
|
|
8
|
sub _summarizers { return @{ $_[0]->{summarizers} }; } |
|
7
|
|
|
|
|
24
|
|
139
|
4
|
|
|
4
|
|
4
|
sub _senders { return @{ $_[0]->{senders} }; } |
|
4
|
|
|
|
|
8
|
|
140
|
|
|
|
|
|
|
|
141
|
6
|
|
|
6
|
0
|
18
|
sub dumper { return $_[0]->{dumper} } |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
#pod =method report_exception |
144
|
|
|
|
|
|
|
#pod |
145
|
|
|
|
|
|
|
#pod $reporter->report_exception(\@dumpables, \%arg); |
146
|
|
|
|
|
|
|
#pod |
147
|
|
|
|
|
|
|
#pod This method makes the reporter do its job: summarize dumpables and send a |
148
|
|
|
|
|
|
|
#pod report. |
149
|
|
|
|
|
|
|
#pod |
150
|
|
|
|
|
|
|
#pod Useful options in C<%arg> are: |
151
|
|
|
|
|
|
|
#pod |
152
|
|
|
|
|
|
|
#pod reporter - the program or authority doing the reporting; defaults to |
153
|
|
|
|
|
|
|
#pod the calling package |
154
|
|
|
|
|
|
|
#pod |
155
|
|
|
|
|
|
|
#pod handled - this indicates that this exception has been handled and that |
156
|
|
|
|
|
|
|
#pod the user has not seen a terrible crash; senders might use |
157
|
|
|
|
|
|
|
#pod this to decide who needs to get woken up |
158
|
|
|
|
|
|
|
#pod |
159
|
|
|
|
|
|
|
#pod extra_rcpts - this can be an arrayref of email addresses to be used as |
160
|
|
|
|
|
|
|
#pod extra envelope recipients by the Email sender |
161
|
|
|
|
|
|
|
#pod |
162
|
|
|
|
|
|
|
#pod Each entry in C<@dumpables> is expected to look like this: |
163
|
|
|
|
|
|
|
#pod |
164
|
|
|
|
|
|
|
#pod [ $short_name, $value, \%arg ] |
165
|
|
|
|
|
|
|
#pod |
166
|
|
|
|
|
|
|
#pod The short name is used for a few things, including identifying the dumps inside |
167
|
|
|
|
|
|
|
#pod the report produced. It's okay to have duplicated short names. |
168
|
|
|
|
|
|
|
#pod |
169
|
|
|
|
|
|
|
#pod The value can, in theory, be I. It can be C, any kind of |
170
|
|
|
|
|
|
|
#pod object, or whatever you want to stick in a scalar. It's possible that |
171
|
|
|
|
|
|
|
#pod extremely exotic values could confuse the "fallback" summarizer of last resort, |
172
|
|
|
|
|
|
|
#pod but for the most part, anything goes. |
173
|
|
|
|
|
|
|
#pod |
174
|
|
|
|
|
|
|
#pod The C<%arg> entry isn't used for anything by the core libraries that ship with |
175
|
|
|
|
|
|
|
#pod Exception::Reporter, but you might want to use it for your own purposes. Feel |
176
|
|
|
|
|
|
|
#pod free. |
177
|
|
|
|
|
|
|
#pod |
178
|
|
|
|
|
|
|
#pod The reporter will try to summarize each dumpable by asking each summarizer, in |
179
|
|
|
|
|
|
|
#pod order, whether it C the dumpable. If it can, it will be asked |
180
|
|
|
|
|
|
|
#pod to C the dumpable. The summaries are collected into a structure |
181
|
|
|
|
|
|
|
#pod that looks like this: |
182
|
|
|
|
|
|
|
#pod |
183
|
|
|
|
|
|
|
#pod [ |
184
|
|
|
|
|
|
|
#pod [ dumpable_short_name => \@summaries ], |
185
|
|
|
|
|
|
|
#pod ... |
186
|
|
|
|
|
|
|
#pod ] |
187
|
|
|
|
|
|
|
#pod |
188
|
|
|
|
|
|
|
#pod If a given dumpable can't be dumped by any summarizer, a not-very-useful |
189
|
|
|
|
|
|
|
#pod placeholder is put in its place. |
190
|
|
|
|
|
|
|
#pod |
191
|
|
|
|
|
|
|
#pod The arrayref constructed is passed to the C method of each sender, |
192
|
|
|
|
|
|
|
#pod in turn. |
193
|
|
|
|
|
|
|
#pod |
194
|
|
|
|
|
|
|
#pod =cut |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
sub report_exception { |
197
|
4
|
|
|
4
|
1
|
7726
|
my ($self, $dumpables, $arg) = @_; |
198
|
4
|
|
50
|
|
|
8
|
$dumpables ||= []; |
199
|
4
|
|
50
|
|
|
6
|
$arg ||= {}; |
200
|
|
|
|
|
|
|
|
201
|
4
|
|
|
|
|
8
|
my $guid = _guid_string; |
202
|
|
|
|
|
|
|
|
203
|
4
|
|
|
|
|
638
|
my @caller = caller( $self->{caller_level} ); |
204
|
4
|
|
33
|
|
|
9
|
$arg->{reporter} ||= $caller[0]; |
205
|
|
|
|
|
|
|
|
206
|
4
|
|
|
|
|
8
|
my $summaries = $self->collect_summaries($dumpables); |
207
|
|
|
|
|
|
|
|
208
|
4
|
|
|
|
|
6
|
for my $sender ($self->_senders) { |
209
|
4
|
|
|
|
|
17
|
$sender->send_report( |
210
|
|
|
|
|
|
|
$summaries, |
211
|
|
|
|
|
|
|
$arg, |
212
|
|
|
|
|
|
|
{ |
213
|
|
|
|
|
|
|
guid => $guid, |
214
|
|
|
|
|
|
|
caller => \@caller, |
215
|
|
|
|
|
|
|
} |
216
|
|
|
|
|
|
|
); |
217
|
|
|
|
|
|
|
} |
218
|
|
|
|
|
|
|
|
219
|
4
|
|
|
|
|
31
|
return $guid; |
220
|
|
|
|
|
|
|
} |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
#pod =method collect_summaries |
223
|
|
|
|
|
|
|
#pod |
224
|
|
|
|
|
|
|
#pod $reporter->report_exception(\@dumpables); |
225
|
|
|
|
|
|
|
#pod |
226
|
|
|
|
|
|
|
#pod This method is used by L to convert dumpables into |
227
|
|
|
|
|
|
|
#pod summaries. It may be called directly by summarizers through |
228
|
|
|
|
|
|
|
#pod C<< $self->reporter->collect_summaries(\@dumpables); >> if your |
229
|
|
|
|
|
|
|
#pod summarizers receive dumpables that may be handled by another summarizer. Be |
230
|
|
|
|
|
|
|
#pod wary though, because you could possibly create an endless loop... |
231
|
|
|
|
|
|
|
#pod |
232
|
|
|
|
|
|
|
#pod =cut |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
sub collect_summaries { |
235
|
4
|
|
|
4
|
1
|
2
|
my ($self, $dumpables) = @_; |
236
|
|
|
|
|
|
|
|
237
|
4
|
|
|
|
|
5
|
my @sumz = $self->_summarizers; |
238
|
|
|
|
|
|
|
|
239
|
4
|
|
|
|
|
5
|
my @summaries; |
240
|
|
|
|
|
|
|
|
241
|
4
|
|
|
|
|
4
|
DUMPABLE: for my $dumpable ( |
242
|
|
|
|
|
|
|
@$dumpables, |
243
|
3
|
|
|
|
|
8
|
map {; [ $_, $self->{always_dump}{$_}->() ] } |
244
|
4
|
|
|
|
|
11
|
sort keys %{$self->{always_dump}} |
245
|
|
|
|
|
|
|
) { |
246
|
15
|
|
|
|
|
23
|
for my $sum (@sumz) { |
247
|
48
|
100
|
|
|
|
289
|
next unless $sum->can_summarize($dumpable); |
248
|
15
|
|
|
|
|
73
|
push @summaries, [ $dumpable->[0], [ $sum->summarize($dumpable) ] ]; |
249
|
15
|
|
|
|
|
156
|
next DUMPABLE; |
250
|
|
|
|
|
|
|
} |
251
|
|
|
|
|
|
|
|
252
|
0
|
|
|
|
|
0
|
push @summaries, [ |
253
|
|
|
|
|
|
|
$dumpable->[0], |
254
|
|
|
|
|
|
|
[ { |
255
|
|
|
|
|
|
|
ident => "UNKNOWN", |
256
|
|
|
|
|
|
|
body => "the entry for <$dumpable->[0]> could not be summarized", |
257
|
|
|
|
|
|
|
mimetype => 'text/plain', |
258
|
|
|
|
|
|
|
filename => 'unknown.txt', |
259
|
|
|
|
|
|
|
} ], |
260
|
|
|
|
|
|
|
]; |
261
|
|
|
|
|
|
|
} |
262
|
|
|
|
|
|
|
|
263
|
4
|
|
|
|
|
9
|
return \@summaries; |
264
|
|
|
|
|
|
|
} |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
1; |
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
__END__ |