File Coverage

blib/lib/Log/Report.pm
Criterion Covered Total %
statement 188 255 73.7
branch 86 176 48.8
condition 36 70 51.4
subroutine 28 41 68.2
pod 19 20 95.0
total 357 562 63.5


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