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