File Coverage

blib/lib/Log/Report/Dispatcher.pm
Criterion Covered Total %
statement 146 165 88.4
branch 45 74 60.8
condition 25 51 49.0
subroutine 30 36 83.3
pod 14 18 77.7
total 260 344 75.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 13     13   76 use warnings;
  13         24  
  13         345  
6 13     13   59 use strict;
  13         25  
  13         298  
7              
8             package Log::Report::Dispatcher;
9 13     13   54 use vars '$VERSION';
  13         23  
  13         467  
10             $VERSION = '1.21';
11              
12              
13 13     13   62 use Log::Report 'log-report';
  13         26  
  13         60  
14 13         1505 use Log::Report::Util qw/parse_locale expand_reasons %reason_code
15 13     13   69 escape_chars/;
  13         29  
16              
17 13     13   98 use POSIX qw/strerror/;
  13         28  
  13         102  
18 13     13   720 use List::Util qw/sum first/;
  13         41  
  13         678  
19 13     13   76 use Encode qw/find_encoding FB_DEFAULT/;
  13         25  
  13         559  
20 13     13   4894 use Devel::GlobalDestruction qw/in_global_destruction/;
  13         20867  
  13         77  
21              
22             eval { POSIX->import('locale_h') };
23             if($@)
24 13     13   1220 { no strict 'refs';
  13         26  
  13         5644  
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 33     33 1 136 { my ($class, $type, $name, %args) = @_;
41              
42             # $type is a class name or predefined name.
43             my $backend
44 33 0       120 = $predef_dispatchers{$type} ? $predef_dispatchers{$type}
    50          
45             : $type->isa('Log::Dispatch::Output') ? __PACKAGE__.'::LogDispatch'
46             : $type;
47              
48 33         1592 eval "require $backend";
49 33 50       214 $@ and alert "cannot use class $backend:\n$@";
50              
51 33         299 (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 33     33 0 78 { my ($self, $args) = @_;
66              
67 33   33     301 my $mode = $self->_set_mode(delete $args->{mode} || $default_mode);
68 33         87 $self->{locale} = delete $args->{locale};
69              
70 33   66     132 my $accept = delete $args->{accept} || $default_accept[$mode];
71 33         94 $self->{needs} = [ expand_reasons $accept ];
72              
73 33   100     1684 my $f = delete $args->{format_reason} || 'LOWERCASE';
74 33 50       159 $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 33         57 my $csenc;
79 33 50       94 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 13     13   91 $csenc = sub { no warnings 'utf8'; $enc->encode($_[0]) };
  13     0   27  
  13         20432  
  0         0  
  0         0  
83             }
84              
85 33   50 10   241 $self->{charset_enc} = $csenc || sub { $_[0] };
  10         38  
86 33         128 $self;
87             }
88              
89              
90             sub close()
91 33     33 1 220 { my $self = shift;
92 33 50       154 $self->{closed}++ and return undef;
93 33         67 $self->{disabled}++;
94 33         333 $self;
95             }
96              
97 15 50   15   2758 sub DESTROY { in_global_destruction or shift->close }
98              
99             #----------------------------
100              
101              
102 37     37 1 1312 sub name {shift->{name}}
103              
104              
105 0     0 1 0 sub type() {shift->{type}}
106              
107              
108 4     4 1 13 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 33     33   70 { my $self = shift;
117 33         259 my $mode = $self->{mode} = $modes{$_[0]};
118 33 50       100 defined $mode or panic "unknown run mode $_[0]";
119              
120 33         136 $self->{needs} = [ expand_reasons $default_accept[$mode] ];
121              
122 33 100       2331 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 33         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 1343 sub isDisabled() {shift->{disabled}}
138              
139              
140             sub needs(;$)
141 33     33 1 448 { my $self = shift;
142 33 50       98 return () if $self->{disabled};
143              
144 33         66 my $needs = $self->{needs};
145 33 100       161 @_ or return @$needs;
146              
147 6         14 my $need = shift;
148 6     32   31 first {$need eq $_} @$needs;
  32         74  
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 52 { my ($self, $opts, $reason, $msg) = @_;
160              
161 10         19 my $mode = $self->{mode};
162 10 50       30 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     86 || ($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     79 || ($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     30 : (textdomain $msg->domain)->nativeLanguage;
179              
180 10   50     108 my $oldloc = setlocale(&LC_ALL) // "";
181 10 50 33     44 setlocale(&LC_ALL, $locale)
182             if $locale && $locale ne $oldloc;
183              
184 10         29 my $r = $self->{format_reason}->((__$reason)->toString);
185 10 100       52 my $e = $opts->{errno} ? strerror($opts->{errno}) : undef;
186              
187 10 50 100     343 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       39 my $text
194             = ( defined $format
195             ? __x($format, message => $msg->toString , reason => $r, error => $e)
196             : $msg
197             )->toString;
198 10         79 $text =~ s/\n*\z/\n/;
199              
200 10 100       32 if($show_loc)
201 3 50 33     17 { if(my $loc = $opts->{location} || $self->collectLocation)
202 3         11 { my ($pkg, $fn, $line, $sub) = @$loc;
203             # pkg and sub are missing when decoded by ::Die
204 3         12 $text .= " "
205             . __x( 'at {filename} line {line}'
206             , filename => $fn, line => $line)->toString
207             . "\n";
208             }
209             }
210              
211 10 50       34 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     39 setlocale(&LC_ALL, $oldloc)
222             if $locale && $locale ne $oldloc;
223              
224 10         28 $self->{charset_enc}->($text);
225             }
226              
227              
228             sub collectStack($)
229 3     3 1 26 { my ($thing, $max) = @_;
230 3         9 my $nest = $thing->skipStack;
231              
232             # special trick by Perl for Carp::Heavy: adds @DB::args
233 3         8 { package DB; # non-blank before package to avoid problem with OODoc
234              
235 3         4 my @stack;
236 3   100     16 while(!defined $max || $max--)
237 14         67 { my ($pkg, $fn, $linenr, $sub) = caller $nest++;
238 14 100       36 defined $pkg or last;
239              
240 12         33 my $line = $thing->stackTraceLine(call => $sub, params => \@DB::args);
241 12         54 push @stack, [$line, $fn, $linenr];
242             }
243              
244 3         13 \@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 15     15 1 30 { my $thing = shift;
258 15         25 my $nest = 1;
259 15         24 my $args;
260              
261 27         298 do { $args = [caller ++$nest] }
262 15   66 27   25 while @$args && first {$_->($args)} @skip_stack;
  27         71  
263              
264             # do not count my own stack level in!
265 15 50       129 @$args ? $nest-1 : 1;
266             }
267              
268              
269 12     12 1 37 sub collectLocation() { [caller shift->skipStack] }
270              
271              
272             sub stackTraceLine(@)
273 12     12 1 34 { my ($thing, %args) = @_;
274              
275 12   50     51 my $max = $args{max_line} ||= 500;
276 12   50     41 my $abstract = $args{abstract} || 1;
277 12   50     42 my $maxparams = $args{max_params} || 8;
278 12         21 my @params = @{$args{params}};
  12         22  
279 12         22 my $call = $args{call};
280              
281 12 50 66     83 my $obj = ref $params[0] && $call =~ m/^(.*\:\:)/ && UNIVERSAL::isa($params[0], $1)
282             ? shift @params : undef;
283              
284 12         18 my $listtail = '';
285 12 50       34 if(@params > $maxparams)
286 0         0 { $listtail = ', [' . (@params-$maxparams) . ' more]';
287 0         0 $#params = $maxparams -1;
288             }
289              
290 12         25 $max -= @params * 2 - length($listtail); # \( ( \,[ ] ){n-1} \)
291              
292 12         32 my $calling = $thing->stackTraceCall(\%args, $abstract, $call, $obj);
293 12         31 my @out = map $thing->stackTraceParam(\%args, $abstract, $_), @params;
294 12         24 my $total = sum map {length $_} $calling, @out;
  27         73  
295              
296             ATTEMPT:
297 12         31 while($total <= $max)
298 16         26 { $abstract++;
299 16 100       35 last if $abstract > 2; # later more levels
300              
301 12         28 foreach my $p (reverse 0..$#out)
302 8         16 { my $old = $out[$p];
303 8         22 $out[$p] = $thing->stackTraceParam(\%args, $abstract, $params[$p]);
304 8         15 $total -= length($old) - length($out[$p]);
305 8 50       29 last ATTEMPT if $total <= $max;
306             }
307              
308 4         7 my $old = $calling;
309 4         22 $calling = $thing->stackTraceCall(\%args, $abstract, $call, $obj);
310 4         11 $total -= length($old) - length($calling);
311             }
312              
313 12         55 $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 16     16 0 33 { my ($thing, $args, $abstract, $call, $obj) = @_;
324              
325 16 50       29 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 16         33 { return $call;
331             }
332             }
333              
334             sub stackTraceParam($$$)
335 23     23 0 45 { my ($thing, $args, $abstract, $param) = @_;
336 23 50       48 defined $param
337             or return 'undef';
338              
339 23 100       63 $param = overload::StrVal($param)
340             if ref $param;
341              
342 23 100       132 return $param # int or float
343             if $param =~ /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/;
344              
345 15         48 my $escaped = escape_chars $param;
346 15 50       112 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 15         47 qq{"$escaped"};
353             }
354              
355             #------------
356              
357             1;