File Coverage

blib/lib/Log/Report.pm
Criterion Covered Total %
statement 192 259 74.1
branch 91 180 50.5
condition 37 70 52.8
subroutine 28 41 68.2
pod 19 20 95.0
total 367 570 64.3


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