File Coverage

blib/lib/Carp/Notify.pm
Criterion Covered Total %
statement 91 203 44.8
branch 29 104 27.8
condition 0 17 0.0
subroutine 13 19 68.4
pod 8 11 72.7
total 141 354 39.8


line stmt bran cond sub pod time code
1             package Carp::Notify;
2             {
3             $Carp::Notify::VERSION = '1.11';
4             }
5              
6             # ABSTRACT: Loudly complain in lots of places when things break badly
7              
8 1     1   1061 use 5.005; # probably lower, but I haven't tested it below 005
  1         4  
  1         54  
9 1     1   6 use strict;
  1         2  
  1         174  
10             local $^W = 1;
11              
12             my %def = (
13             "smtp" => 'your.smtp.com', # IMPORTANT! Set this! I mean it!
14             "domain" => 'smtp.com', # IMPORTANT! Set this! I mean it!
15             "port" => 25,
16              
17             "email_it" => 1, # should we email by default? true/false
18             "email" => 'someone@your.smtp.com', # who are we emailing by default?
19             "return" => 'someone@your.smtp.com', # who is the error coming from?
20             "subject" => 'Ye Gods! An error!',
21              
22             "log_it" => 1, # should we log by default?
23             "log_file" => '/tmp/error.log', # default error log for notifys and explodes
24              
25             "log_explode" => 0, # should we log explodes by default?
26             "explode_log" => '/tmp/explode.log', # default error log for explodes ONLY
27              
28             "log_notify" => 0, # should we log notifys by default?
29             "notify_log" => '/tmp/notify.log', # default error log for notifys ONLY
30              
31             "store_vars" => 1, # should we store variables by default? true/false
32             "stack_trace" => 1, # should we do a stack trace by default? true/false
33             "store_env" => 1, # should we store our environment by default? true/false
34              
35             "die_to_stdout" => 0, # should we send our death_message to STDOUT by default? true/false
36             "die_everywhere" => 0, # should we send our death_message to STDOUT and STDERR by default? true/false
37             "die_quietly" => 0, # should we not print our death_message anywhere? true/false
38              
39             "error_function" => '', # function to call if Carp::Notify encounters an error
40             "death_function" => '', # function to call upon termination, used in place of death_message
41              
42             # What would you like to die with? This is probably the message that's going to your user in
43             # his browser, so make it something nice. You'll have to set the content type yourself, though.
44             # Why's that, you ask? I wanted to be sure that you had the option of easily redirecting to
45             # a different page if you'd prefer.
46              
47             "death_message" => <<'eoE'
48             Content-type:text/plain\n\n
49              
50             We're terribly sorry, but some horrid internal error seems to have occurred. We are actively
51             looking into the problem and hope to repair the error shortly. We're sorry for any inconvenience.
52              
53             eoE
54             );
55             # end defaults. Don't mess with anything else! I mean it!
56              
57             my $settables = "(?:" . join('|', keys %def) . ')';
58              
59             BEGIN {
60 1     1   11 $Carp::Notify::can_email = 1;
61 1     1   60 eval "use Socket";
  1         901  
  1         4776  
  1         611  
62 1 50       5 $Carp::Notify::can_email = 0 if $@;
63              
64 1         68 $Carp::Notify::fatal = 1; # doesn't really belong here, but it's the cleanest place to put it...
65             };
66              
67              
68             {
69 1     1   10 no strict 'refs';
  1         1  
  1         1320  
70             my $calling_package = undef;
71              
72             my %storable_vars = ();
73              
74             my @storable_vars = ();
75             my %init = ();
76              
77             sub import {
78 9     9   25050 my ($package, $file, $line) = caller;
79 9         18 $calling_package = $package;
80              
81 9         15 *{$package . "::explode"} = \&Carp::Notify::explode;
  9         42  
82 9         15 *{$package . "::notify"} = \&Carp::Notify::notify;
  9         30  
83              
84 9         30 while (defined (my $var = shift)){
85 25 50       46 if ($var eq ""){die ("Error...tried to import undefined value in $file, Line $line\n")};
  0         0  
86              
87 25 50       6787 if ($var =~ /^$settables$/o){
88 0         0 $def{$var} = shift;
89 0         0 next;
90             };
91              
92 25 100       67 push @storable_vars, $var if $var =~ /^[\$@%&]/;
93 25 100       50 push @{$storable_vars{$calling_package}}, $var if $var =~ /^[\$@%&]/;
  8         18  
94              
95             # see if we want to overload croak or export anything while we're at it.
96              
97 25 100       43 *{$package . "::croak"} = \&Carp::Notify::explode if $var eq "croak";
  2         8  
98 25 100       51 *{$package . "::carp"} = \&Carp::Notify::notify if $var eq "carp";
  2         12  
99 25 100       47 *{$package . "::make_storable"} = \&Carp::Notify::make_storable if $var eq "make_storable";
  2         9  
100 25 100       149 *{$package . "::make_unstorable"} = \&Carp::Notify::make_unstorable if $var eq "make_unstorable";
  2         31  
101             };
102             };
103              
104             sub store_vars {
105 3     3 1 4172 my $stored_vars = "";
106 3         15 my $calling_package = (caller(1))[0]; # eek! This may not always work
107              
108 3         6 foreach my $storable_var (@{$storable_vars{$calling_package}}){
  3         9  
109 12 100       45 my $type = $1 if $storable_var =~ s/([\$@%&])//;
110              
111 12         20 my $package = $calling_package . "::";
112 12 50       27 $package = $1 if $storable_var =~ s/(.+::)//;
113              
114 12 100       46 if ($type eq '$') {
    100          
    100          
    100          
115 2         2 my $storable_val = ${$package . "$storable_var"};
  2         8  
116 2         7 $stored_vars .= "\t\$${package}$storable_var : $storable_val\n";next;
  2         4  
117             }
118             elsif ($type eq '@') {
119 2         3 my @storable_val = @{$package . "$storable_var"};
  2         9  
120 2         8 $stored_vars .= "\t\@${package}$storable_var : (@storable_val)\n";next;
  2         4  
121             }
122             elsif ($type eq '%') {
123 2         3 my %temp_hash = %{$package . "$storable_var"};
  2         31  
124 2         8 my @storable_val = map {"\n\t\t$_ => $temp_hash{$_}"} keys %temp_hash;
  4         14  
125 2         9 $stored_vars .= "\t\%${package}$storable_var : @storable_val\n";next;
  2         7  
126             }
127             elsif ($type eq '&'){
128 2         3 my $storable_val = &{$package . "$storable_var"};
  2         9  
129 2         10 $stored_vars .= "\t\&${package}$storable_var : $storable_val\n";next;
  2         5  
130             };
131             };
132              
133 3         10 return $stored_vars;
134             };
135              
136             sub make_storable {
137 1     1 1 433 foreach my $var (@_){
138 4 50       19 push @storable_vars, $var if $var =~ /^[\$@%&]/;;
139             };
140 1         5 return 1;
141             };
142              
143             sub make_unstorable {
144 1     1 1 3 my $no_store = join("|", map {quotemeta} @_);
  4         11  
145 1         3 @storable_vars = grep {!/^(?:$no_store)$/} @storable_vars;
  12         127  
146 1         7 return 1;
147             };
148              
149             # hee hee! Remember, a notification is just an explosion that isn't fatal. So we use our nifty handy dandy
150             # fatal class variable to tell explode that it's not a fatal error. explode() will set fatal back to 1 once
151             # it realizes that errors are non-fatal. That way a future explosion will still be fatal.
152             #
153             # and then goto &explode makes perl think it just started at the explode function. Even caller can't catch it!
154             sub notify {
155 0     0 1 0 $Carp::Notify::fatal = 0;
156 0         0 goto &explode;
157             };
158              
159             sub explode {
160 0     0 1 0 my $errors = undef;
161              
162 0         0 my %init = ();
163              
164 0         0 while (defined (my $arg = shift)) {
165 0 0       0 if ($arg =~ /^$settables$/o){
  0         0  
166 0         0 $init{$arg} = shift;
167             }
168             else {$errors .= "\t$arg\n"};
169             };
170              
171 0         0 %init = (%def, %init);
172              
173 0 0       0 my $stored_vars = store_vars() if $init{'store_vars'};
174 0 0       0 my $stack = stack_trace() if $init{'stack_trace'};
175 0 0       0 my $environment = store_env() if $init{'store_env'};
176              
177 0         0 my $message = "";
178              
179 0 0       0 my $method = $Carp::Notify::fatal ? 'explosion' : 'notification';
180              
181 0         0 $message .= "An error via $method occurred on " . today() . "\n";
182              
183 0 0       0 $message .= "\n>>>>>>>>>\nERROR MESSAGES\n>>>>>>>>>\n\n$errors\n<<<<<<<<<\nEND ERROR MESSAGES\n<<<<<<<<<\n" if $errors;
184 0 0       0 $message .= "\n>>>>>>>>>\nSTORED VARIABLES\n>>>>>>>>>\n\n$stored_vars\n<<<<<<<<<\nEND STORED VARIABLES\n<<<<<<<<<\n" if $stored_vars;
185 0 0       0 $message .= "\n>>>>>>>>>\nCALL STACK TRACE\n>>>>>>>>>\n\n$stack\n<<<<<<<<<\nEND CALL STACK TRACE\n<<<<<<<<<\n" if $init{'stack_trace'};
186 0 0       0 $message .= "\n>>>>>>>>>\nENVIRONMENT\n>>>>>>>>>\n\n$environment\n<<<<<<<<<\nEND ENVIRONMENT\n<<<<<<<<<\n" if $init{'store_env'};
187              
188 0 0 0     0 log_it(
    0 0        
189             "log_it" => $init{'log_it'},
190             "log_file" => $init{'log_file'},
191              
192             "log_explode" => $Carp::Notify::fatal && $init{"log_explode"} ? $init{"log_explode"} : 0,
193             "explode_log" => $init{'explode_log'},
194              
195             "log_notify" => ! $Carp::Notify::fatal && $init{"log_notify"} ? $init{"log_notify"} : 0,
196             "notify_log" => $init{"notify_log"},
197              
198             "message" => $message,
199             "error_function" => $init{'error_function'}
200             );
201              
202 0 0       0 simple_smtp_mailer(
203             "email" => $init{'email'},
204             "return" => $init{'return'},
205             "message" => $message,
206             "subject" => $init{'subject'},
207             "smtp" => $init{'smtp'},
208             "port" => $init{'port'},
209             "error_function" => $init{'error_function'}
210             ) if $init{'email_it'};
211              
212 0 0       0 if ($Carp::Notify::fatal){
213 0 0       0 if ($init{'die_quietly'}){
    0          
214 0         0 exit;
215             }
216             elsif ($init{'death_function'}){
217 0 0       0 if (ref $init{'death_function'} eq 'CODE'){
218 0         0 $init{'death_function'}->(%init, 'errors' => $errors);
219             }
220             else {
221 1     1   7 no strict 'vars';
  1         1  
  1         1420  
222 0         0 my ($calling_package) = (caller)[0];
223 0         0 my $package = $calling_package . "::";
224 0 0       0 $package = $1 if $init{'death_function'} =~ s/(.+::)//;
225 0         0 $init{'death_function'} =~ s/^&//;
226 0         0 &{$package . $init{'death_function'}}(%init, 'errors' => $errors);
  0         0  
227 0         0 exit;
228             };
229             }
230             else {
231 0 0       0 if ($init{'die_to_stdout'}){
232 0 0       0 print STDERR $init{'death_message'} if $init{'die_everywhere'};
233 0         0 print $init{'death_message'};
234 0         0 exit;
235             }
236             else {
237 0 0       0 print $init{'death_message'} if $init{'die_everywhere'};
238 0         0 die $init{'death_message'};
239             };
240             };
241             }
242             else {
243 0         0 $Carp::Notify::fatal = 1;
244 0         0 return undef;
245             };
246             };
247             };
248              
249              
250             # psst! If you're looking for store_vars, it's up at the top wrapped up with import!
251              
252             sub store_env {
253 1     1 1 5070 my $env = '';
254 1         18 foreach (sort keys %ENV){
255 22         41 $env .= "\t$_ : $ENV{$_}\n";
256             };
257 1         15 return $env;
258             };
259              
260             sub stack_trace {
261 0     0 1 0 my $caller_count = 1;
262 0         0 my $caller_stack = undef;
263 0         0 my @verbose_caller = ("Package: ", "Filename: ", "Line number: ", "Subroutine: ", "Has Args? : ", "Want array? : ", "Evaltext: ", "Is require? : ");
264              
265 0 0       0 push @verbose_caller, ("Hints: ", "Bitmask: ") if $] >= 5.006; # 5.6 has a more verbose caller stack.
266              
267 0         0 while (my @caller = caller($caller_count++)){
268 0         0 $caller_stack .= "\t---------\n";
269 0         0 foreach (0..$#caller){
270 0 0       0 $caller_stack .= "\t\t$verbose_caller[$_]$caller[$_]\n" if $caller[$_];
271             };
272             };
273              
274 0         0 $caller_stack .= "\t---------\n";
275 0         0 return $caller_stack;
276             };
277              
278             sub log_it {
279 0     0 1 0 my %init = @_;
280              
281 0         0 my $message = $init{message};
282              
283 0         0 local *LOG;
284 0         0 my %pairs = (
285             "log_notify" => "notify_log",
286             "log_explode" => "explode_log",
287             "log_it" => "log_file"
288             );
289              
290 0         0 foreach my $permission (grep {$init{$_}} keys %pairs) {
  0         0  
291 0         0 my $file = $init{$pairs{$permission}};
292 0 0       0 if (ref $file){
293 0         0 print $file "\n__________________\n$message\n__________________\n";
294             }
295             else {
296 0 0       0 open (LOG, ">>$file") or error($init{'error_function'},"Cannot open log file: $!");
297 0         0 print LOG "\n__________________\n$message\n__________________\n";
298 0 0       0 close LOG or error($init{'error_function'},"Cannot close log file: $!");
299             };
300             };
301             };
302              
303             sub simple_smtp_mailer {
304 0     0 0 0 my %init = @_;
305 0         0 my $message = $init{"message"};
306 0 0       0 error($init{'error_function'},"Cannot email: Socket.pm could not load!") unless $Carp::Notify::can_email;
307              
308 0         0 local *MAIL;
309 0         0 my $response = undef;
310 0         0 my ($s_tries, $c_tries) = (5, 5);
311 0         0 local $\ = "\015\012";
312 0         0 local $/ = "\015\012";
313              
314             # connect to the server
315 0   0     0 1 while ($s_tries-- && ! socket(MAIL, PF_INET, SOCK_STREAM, getprotobyname('tcp')));
316 0 0       0 return error($init{'error_function'}, "Socket error $!") if $s_tries < 0;
317              
318 0         0 my $remote_address = inet_aton($init{'smtp'});
319 0         0 my $paddr = sockaddr_in($init{'port'}, $remote_address);
320 0   0     0 1 while ! connect(MAIL, $paddr) && $c_tries--;
321 0 0       0 return error($init{'error_function'}, "Connect error $!") if $c_tries < 0;
322              
323             # keep our bulk pipes piping hot.
324 0         0 select((select(MAIL), $| = 1)[0]);
325             # connected
326              
327             # build the envelope
328 0         0 my @conversation =
329             (
330             ["", "No response from server: ?"],
331             ["HELO $def{'domain'}", "Mean ole' server won't say HELO: ?"],
332             ["RSET", "Cannot reset connection: ?"],
333             ["MAIL FROM:<$def{'return'}>", "Invalid Sender: ?"],
334             ["RCPT TO:<$init{'email'}>", "Invalid Recipient: ?"],
335             ["DATA", "Not ready to accept data: ?"]
336             );
337              
338 0         0 while (my $array_ref = shift @conversation){
339 0         0 my ($i_say, $i_die) = @{$array_ref};
  0         0  
340 0 0       0 print MAIL $i_say if $i_say;
341 0   0     0 my $response = || "";
342              
343 0 0 0     0 if (! $response || $response =~ /^[45]/){
344 0         0 $i_die =~ s/\?/$response/;
345 0         0 return error($init{'error_function'}, $i_die);
346             };
347 0 0       0 return error($init{'error_function'}, "Server disconnected: $response") if $response =~ /^221/;
348              
349             };
350             # built
351              
352             # send the data
353 0         0 print MAIL "Date: ", today();
354 0         0 print MAIL "From: $init{'return'}";
355 0         0 print MAIL "Subject: $init{'subject'}";
356 0         0 print MAIL "To: $init{'email'}";
357 0         0 print MAIL "X-Priority:2 (High)";
358 0         0 print MAIL "X-Carp-Notify: $Carp::Notify::VERSION";
359              
360 0         0 print MAIL "";
361              
362 0         0 $message =~ s/^\./../gm;
363 0         0 $message =~ s/(\r?\n|\r)/\015\012/g;
364              
365 0         0 print MAIL $message;
366              
367 0         0 print MAIL ".";
368             # sent
369              
370 0         0 return 1; # yay!
371             };
372              
373             sub today {
374 1     1 0 8939 my @months = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
375 1         4 my @days = qw(Sun Mon Tue Wed Thu Fri Sat);
376              
377 1         24 my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(time);
378 1         3 $year += 1900;
379 1         4 my ($gmin, $ghour, $gsdst) = (gmtime(time))[1,2, -1];
380              
381 1         2 my $diffhour = $hour - $ghour;
382 1 50       5 $diffhour = 12 - $diffhour if $diffhour > 12;
383 1 50       4 $diffhour = 12 + $diffhour if $diffhour < -12;
384              
385 1         6 ($diffhour = sprintf("%03d", $diffhour)) =~ s/^0/\+/;
386              
387 1         9 return sprintf("%s, %02d %s %04d %02d:%02d:%02d %05s",
388             $days[$wday], $mday, $months[$mon], $year + 1900, $hour, $min, $sec, $diffhour . sprintf("%02d", $min - $gmin));
389             };
390              
391             # error does nothing unless you specify the error_function, in that case it's called with the error provided.
392             sub error {
393 0     0 0   my ($func, $error) = @_;
394 0 0         if (ref $func eq 'CODE'){
    0          
395 0           $func->($error);
396             }
397 0           elsif ($func){
398 1     1   5 no strict 'refs';
  1         2  
  1         105  
399 0           my ($calling_package) = (caller)[0];
400 0           my $package = $calling_package . "::";
401 0 0         $package = $1 if $$func =~ s/(.+::)//;
402 0           &{$package . $func}($error);
  0            
403             }
404             else {undef};
405             };
406              
407              
408             1;
409              
410             __END__