File Coverage

blib/lib/Log/Any/Adapter/TAP.pm
Criterion Covered Total %
statement 147 184 79.8
branch 44 76 57.8
condition 15 29 51.7
subroutine 27 37 72.9
pod 4 6 66.6
total 237 332 71.3


line stmt bran cond sub pod time code
1             package Log::Any::Adapter::TAP;
2 5     5   8064 use strict;
  5         9  
  5         141  
3 5     5   17 use warnings;
  5         6  
  5         107  
4 5     5   1943 use parent 'Log::Any::Adapter::Base';
  5         1346  
  5         27  
5 5     5   941 use Log::Any ();
  5         8  
  5         57  
6 5     5   16 use Try::Tiny;
  5         6  
  5         251  
7 5     5   19 use Carp 'croak';
  5         5  
  5         872  
8             require Scalar::Util;
9             require Data::Dumper;
10              
11             our $VERSION= '0.003001';
12              
13             # ABSTRACT: Logging adapter suitable for use in TAP testcases
14              
15              
16             our %level_map; # mapping from level name to numeric level
17             BEGIN {
18             # Initialize globals, and use %ENV vars for defaults
19 5     5   42 %level_map= (
20             min => -1,
21             trace => -1,
22             debug => 0,
23             info => 1,
24             notice => 2,
25             warning => 3,
26             error => 4,
27             critical => 5,
28             alert => 6,
29             emergency => 7,
30             max => 7,
31             );
32             # Make sure we have numeric levels for all the core logging methods
33 5         30 for ( Log::Any->logging_methods() ) {
34 45 50       101 if (!defined $level_map{$_}) {
35             # This is an attempt at being future-proof to the degree that a new level
36             # added to Log::Any won't kill a program using this logging adapter,
37             # but will emit a warning so it can be fixed properly.
38 0         0 warn __PACKAGE__." encountered unknown level '$_'";
39 0         0 $level_map{$_}= 4;
40             }
41             }
42             # Now add numeric values for all the aliases, too
43 5         22 my %aliases= Log::Any->log_level_aliases;
44             $level_map{$_} ||= $level_map{$aliases{$_}}
45 5   33     2125 for keys %aliases;
46             }
47              
48 36     36   100 sub _log_level_value { $level_map{$_[1]} }
49              
50             sub _coerce_filter_level {
51 19     19   21 my $val= shift;
52 19 100 66     307 return (!defined $val || $val eq 'none')? $level_map{trace}-1
    100 66        
    100          
    100          
53             : ($val eq 'all')? $level_map{emergency}
54             : exists $level_map{$val}? $level_map{$val}
55             : ($val =~ /^([A-Za-z]+)([-+][0-9]+)$/) && defined $level_map{lc $1}? $level_map{lc $1} + $2
56             : croak "unknown log level '$val'";
57             }
58              
59             our $global_filter_level; # default for level-filtering
60             our %category_filter_level; # per-category filter levels
61             our $show_category; # whether to show logging category on each message
62             our $show_file_line; # Whether to show caller for each message
63             our $show_file_fullname; # whether to use full path for caller info
64             our $show_usage; # whether to print usage notes on initialization
65             BEGIN {
66             # Suppress debug and trace by default
67 5     5   9 $global_filter_level= 'debug';
68            
69             # Apply TAP_LOG_FILTER settings
70 5 100       26 if ($ENV{TAP_LOG_FILTER}) {
71 2         6 for (split /,/, $ENV{TAP_LOG_FILTER}) {
72 4 100       9 if (index($_, '=') > -1) {
73 2         3 my ($pkg, $level)= split /=/, $_;
74 2         1 local $@;
75 2 50       6 eval { _coerce_filter_level($level); $category_filter_level{$pkg}= $level; 1; }
  2         3  
  2         3  
  2         4  
76             or warn "$@";
77             }
78             else {
79 2         2 local $@;
80 2 100       1 eval { _coerce_filter_level($_); $global_filter_level= $_; 1; }
  2         4  
  1         1  
  1         3  
81             or warn "$@";
82             }
83             }
84             }
85            
86             # Apply TAP_LOG_ORIGIN
87 5 50       21 if ($ENV{TAP_LOG_ORIGIN}) {
88 0         0 $show_category= $ENV{TAP_LOG_ORIGIN} & 1;
89 0         0 $show_file_line= $ENV{TAP_LOG_ORIGIN} & 2;
90 0         0 $show_file_fullname= $show_file_line;
91             }
92            
93             # Will show usage on first instance created, but suppress if ENV var
94             # is defined and false.
95 5 50 33     4573 $show_usage= 1 unless defined $ENV{TAP_LOG_SHOW_USAGE} && !$ENV{TAP_LOG_SHOW_USAGE};
96             }
97              
98              
99 15     15 1 42 sub filter { $_[0]{filter} }
100              
101              
102 0   0 0 1 0 sub dumper { $_[0]{dumper} ||= $_[0]->default_dumper }
103              
104 0     0 0 0 sub category { $_[0]{category} }
105              
106              
107             our $_show_dumper_warning= 1;
108             sub init {
109 15     15 0 14670 my $self= shift;
110 15         36 my $custom_dumper= $self->{dumper};
111             # Apply default dumper if not set
112 15   66     60 $self->{dumper} ||= $self->default_dumper;
113             # Apply default filter if not set
114 15 100       48 exists $self->{filter}
    100          
115             or $self->{filter}= defined $category_filter_level{$self->{category}}?
116             $category_filter_level{$self->{category}}
117             : $global_filter_level;
118            
119             # Rebless to a "level filter" package, which is a subclass of this one
120             # but with some methods replaced by empty subs.
121             # If log level is negative (trace), we show all messages, so no need to rebless.
122 15         35 my $level= _coerce_filter_level($self->filter);
123 15 100       39 $level= $level_map{emergency} if $level > $level_map{emergency};
124 15         19 my $pkg_id= $level+1;
125 15 100       65 bless $self, ref($self)."::Lev$pkg_id"
126             if $pkg_id >= 0;
127            
128             # As a courtesy to people running "prove -v", we show a quick usage for env
129             # vars that affect logging output. This can be suppressed by either
130             # filtering the 'info' level, or setting env var TAP_LOG_SHOW_USAGE=0
131 15 100       28 if ($show_usage) {
132 5         29 $self->info("Logging via ".ref($self)."; set TAP_LOG_FILTER=none to see"
133             ." all log levels, and TAP_LOG_ORIGIN=3 to see caller info.");
134 5         898 $show_usage= 0;
135             }
136 15 50 66     38 if ($custom_dumper && $_show_dumper_warning) {
137 1         6 $self->notice("Custom 'dumper' will not work with Log::Any versions >= 0.9");
138 1         52 $_show_dumper_warning= 0;
139             }
140            
141 15         25 return $self;
142             }
143              
144              
145             my %_tap_method;
146             sub write_msg {
147 37     37 1 45 my ($self, $level_name, $str)= @_;
148            
149 37         45 chomp $str;
150 37 100       93 $str= "$level_name: $str" unless $level_name eq 'info';
151            
152 37 50       64 if ($show_category) {
153 0         0 $str .= ' (' . $self->category . ')';
154             }
155            
156 37 50       57 if ($show_file_line) {
157 0         0 my $i= 0;
158 0         0 ++$i while caller($i) =~ /^Log::Any(:|$)/;
159 0         0 my (undef, $file, $line)= caller($i);
160 0 0       0 $file =~ s|.*/lib/||
161             unless $show_file_fullname;
162 0         0 $str .= ' (' . $file . ':' . $line . ')';
163             }
164              
165             # Was going to cache more of this, but logger might load before Test::More,
166             # so better to keep testing it each time. At least cache which method name we're using.
167 37 100 66     116 my $name= ($_tap_method{$level_name} ||=
168             ($self->_log_level_value($level_name) >= $self->_log_level_value('warning')?
169             'diag':'note'));
170 37         34 my $m;
171 37 50       178 if ($m= main->can($name)) {
    0          
172 37         85 $m->($str);
173             }
174             elsif (Test::Builder->can('new')) {
175 0         0 Test::Builder->new->$name($str);
176             }
177             else {
178 0         0 $str =~ s/\n/\n# /sg;
179 0 0       0 if ($name eq 'diag') {
180 0         0 print STDERR "# $str\n";
181             } else {
182 0         0 print STDOUT "# $str\n";
183             }
184             }
185             }
186              
187              
188             sub default_dumper {
189 14     14 1 43 return \&_default_dumper;
190             }
191              
192             sub _default_dumper {
193 0     0   0 my $val= shift;
194             try {
195 0     0   0 Data::Dumper->new([$val])->Indent(0)->Terse(1)->Useqq(1)->Quotekeys(0)->Maxdepth(4)->Sortkeys(1)->Dump;
196             } catch {
197 0     0   0 my $x= "$_";
198 0         0 $x =~ s/\n//;
199 0 0       0 substr($x, 50)= '...' if length $x >= 50;
200 0         0 "";
201 0         0 };
202             }
203              
204              
205             # Programmatically generate all the info, infof, is_info ... methods
206             sub _build_logging_methods {
207 5     5   20 my $class= shift;
208 5         6 my %seen;
209             # We implement the stock methods, but also 'fatal' because in my mind, fatal is not
210             # an alias for 'critical' and I want to see a prefix of "fatal" on messages.
211 5         23 for my $method ( grep { !$seen{$_}++ } Log::Any->logging_methods(), 'fatal' ) {
  50         87  
212 50         40 my ($impl, $printfn);
213 50 100       75 if ($level_map{$method} >= $level_map{info}) {
214             # Standard logging. Concatenate everything as a string.
215             $impl= sub {
216 33 50   33   19707 (shift)->write_msg($method, join('', map { !defined $_? '' : $_ } @_));
  33         156  
217 40         83 };
218             # Formatted logging. We dump data structures (because Log::Any says to)
219             $printfn= sub {
220 0     0   0 my $self= shift;
221 0 0       0 $self->write_msg($method, sprintf((shift), map { !defined $_? '' : !ref $_? $_ : $self->dumper->($_) } @_));
  0 0       0  
222 40         117 };
223             } else {
224             # Debug and trace logging. For these, we trap exceptions and dump data structures
225             $impl= sub {
226 4     4   2702 my $self= shift;
227 4         5 local $@;
228 4 50       5 eval { $self->write_msg($method, join('', map { !defined $_? '' : !ref $_? $_ : $self->dumper->($_) } @_)); 1 }
  4 50       7  
  4 50       23  
  4         156  
229             or $self->warn("$@");
230 10         28 };
231             $printfn= sub {
232 0     0   0 my $self= shift;
233 0         0 local $@;
234 0 0       0 eval { $self->write_msg($method, sprintf((shift), map { !defined $_? '' : !ref $_? $_ : $self->dumper->($_) } @_)); 1; }
  0 0       0  
  0 0       0  
  0         0  
235             or $self->warn("$@");
236 10         27 };
237             }
238            
239             # Install methods in base package
240 5     5   30 no strict 'refs';
  5         11  
  5         523  
241 50         48 *{"${class}::$method"}= $impl;
  50         131  
242 50         32 *{"${class}::${method}f"}= $printfn;
  50         242  
243 50     0   94 *{"${class}::is_$method"}= sub { 1 };
  50         148  
  0         0  
244             }
245             # Now create any alias that isn't handled
246 5         19 my %aliases= Log::Any->log_level_aliases;
247 5         45 for my $method (grep { !$seen{$_}++ } keys %aliases) {
  25         32  
248 5     5   20 no strict 'refs';
  5         5  
  5         748  
249 20         13 *{"${class}::$method"}= *{"${class}::$aliases{$method}"};
  20         57  
  20         32  
250 20         13 *{"${class}::${method}f"}= *{"${class}::$aliases{$method}f"};
  20         50  
  20         31  
251 20         16 *{"${class}::is_$method"}= *{"${class}::is_$aliases{$method}"};
  20         91  
  20         28  
252             }
253             }
254              
255             # Create per-filter-level packages
256             # This is an optimization for minimizing overhead when using disabled levels
257             sub _build_filtered_subclasses {
258 5     5   7 my $class= shift;
259 5         5 my $max_level= 0;
260             $_ > $max_level and $max_level= $_
261 5   100     75 for values %level_map;
262            
263             # Create packages, inheriting from $class
264 5         13 for (0..$max_level+1) {
265 5     5   20 no strict 'refs';
  5         6  
  5         388  
266 45         36 push @{"${class}::Lev${_}::ISA"}, $class;
  45         377  
267             }
268             # For each method, mask it in any package of a higher filtering level
269 5         17 for my $method (keys %level_map) {
270 80         83 my $level= $level_map{$method};
271             # Suppress methods in all higher filtering level packages
272 80         104 for ($level+1 .. $max_level+1) {
273 5     5   19 no strict 'refs';
  5         5  
  5         733  
274 385     28   649 *{"${class}::Lev${_}::$method"}= sub {};
  385         1148  
  28         21768  
275 385     0   717 *{"${class}::Lev${_}::${method}f"}= sub {};
  385         1049  
  0         0  
276 385     0   1353 *{"${class}::Lev${_}::is_$method"}= sub { 0 }
  0            
277 385         686 }
278             }
279             }
280              
281             our $_called_as_fatal;
282             BEGIN {
283 5     5   22 __PACKAGE__->_build_logging_methods;
284 5         12 __PACKAGE__->_build_filtered_subclasses;
285            
286 5 50 33     47 if ($Log::Any::VERSION >= 0.9 && $Log::Any::VERSION <= 1.032) {
287             # Log::Any broke the adapter contract a bit during these releases.
288             # This is an ugly hack to preserve the function of this module.
289 5         430 require Log::Any::Proxy;
290 5     5   24 no warnings 'redefine';
  5         5  
  5         584  
291 5         882 my $fatal= Log::Any::Proxy->can('fatal');
292 5     7   31 *Log::Any::Proxy::fatal= sub { local $_called_as_fatal= 1; $fatal->(@_) };
  7         4811  
  7         21  
293 5         7 my $crit= \&critical;
294 5 50   6   156 *critical= sub { $_called_as_fatal? fatal(@_) : $crit->(@_) };
  6         119  
295             }
296             }
297              
298             1;
299              
300             __END__