File Coverage

blib/lib/Log/Report.pm
Criterion Covered Total %
statement 187 247 75.7
branch 84 174 48.2
condition 36 70 51.4
subroutine 28 37 75.6
pod 19 20 95.0
total 354 548 64.6


line stmt bran cond sub pod time code
1             # Copyrights 2007-2017 by [Mark Overmeer].
2             # For other contributors see ChangeLog.
3             # See the manual pages for details on the licensing terms.
4             # Pod stripped from pm file by OODoc 2.02.
5              
6 13     13   282548 use warnings;
  13         32  
  13         396  
7 13     13   66 use strict;
  13         22  
  13         398  
8              
9             package Log::Report;
10 13     13   66 use vars '$VERSION';
  13         24  
  13         686  
11             $VERSION = '1.21';
12              
13 13     13   84 use base 'Exporter';
  13         26  
  13         1040  
14              
15 13     13   80 use List::Util qw/first/;
  13         25  
  13         1245  
16 13     13   78 use Scalar::Util qw/blessed/;
  13         26  
  13         766  
17              
18 13     13   5200 use Log::Report::Util;
  13         746987  
  13         36496  
19             my $lrm = 'Log::Report::Message';
20              
21             ### if you change anything here, you also have to change Log::Report::Minimal
22             my @make_msg = qw/__ __x __n __nx __xn N__ N__n N__w/;
23             my @functions = qw/report dispatcher try textdomain/;
24             my @reason_functions = qw/trace assert info notice warning
25             mistake error fault alert failure panic/;
26              
27             our @EXPORT_OK = (@make_msg, @functions, @reason_functions);
28              
29             sub _whats_needed(); sub dispatcher($@); sub textdomain(@);
30             sub trace(@); sub assert(@); sub info(@); sub notice(@); sub warning(@);
31             sub mistake(@); sub error(@); sub fault(@); sub alert(@); sub failure(@);
32             sub panic(@);
33             sub __($); sub __x($@); sub __n($$$@); sub __nx($$$@); sub __xn($$$@);
34             sub N__($); sub N__n($$); sub N__w(@);
35              
36             #
37             # Some initiations
38             #
39              
40             my $reporter = {};
41             my $default_mode = 0;
42             my @nested_tries;
43              
44             # we can only load these after Log::Report has compiled, because
45             # the use this module themselves.
46              
47             require Log::Report::Die;
48             require Log::Report::Domain;
49             require Log::Report::Message;
50             require Log::Report::Exception;
51             require Log::Report::Dispatcher;
52             require Log::Report::Dispatcher::Try;
53              
54             #eval "require Log::Report::Translator::POT"; panic $@ if $@;
55             #, translator => Log::Report::Translator::POT->new(charset => 'utf-8');
56             textdomain 'log-report';
57              
58             my $default_dispatcher = dispatcher PERL => 'default', accept => 'NOTICE-';
59              
60              
61             sub report($@)
62 33 100   33 1 148 { my $opts = ref $_[0] eq 'HASH' ? +{ %{ (shift) } } : {};
  3         11  
63 33         76 my $reason = shift;
64 33 100       144 my $stop = exists $opts->{is_fatal} ? $opts->{is_fatal} : is_fatal $reason;
65              
66 33         130 my @disp;
67 33 100       95 if(defined(my $try = $nested_tries[-1]))
68 6 50 66     28 { push @disp, @{$reporter->{needs}{$reason}||[]}
  1 100       4  
69             unless $stop || $try->hides($reason);
70 6 50       29 push @disp, $try if $try->needs($reason);
71             }
72             else
73 27 100       45 { @disp = @{$reporter->{needs}{$reason} || []};
  27         164  
74             }
75              
76 33 50       117 is_reason $reason
77             or error __x"token '{token}' not recognized as reason", token=>$reason;
78              
79             # return when no-one needs it: skip unused trace() fast!
80 33 100 66     265 @disp || $stop
81             or return;
82              
83             $opts->{errno} ||= $!+0 || $? || 1
84 15 100 50     57 if use_errno($reason) && !defined $opts->{errno};
      33        
      100        
85              
86 15 50       158 if(my $to = delete $opts->{to})
87             { # explicit destination, still disp may not need it.
88 0 0       0 if(ref $to eq 'ARRAY')
89 0         0 { my %disp = map +($_->name => $_), @disp;
90 0         0 @disp = grep defined, @disp{@$to};
91             }
92             else
93 0         0 { @disp = grep $_->name eq $to, @disp;
94             }
95 0 0 0     0 @disp || $stop
96             or return;
97             }
98              
99 15         24 my $message = shift;
100              
101 15 50       94 unless(Log::Report::Dispatcher->can('collectLocation'))
102             { # internal Log::Report error can result in "deep recursions".
103 0         0 eval "require Carp"; Carp::confess($message);
  0         0  
104             }
105 15   66     109 $opts->{location} ||= Log::Report::Dispatcher->collectLocation;
106              
107 15         61 my $exception;
108 15 100       86 if(!blessed $message)
    50          
    0          
109             { # untranslated message into object
110 12 50       56 @_%2 and error __x"odd length parameter list with '{msg}'", msg => $message;
111 12         50 $message = $lrm->new(_prepend => $message, @_);
112             }
113             elsif($message->isa('Log::Report::Exception'))
114 3         6 { $exception = $message;
115 3         9 $message = $exception->message;
116             }
117             elsif($message->isa('Log::Report::Message'))
118 0 0       0 { @_==0 or error __x"a message object is reported with more parameters";
119             }
120              
121 15 50       55 if(my $to = $message->to)
122 0         0 { @disp = grep $_->name eq $to, @disp;
123 0 0       0 @disp or return;
124             }
125              
126 15         44 my $domain = $message->domain;
127 15 100       45 if(my $filters = $reporter->{filters})
128             {
129             DISPATCHER:
130 1         3 foreach my $d (@disp)
131 1         3 { my ($r, $m) = ($reason, $message);
132 1         2 foreach my $filter (@$filters)
133 2 100 66     3 { next if keys %{$filter->[1]} && !$filter->[1]{$d->name};
  2         12  
134 1         5 ($r, $m) = $filter->[0]->($d, $opts, $r, $m, $domain);
135 1 50       9 $r or next DISPATCHER;
136             }
137 1         4 $d->log($opts, $r, $m, $domain);
138             }
139             }
140             else
141 14         67 { $_->log($opts, $reason, $message, $domain) for @disp;
142             }
143              
144 15 100       577 if($stop)
145             { # $^S = $EXCEPTIONS_BEING_CAUGHT; parse: undef, eval: 1, else 0
146 6 50 0     31 (defined($^S) ? $^S : 1) or exit($opts->{errno} || 0);
    50          
147              
148 6   100     25 $! = $opts->{errno} || 0;
149 6   66     28 $@ = $exception || Log::Report::Exception->new(report_opts => $opts
150             , reason => $reason, message => $message);
151 6         52 die; # $@->PROPAGATE() will be called, some eval will catch this
152             }
153              
154 9         66 @disp;
155             }
156              
157              
158             my %disp_actions = map +($_ => 1), qw/
159             close find list disable enable mode needs filter active-try do-not-reopen
160             /;
161              
162             my $reopen_disp = 1;
163              
164             sub dispatcher($@)
165 31 100   31 1 7612 { if(! $disp_actions{$_[0]})
166 18         55 { my ($type, $name) = (shift, shift);
167              
168             # old dispatcher with same name will be closed in DESTROY
169 18         48 my $disps = $reporter->{dispatchers};
170            
171 18 100       58 if(!$reopen_disp)
172 13     0   148 { my $has = first {$_->name eq $name} @$disps;
  0         0  
173 13 50 33     89 if(defined $has && $has ne $default_dispatcher)
174 0 0       0 { my $default = $name eq 'default'
175             ? ' (refreshing configuration instead)' : '';
176 0         0 trace "not reopening $name$default";
177 0         0 return $has;
178             }
179             }
180              
181 18         77 my @disps = grep $_->name ne $name, @$disps;
182 18 50       74 trace "reopening dispatcher $name" if @disps != @$disps;
183              
184 18         90 my $disp = Log::Report::Dispatcher
185             ->new($type, $name, mode => $default_mode, @_);
186              
187 18 50       75 push @disps, $disp if $disp;
188 18         43 $reporter->{dispatchers} = \@disps;
189              
190 18         68 _whats_needed;
191 18 50       88 return $disp ? ($disp) : undef;
192             }
193              
194 13         31 my $command = shift;
195 13 100       39 if($command eq 'list')
196 7 50       18 { mistake __"the 'list' sub-command doesn't expect additional parameters"
197             if @_;
198 7         11 my @disp = @{$reporter->{dispatchers}};
  7         18  
199 7 100       21 push @disp, $nested_tries[-1] if @nested_tries;
200 7         22 return @disp;
201             }
202 6 50       21 if($command eq 'needs')
203 0   0     0 { my $reason = shift || 'undef';
204 0 0       0 error __"the 'needs' sub-command parameter '{reason}' is not a reason"
205             unless is_reason $reason;
206 0         0 my $disp = $reporter->{needs}{$reason};
207 0 0       0 return $disp ? @$disp : ();
208             }
209 6 100       17 if($command eq 'filter')
210 2         4 { my $code = shift;
211 2 50       6 error __"the 'filter' sub-command needs a CODE reference"
212             unless ref $code eq 'CODE';
213 2         9 my %names = map +($_ => 1), @_;
214 2         4 push @{$reporter->{filters}}, [ $code, \%names ];
  2         6  
215 2         5 return ();
216             }
217 4 50       12 if($command eq 'active-try')
218 0         0 { return $nested_tries[-1];
219             }
220 4 50       14 if($command eq 'do-not-reopen')
221 0         0 { $reopen_disp = 0;
222 0         0 return ();
223             }
224              
225 4 50       16 my $mode = $command eq 'mode' ? shift : undef;
226              
227 4   33     26 my $all_disp = @_==1 && $_[0] eq 'ALL';
228 4         12 my $disps = $reporter->{dispatchers};
229 4         6 my @disps;
230 4 50       14 if($all_disp) { @disps = @$disps }
  0         0  
231             else
232             { # take the dispatchers in the specified order. Both lists
233             # are small, so O(x²) is small enough
234 4         12 for my $n (@_) { push @disps, grep $_->name eq $n, @$disps }
  4         30  
235             }
236              
237 4 0 33     20 error __"only one dispatcher name accepted in SCALAR context"
      33        
238             if @disps > 1 && !wantarray && defined wantarray;
239              
240 4 50       13 if($command eq 'close')
    0          
    0          
    0          
241 4         15 { my %kill = map +($_->name => 1), @disps;
242 4         13 @$disps = grep !$kill{$_->name}, @$disps;
243 4         26 $_->close for @disps;
244             }
245 0         0 elsif($command eq 'enable') { $_->_disabled(0) for @disps }
246 0         0 elsif($command eq 'disable') { $_->_disabled(1) for @disps }
247             elsif($command eq 'mode')
248 0 0       0 { Log::Report::Dispatcher->defaultMode($mode) if $all_disp;
249 0         0 $_->_set_mode($mode) for @disps;
250             }
251              
252             # find does require reinventarization
253 4 50       31 _whats_needed if $command ne 'find';
254              
255 4 50       30 wantarray ? @disps : $disps[0];
256             }
257              
258 13     13   5647 END { $_->close for @{$reporter->{dispatchers}} }
  13         142  
