File Coverage

blib/lib/Log/Report/Dispatcher.pm
Criterion Covered Total %
statement 146 165 88.4
branch 44 74 59.4
condition 25 51 49.0
subroutine 30 36 83.3
pod 14 18 77.7
total 259 344 75.2


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 15     15   88 use warnings;
  15         28  
  15         453  
6 15     15   74 use strict;
  15         27  
  15         386  
7              
8             package Log::Report::Dispatcher;
9 15     15   69 use vars '$VERSION';
  15         25  
  15         628  
10             $VERSION = '1.23';
11              
12              
13 15     15   286 use Log::Report 'log-report';
  15         174  
  15         83  
14 15         1388 use Log::Report::Util qw/parse_locale expand_reasons %reason_code
15 15     15   87 escape_chars/;
  15         29  
16              
17 15     15   97 use POSIX qw/strerror/;
  15         35  
  15         89  
18 15     15   983 use List::Util qw/sum first/;
  15         37  
  15         753  
19 15     15   82 use Encode qw/find_encoding FB_DEFAULT/;
  15         27  
  15         673  
20 15     15   3870 use Devel::GlobalDestruction qw/in_global_destruction/;
  15         22444  
  15         78  
21              
22             eval { POSIX->import('locale_h') };
23             if($@)
24 15     15   1284 { no strict 'refs';
  15         29  
  15         6176  
25             *setlocale = sub { $_[1] }; *LC_ALL = sub { undef };
26             }
27              
28             my %modes = (NORMAL => 0, VERBOSE => 1, ASSERT => 2, DEBUG => 3
29             , 0 => 0, 1 => 1, 2 => 2, 3 => 3);
30             my @default_accept = ('NOTICE-', 'INFO-', 'ASSERT-', 'ALL');
31             my %always_loc = map +($_ => 1), qw/ASSERT ALERT FAILURE PANIC/;
32              
33             my %predef_dispatchers = map +(uc($_) => __PACKAGE__.'::'.$_)
34             , qw/File Perl Syslog Try Callback Log4perl/;
35              
36             my @skip_stack = sub { $_[0][0] =~ m/^Log\:\:Report(?:\:\:|$)/ };
37              
38              
39             sub new(@)
40 37     37 1 158 { my ($class, $type, $name, %args) = @_;
41              
42             # $type is a class name or predefined name.
43             my $backend
44 37 0       144 = $predef_dispatchers{$type} ? $predef_dispatchers{$type}
    50          
45             : $type->isa('Log::Dispatch::Output') ? __PACKAGE__.'::LogDispatch'
46             : $type;
47              
48 37         1883 eval "require $backend";
49 37 50       182 $@ and alert "cannot use class $backend:\n$@";
50              
51 37         353 (bless {name => $name, type => $type, filters => []}, $backend)
52             ->init(\%args);
53             }
54              
55             my %format_reason =
56             ( LOWERCASE => sub { lc $_[0] }
57             , UPPERCASE => sub { uc $_[0] }
58             , UCFIRST => sub { ucfirst lc $_[0] }
59             , IGNORE => sub { '' }
60             );
61              
62             my $default_mode = 'NORMAL';
63              
64             sub init($)
65 37     37 0 100 { my ($self, $args) = @_;
66              
67 37   66     293 my $mode = $self->_set_mode(delete $args->{mode} || $default_mode);
68 37         107 $self->{locale} = delete $args->{locale};
69              
70 37   66     157 my $accept = delete $args->{accept} || $default_accept[$mode];
71 37         105 $self->{needs} = [ expand_reasons $accept ];
72              
73 37   100     1967 my $f = delete $args->{format_reason} || 'LOWERCASE';
74 37 50       213 $self->{format_reason} = ref $f eq 'CODE' ? $f : $format_reason{$f}
    50          
75             or error __x"illegal format_reason '{format}' for dispatcher",
76             format => $f;
77              
78 37         67 my $csenc;
79 37 50       118 if(my $cs = delete $args->{charset})
80 0 0       0 { my $enc = find_encoding $cs
81             or error __x"Perl does not support charset {cs}", cs => $cs;
82 15     15   97 $csenc = sub { no warnings 'utf8'; $enc->encode($_[0]) };
  15     0   25  
  15         22647  
  0         0  
  0         0  
83             }
84              
85 37   50 10   278 $self->{charset_enc} = $csenc || sub { $_[0] };
  10         58  
86 37         170 $self;
87             }
88              
89              
90             sub close()
91 37     37 1 204 { my $self = shift;
92 37 50       194 $self->{closed}++ and return undef;
93 37         104 $self->{disabled}++;
94 37         626 $self;
95             }
96              
97 17 50   17   3432 sub DESTROY { in_global_destruction or shift->close }
98              
99             #----------------------------
100              
101              
102 37     37 1 1587 sub name {shift->{name}}
103              
104              
105 0     0 1 0 sub type() {shift->{type}}
106              
107              
108 6     6 1 19 sub mode() {shift->{mode}}
109              
110             #Please use C $MODE;>
111 0     0 0 0 sub defaultMode($) {$default_mode = $_[1]}
112              
113             # only to be used via Log::Report::dispatcher(mode => ...)
114             # because requires re-investigating collective dispatcher needs
115             sub _set_mode($)
116 37     37   78 { my $self = shift;
117 37         378 my $mode = $self->{mode} = $modes{$_[0]};
118 37 50       116 defined $mode or panic "unknown run mode $_[0]";
119              
120 37         157 $self->{needs} = [ expand_reasons $default_accept[$mode] ];
121              
122 37 100       2752 trace __x"switching to run mode {mode} for {pkg}, accept {accept}"
123             , mode => $mode, pkg => ref $self, accept => $default_accept[$mode]
124             unless $self->isa('Log::Report::Dispatcher::Try');
125              
126 37         213 $mode;
127             }
128              
129             # only to be called from Log::Report::dispatcher()!!
130             # because requires re-investigating needs
131             sub _disabled($)
132 0     0   0 { my $self = shift;
133 0 0       0 @_ ? ($self->{disabled} = shift) : $self->{disabled};
134             }
135              
136              
137 1     1 1 1990 sub isDisabled() {shift->{disabled}}
138              
139              
140             sub needs(;$)
141 36     36 1 537 { my $self = shift;
142 36 50       115 return () if $self->{disabled};
143              
144 36         65 my $needs = $self->{needs};
145 36 100       178 @_ or return @$needs;
146              
147 7         15 my $need = shift;
148 7     51   56 first {$need eq $_} @$needs;
  51         100  
149             }
150              
151             #-----------
152              
153             sub log($$$$)
154 0     0 1 0 { panic "method log() must be extended per back-end";
155             }
156              
157              
158             sub translate($$$)
159 10     10 1 28 { my ($self, $opts, $reason, $msg) = @_;
160              
161 10         19 my $mode = $self->{mode};
162 10 50       32 my $code = $reason_code{$reason}
163             or panic "unknown reason '$reason'";
164              
165             my $show_loc
166             = $always_loc{$reason}
167             || ($mode==2 && $code >= $reason_code{WARNING})
168 10   33     74 || ($mode==3 && $code >= $reason_code{MISTAKE});
169              
170             my $show_stack
171             = $reason eq 'PANIC'
172             || ($mode==2 && $code >= $reason_code{ALERT})
173 10   33     75 || ($mode==3 && $code >= $reason_code{ERROR});
174              
175             my $locale
176             = defined $msg->msgid
177             ? ($opts->{locale} || $self->{locale}) # translate whole
178 10 50 0     45 : (textdomain $msg->domain)->nativeLanguage;
179              
180 10   50     67 my $oldloc = setlocale(&LC_ALL) // "";
181 10 50 33     35 setlocale(&LC_ALL, $locale)
182             if $locale && $locale ne $oldloc;
183              
184 10         32 my $r = $self->{format_reason}->((__$reason)->toString);
185 10 100       57 my $e = $opts->{errno} ? strerror($opts->{errno}) : undef;
186              
187 10 50 100     472 my $format
    100          
    100          
188             = $r && $e ? N__"{reason}: {message}; {error}"
189             : $r ? N__"{reason}: {message}"
190             : $e ? N__"{message}; {error}"
191             : undef;
192              
193 10 100       40 my $text
194             = ( defined $format
195             ? __x($format, message => $msg->toString , reason => $r, error => $e)
196             : $msg
197             )->toString;
198 10         89 $text =~ s/\n*\z/\n/;
199              
200 10 100       31 if($show_loc)
201 3 50 33     21 { if(my $loc = $opts->{location} || $self->collectLocation)
202 3         12 { my ($pkg, $fn, $line, $sub) = @$loc;
203             # pkg and sub are missing when decoded by ::Die
204 3         14 $text .= " "
205             . __x( 'at {filename} line {line}'
206             , filename => $fn, line => $line)->toString
207             . "\n";
208             }
209             }
210              
211 10 50       31 if($show_stack)
212 0   0     0 { my $stack = $opts->{stack} ||= $self->collectStack;
213 0         0 foreach (@$stack)
214 0         0 { $text .= $_->[0] . " "
215             . __x( 'at {filename} line {line}'
216             , filename => $_->[1], line => $_->[2] )->toString
217             . "\n";
218             }
219             }
220              
221 10 50 33     29 setlocale(&LC_ALL, $oldloc)
222             if $locale && $locale ne $oldloc;
223              
224 10         30 $self->{charset_enc}->($text);
225             }
226              
227              
228             sub collectStack($)
229 4     4 1 99 { my ($thing, $max) = @_;
230 4         16 my $nest = $thing->skipStack;
231              
232             # special trick by Perl for Carp::Heavy: adds @DB::args
233 4         9 { package DB; # non-blank before package to avoid problem with OODoc
234              
235 4         13 my @stack;
236 4   100     22 while(!defined $max || $max--)
237 22         136 { my ($pkg, $fn, $linenr, $sub) = caller $nest++;
238 22 100       55 defined $pkg or last;
239              
240 19         60 my $line = $thing->stackTraceLine(call => $sub, params => \@DB::args);
241 19         77 push @stack, [$line, $fn, $linenr];
242             }
243              
244 4         15 \@stack;
245             }
246             }
247              
248              
249             sub addSkipStack(@)
250 0     0 1 0 { my $thing = shift;
251 0         0 push @skip_stack, @_;
252 0         0 $thing;
253             }
254              
255              
256             sub skipStack()
257 17     17 1 28 { my $thing = shift;
258 17         25 my $nest = 1;
259 17         26 my $args;
260              
261 35         414 do { $args = [caller ++$nest] }
262 17   66 35   23 while @$args && first {$_->($args)} @skip_stack;
  35         85  
263              
264             # do not count my own stack level in!
265 17 50       163 @$args ? $nest-1 : 1;
266             }
267              
268              
269 13     13 1 44 sub collectLocation() { [caller shift->skipStack] }
270              
271              
272             sub stackTraceLine(@)
273 19     19 1 56 { my ($thing, %args) = @_;
274              
275 19   50     81 my $max = $args{max_line} ||= 500;
276 19   50     55 my $abstract = $args{abstract} || 1;
277 19   50     78 my $maxparams = $args{max_params} || 8;
278 19         28 my @params = @{$args{params}};
  19         40  
279 19         31 my $call = $args{call};
280              
281 19 50 33     56 my $obj = ref $params[0] && $call =~ m/^(.*\:\:)/ && UNIVERSAL::isa($params[0], $1)
282             ? shift @params : undef;
283              
284 19         27 my $listtail = '';
285 19 50       38 if(@params > $maxparams)
286 0         0 { $listtail = ', [' . (@params-$maxparams) . ' more]';
287 0         0 $#params = $maxparams -1;
288             }
289              
290 19         41 $max -= @params * 2 - length($listtail); # \( ( \,[ ] ){n-1} \)
291              
292 19         47 my $calling = $thing->stackTraceCall(\%args, $abstract, $call, $obj);
293 19         51 my @out = map $thing->stackTraceParam(\%args, $abstract, $_), @params;
294 19         34 my $total = sum map {length $_} $calling, @out;
  40         89  
295              
296             ATTEMPT:
297 19         45 while($total <= $max)
298 27         38 { $abstract++;
299 27 100       47 last if $abstract > 2; # later more levels
300              
301 19         43 foreach my $p (reverse 0..$#out)
302 11         21 { my $old = $out[$p];
303 11         40 $out[$p] = $thing->stackTraceParam(\%args, $abstract, $params[$p]);
304 11         23 $total -= length($old) - length($out[$p]);
305 11 50       33 last ATTEMPT if $total <= $max;
306             }
307              
308 8         15 my $old = $calling;
309 8         19 $calling = $thing->stackTraceCall(\%args, $abstract, $call, $obj);
310 8         25 $total -= length($old) - length($calling);
311             }
312              
313 19         92 $calling .'(' . join(', ',@out) . $listtail . ')';
314             }
315              
316             # 1: My::Object(0x123141, "my string")
317             # 2: My::Object=HASH(0x1231451)
318             # 3: My::Object("my string")
319             # 4: My::Object()
320             #
321              
322             sub stackTraceCall($$$;$)
323 27     27 0 57 { my ($thing, $args, $abstract, $call, $obj) = @_;
324              
325 27 50       47 if(defined $obj) # object oriented
326 0         0 { my ($pkg, $method) = $call =~ m/^(.*\:\:)(.*)/;
327 0         0 return overload::StrVal($obj) . '->' . $call;
328             }
329             else # imperative
330 27         55 { return $call;
331             }
332             }
333              
334             sub stackTraceParam($$$)
335 32     32 0 63 { my ($thing, $args, $abstract, $param) = @_;
336 32 50       69 defined $param
337             or return 'undef';
338              
339 32 50       49 $param = overload::StrVal($param)
340             if ref $param;
341              
342 32 100       129 return $param # int or float
343             if $param =~ /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/;
344              
345 24         61 my $escaped = escape_chars $param;
346 24 50       171 if(length $escaped > 80)
347 0         0 { $escaped = substr($escaped, 0, 30)
348             . '...['. (length($escaped) -80) .' chars more]...'
349             . substr($escaped, -30);
350             }
351              
352 24         74 qq{"$escaped"};
353             }
354              
355             #------------
356              
357             1;