259              
260             # _whats_needed
261             # Investigate from all dispatchers which reasons will need to be
262             # passed on. After dispatchers are added, enabled, or disabled,
263             # this method shall be called to re-investigate the back-ends.
264              
265             sub _whats_needed()
266 22     22   38 { my %needs;
267 22         38 foreach my $disp (@{$reporter->{dispatchers}})
  22         65  
268 25         117 { push @{$needs{$_}}, $disp for $disp->needs;
  196         442  
269             }
270 22         55 $reporter->{needs} = \%needs;
271             }
272              
273              
274             sub try(&@)
275 15     15 1 4322 { my $code = shift;
276              
277 15 50       54 @_ % 2
278             and report {location => [caller 0]}, PANIC =>
279             __x"odd length parameter list for try(): forgot the terminating ';'?";
280              
281 15         94 my $disp = Log::Report::Dispatcher::Try->new(TRY => 'try', @_);
282 15         39 push @nested_tries, $disp;
283              
284             # user's __DIE__ handlers would frustrate the exception mechanism
285 15         48 local $SIG{__DIE__};
286              
287 15         28 my ($ret, @ret);
288 15 100       42 if(!defined wantarray) { eval { $code->() } } # VOID context
  9 100       18  
  9         36  
289 1         2 elsif(wantarray) { @ret = eval { $code->() } } # LIST context
  1         4  
290 5         9 else { $ret = eval { $code->() } } # SCALAR context
  5         15  
291              
292 15         577 my $err = $@;
293 15         29 pop @nested_tries;
294              
295 15   66     83 my $is_exception = blessed $err && $err->isa('Log::Report::Exception');
296 15 100 100     82 if(!$is_exception && $err && !$disp->wasFatal)
      66        
297 6         23 { ($err, my($opts, $reason, $text))
298             = Log::Report::Die::die_decode($err, on_die => $disp->die2reason);
299 6         23 $disp->log($opts, $reason, __$text);
300             }
301              
302 15 100       99 $disp->died($err)
    100          
303             if $is_exception ? $err->isFatal : $err;
304              
305 15         71 $@ = $disp;
306              
307 15 100       82 wantarray ? @ret : $ret;
308             }
309              
310              
311 19     19 1 1226 sub trace(@) {report TRACE => @_}
312 1     1 1 1195 sub assert(@) {report ASSERT => @_}
313 2     2 1 1689 sub info(@) {report INFO => @_}
314 3     3 1 1185 sub notice(@) {report NOTICE => @_}
315 1     1 1 1217 sub warning(@) {report WARNING => @_}
316 0     0 1 0 sub mistake(@) {report MISTAKE => @_}
317 2     2 1 10 sub error(@) {report ERROR => @_}
318 0     0 1 0 sub fault(@) {report FAULT => @_}
319 0     0 1 0 sub alert(@) {report ALERT => @_}
320 1     1 1 7 sub failure(@) {report FAILURE => @_}
321 0     0 1 0 sub panic(@) {report PANIC => @_}
322              
323              
324              
325             sub __($)
326 28     28   1294 { my ($cpkg, $fn, $linenr) = caller;
327 28         93 $lrm->new
328             ( _msgid => shift
329             , _domain => pkg2domain($cpkg)
330             , _use => "$fn line $linenr"
331             );
332             }
333              
334              
335             # label "msgid" added before first argument
336             sub __x($@)
337 50     50   2718 { my ($cpkg, $fn, $linenr) = caller;
338 50 50       165 @_%2 or error __x"even length parameter list for __x at {where}",
339             where => "$fn line $linenr";
340              
341 50         88 my $msgid = shift;
342 50         164 $lrm->new
343             ( _msgid => $msgid
344             , _expand => 1
345             , _domain => pkg2domain($cpkg)
346             , _use => "$fn line $linenr"
347             , @_
348             );
349             }
350              
351              
352             sub __n($$$@)
353 4     4   1701 { my ($single, $plural, $count) = (shift, shift, shift);
354 4         16 my ($cpkg, $fn, $linenr) = caller;
355 4         14 $lrm->new
356             ( _msgid => $single
357             , _plural => $plural
358             , _count => $count
359             , _domain => pkg2domain($cpkg)
360             , _use => "$fn line $linenr"
361             , @_
362             );
363             }
364              
365              
366             sub __nx($$$@)
367 13     13   3080 { my ($single, $plural, $count) = (shift, shift, shift);
368 13         39 my ($cpkg, $fn, $linenr) = caller;
369 13         54 $lrm->new
370             ( _msgid => $single
371             , _plural => $plural
372             , _count => $count
373             , _expand => 1
374             , _domain => pkg2domain($cpkg)
375             , _use => "$fn line $linenr"
376             , @_
377             );
378             }
379              
380              
381             sub __xn($$$@) # repeated for prototype
382 3     3   10 { my ($single, $plural, $count) = (shift, shift, shift);
383 3         11 my ($cpkg, $fn, $linenr) = caller;
384 3         11 $lrm->new
385             ( _msgid => $single
386             , _plural => $plural
387             , _count => $count
388             , _expand => 1
389             , _domain => pkg2domain($cpkg)
390             , _use => "$fn line $linenr"
391             , @_
392             );
393             }
394              
395              
396 13     13 1 916 sub N__($) { $_[0] }
397              
398              
399 1     1 1 6 sub N__n($$) {@_}
400              
401              
402 0     0 1 0 sub N__w(@) {split " ", $_[0]}
403              
404              
405             sub import(@)
406 110     110   345 { my $class = shift;
407              
408 110 50       342 if($INC{'Log/Report/Minimal.pm'})
409 0         0 { my ($pkg, $fn, $line) = caller; # do not report on LR:: modules
410 0 0       0 if(index($pkg, 'Log::Report::') != 0)
411             { # @pkgs empty during release testings of L::R distributions
412 0         0 my @pkgs = Log::Report::Optional->usedBy;
413 0 0       0 die "Log::Report loaded too late in $fn line $line, "
414             . "put in $pkg before ", (join ',', @pkgs) if @pkgs;
415             }
416             }
417              
418 110   50     833 my $to_level = ($_[0] && $_[0] =~ m/^\+\d+$/ ? shift : undef) || 0;
419 110 100       303 my $textdomain = @_%2 ? shift : undef;
420 110         284 my %opts = @_;
421              
422 110         638 my ($pkg, $fn, $linenr) = caller $to_level;
423 110         230 my $domain;
424              
425 110 100       275 if(defined $textdomain)
426 98         338 { pkg2domain $pkg, $textdomain, $fn, $linenr;
427 98         1350 $domain = textdomain $textdomain;
428             }
429              
430             ### Log::Report options
431              
432 110 50       289 if(exists $opts{mode})
433 0   0     0 { $default_mode = delete $opts{mode} || 0;
434 0         0 Log::Report::Dispatcher->defaultMode($default_mode);
435 0         0 dispatcher mode => $default_mode, 'ALL';
436             }
437              
438 110         164 my @export;
439 110 50       252 if(my $in = delete $opts{import})
440 0 0       0 { push @export, ref $in eq 'ARRAY' ? @$in : $in;
441             }
442             else
443 110         431 { push @export, @functions, @make_msg;
444              
445 110   100     398 my $syntax = delete $opts{syntax} || 'SHORT';
446 110 50 0     256 if($syntax eq 'SHORT')
    0          
447 110         321 { push @export, @reason_functions
448             }
449             elsif($syntax ne 'REPORT' && $syntax ne 'LONG')
450 0         0 { error __x"syntax flag must be either SHORT or REPORT, not `{flag}' in {fn} line {line}"
451             , flag => $syntax, fn => $fn, line => $linenr;
452             }
453             }
454              
455 110 50       252 if(my $msg_class = delete $opts{message_class})
456 0 0       0 { $msg_class->isa($lrm)
457             or error __x"message_class {class} does not extend {base}"
458             , base => $lrm, class => $msg_class;
459 0         0 $lrm = $msg_class;
460             }
461              
462 110         12952 $class->export_to_level(1+$to_level, undef, @export);
463              
464             ### Log::Report::Domain configuration
465              
466 110 50       4619 if(!%opts) { }
    0          
467             elsif($domain)
468 0         0 { $domain->configure(%opts, where => [$pkg, $fn, $linenr ]) }
469             else
470 0         0 { error __x"no domain for configuration options in {fn} line {line}"
471             , fn => $fn, line => $linenr;
472             }
473             }
474              
475             # deprecated, since we have a ::Domain object in 1.00
476             sub translator($;$$$$)
477             { # replaced by (textdomain $domain)->configure
478              
479 0     0 0 0 my ($class, $name) = (shift, shift);
480 0 0       0 my $domain = textdomain $name
481             or error __x"textdomain `{domain}' for translator not defined"
482             , domain => $name;
483              
484 0 0       0 @_ or return $domain->translator;
485              
486 0         0 my ($translator, $pkg, $fn, $line) = @_;
487 0 0       0 ($pkg, $fn, $line) = caller # direct call, not via import
488             unless defined $pkg;
489              
490 0 0       0 $translator->isa('Log::Report::Translator')
491             or error __x"translator must be a {pkg} object for {domain}"
492             , pkg => 'Log::Report::Translator', domain => $name;
493              
494 0         0 $domain->configure(translator => $translator, where => [$pkg, $fn, $line]);
495             }
496              
497              
498             sub textdomain(@)
499 208 50 33 208 1 1229 { if(@_==1 && blessed $_[0])
500 0         0 { my $domain = shift;
501 0 0       0 $domain->isa('Log::Report::Domain') or panic;
502 0         0 return $reporter->{textdomains}{$domain->name} = $domain;
503             }
504              
505 208 50       480 if(@_==2)
506             { # used for 'maintenance' and testing
507 0 0       0 return delete $reporter->{textdomains}{$_[0]} if $_[1] eq 'DELETE';
508 0 0       0 return $reporter->{textdomains}{$_[0]} if $_[1] eq 'EXISTS';
509             }
510              
511 208   100     667 my $name = (@_%2 ? shift : pkg2domain((caller)[0])) || 'default';
512 208   66     916 my $domain = $reporter->{textdomains}{$name}
513             ||= Log::Report::Domain->new(name => $name);
514              
515 208 50       725 $domain->configure(@_, where => [caller]) if @_;
516 208         423 $domain;
517             }
518              
519             #--------------
520              
521             sub needs(@)
522 0     0 1   { my $thing = shift;
523 0 0         my $self = ref $thing ? $thing : $reporter;
524 0     0     first {$self->{needs}{$_}} @_;
  0            
525             }
526              
527              
528             1;