File Coverage

blib/lib/Log/ger/Heavy.pm
Criterion Covered Total %
statement 141 176 80.1
branch 60 104 57.6
condition 9 45 20.0
subroutine 20 47 42.5
pod 0 2 0.0
total 230 374 61.5


line stmt bran cond sub pod time code
1             ## no critic: TestingAndDebugging::RequireUseStrict
2             package Log::ger::Heavy;
3              
4             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
5             our $DATE = '2023-12-29'; # DATE
6             our $DIST = 'Log-ger'; # DIST
7             our $VERSION = '0.042'; # VERSION
8              
9             #IFUNBUILT
10             # use strict;
11             # use warnings;
12             #END IFUNBUILT
13              
14             package
15             Log::ger;
16              
17             #IFUNBUILT
18             # our (
19             # $re_addr,
20             # %Levels,
21             # %Level_Aliases,
22             # $Current_Level,
23             # $_outputter_is_null,
24             # $_dumper,
25             # %Global_Hooks,
26             # %Package_Targets,
27             # %Per_Package_Hooks,
28             # %Hash_Targets,
29             # %Per_Hash_Hooks,
30             # %Object_Targets,
31             # %Per_Object_Hooks,
32             # );
33             #END IFUNBUILT
34              
35             # key = phase, value = [ [key, prio, coderef], ... ]
36             our %Default_Hooks = (
37             create_filter => [],
38              
39             create_formatter => [
40             [__PACKAGE__, 90,
41             sub {
42             my %args = @_;
43              
44             # BEGIN_BLOCK: default_formatter
45              
46             my $formatter =
47              
48             # the default formatter is sprintf-style that dumps data
49             # structures arguments as well as undef as ''.
50             sub {
51             return $_[0] if @_ < 2;
52             my $fmt = shift;
53             my @args;
54             for (@_) {
55             if (!defined($_)) {
56             push @args, '';
57             } elsif (ref $_) {
58             require Log::ger::Util unless $Log::ger::_dumper;
59             push @args, Log::ger::Util::_dump($_);
60             } else {
61             push @args, $_;
62             }
63             }
64             # redefine is just a dummy category for perls < 5.22 which
65             # don't have 'redundant' yet
66 17 50   29   367926 no warnings ($warnings::Bits{'redundant'} ? 'redundant' : 'redefine');
  17         33  
  17         46243  
67             sprintf $fmt, @args;
68             };
69              
70             [$formatter];
71              
72             # END_BLOCK: default_formatter
73              
74             }],
75             ],
76              
77             create_layouter => [],
78              
79             create_routine_names => [
80             [__PACKAGE__, 90,
81             # the default names are log_LEVEL() and log_is_LEVEL() for subroutine
82             # names, or LEVEL() and is_LEVEL() for method names
83             sub {
84             my %args = @_;
85              
86             my $levels = [keys %Levels];
87              
88             return [{
89             logger_subs => [map { ["log_$_", $_] } @$levels],
90             level_checker_subs => [map { ["log_is_$_", $_] } @$levels],
91             # used when installing to hash or object
92             logger_methods => [map { ["$_", $_] } @$levels],
93             level_checker_methods => [map { ["is_$_", $_] } @$levels],
94             }, 1];
95             }],
96             ],
97              
98             # old name for create_outputter, deprecated and will be removed in the
99             # future
100             create_log_routine => [],
101              
102             create_outputter => [
103             [__PACKAGE__, 10,
104             # the default behavior is to create a null routine for levels that are
105             # too high than the global level ($Current_Level). since we run at high
106             # priority (10), we block typical output plugins at normal priority
107             # (50). this is a convenience so normally a plugin does not have to
108             # deal with level checking. plugins that want to do its own level
109             # checking can use a higher priority.
110             sub {
111             my %args = @_;
112             my $level = $args{level};
113             my $num_outputs = 0;
114             $num_outputs += @{ $Global_Hooks{create_log_routine} }; # old name, will be removed
115             $num_outputs += @{ $Global_Hooks{create_outputter} };
116             if ( # level indicates routine should be a null logger
117             (defined $level && $Current_Level < $level) ||
118             # there's only us that produces log routines (e.g. no outputs)
119             $num_outputs == 1
120             ) {
121             $_outputter_is_null = 1;
122 40     78   137 return [sub {0}];
  0     85      
        88      
123             }
124             [undef]; # decline, let output plugin supply logger routines
125             }],
126             ],
127              
128             # old name for create_level_checker, deprecated and will be removed in the
129             # future
130             create_is_routine => [],
131              
132             create_level_checker => [
133             [__PACKAGE__, 90,
134             # the default behavior is to compare to global level. normally this
135             # behavior suffices. we run at low priority (90) so normal plugins
136             # which typically use priority 50 can override us.
137             sub {
138             my %args = @_;
139             my $level = $args{level};
140 0     46     [sub { $Current_Level >= $level }];
        34      
        47      
        50      
        21      
        21      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
141             }],
142             ],
143              
144             before_install_routines => [],
145              
146             after_install_routines => [],
147             );
148              
149             for my $phase (keys %Default_Hooks) {
150             $Global_Hooks{$phase} = [@{ $Default_Hooks{$phase} }];
151             }
152              
153             # if flow_control is 1, stops after the first hook that gives non-undef result.
154             # flow_control can also be a coderef that will be called after each hook with
155             # ($hook, $hook_res) and can return 1 to mean stop.
156             sub run_hooks {
157 1760     1760 0 3249 my ($phase, $hook_args, $flow_control,
158             $target_type, $target_name) = @_;
159             #print "D: running hooks for phase $phase\n";
160              
161 1760 50       3808 $Global_Hooks{$phase} or die "Unknown phase '$phase'";
162 1760         2197 my @hooks = @{ $Global_Hooks{$phase} };
  1760         3281  
163              
164 1760 100       3703 if ($target_type eq 'package') {
    100          
    50          
165 944 100       1236 unshift @hooks, @{ $Per_Package_Hooks{$target_name}{$phase} || [] };
  944         2956  
166             } elsif ($target_type eq 'hash') {
167 216         1180 my ($addr) = "$target_name" =~ $re_addr;
168 216 50       337 unshift @hooks, @{ $Per_Hash_Hooks{$addr}{$phase} || [] };
  216         722  
169             } elsif ($target_type eq 'object') {
170 600         3562 my ($addr) = "$target_name" =~ $re_addr;
171 600 50       853 unshift @hooks, @{ $Per_Object_Hooks{$addr}{$phase} || [] };
  600         1871  
172             }
173              
174 1760         2560 my $res;
175 1760         9101 for my $hook (sort {$a->[1] <=> $b->[1]} @hooks) {
  395         1000  
176 1270         3867 my $hook_res = $hook->[2]->(%$hook_args);
177 1270 100       4367 if (defined $hook_res->[0]) {
178 1035         1425 $res = $hook_res->[0];
179             #print "D: got result from hook $hook->[0]: $res\n";
180 1035 100       1871 if (ref $flow_control eq 'CODE') {
181 157 100       310 last if $flow_control->($hook, $hook_res);
182             } else {
183 878 50       2024 last if $flow_control;
184             }
185             }
186 318 50       775 last if $hook_res->[1];
187             }
188 1760         22292 return $res;
189             }
190              
191             sub init_target {
192 74     74 0 228 my ($target_type, $target_name, $per_target_conf) = @_;
193              
194             #print "D:init_target($target_type, $target_name, ...)\n";
195 74         306 my %hook_args = (
196             target_type => $target_type,
197             target_name => $target_name,
198             per_target_conf => $per_target_conf,
199             );
200              
201             # collect only a single filter
202 74         116 my %filters;
203             run_hooks(
204             'create_filter', \%hook_args,
205             # collect filters, until a hook instructs to stop
206             sub {
207 2     14   3 my ($hook, $hook_res) = @_;
208 2         3 my ($filter, $flow_control, $fltname) = @$hook_res;
209 2 50       3 $fltname = 'default' if !defined($fltname);
210 2   33     9 $filters{$fltname} ||= $filter;
211 2         4 $flow_control;
212             },
213 74         516 $target_type, $target_name);
214              
215 74         333 my %formatters;
216             run_hooks(
217             'create_formatter', \%hook_args,
218             # collect formatters, until a hook instructs to stop
219             sub {
220 79     79   138 my ($hook, $hook_res) = @_;
221 79         181 my ($formatter, $flow_control, $fmtname) = @$hook_res;
222 79 100       212 $fmtname = 'default' if !defined($fmtname);
223 79   66     376 $formatters{$fmtname} ||= $formatter;
224 79         200 $flow_control;
225             },
226 74         326 $target_type, $target_name);
227              
228             # collect only a single layouter
229 74         308 my $layouter =
230             run_hooks(
231             'create_layouter', \%hook_args, 1, $target_type, $target_name);
232              
233 74         145 my $routine_names = {};
234             run_hooks(
235             'create_routine_names', \%hook_args,
236             # collect routine names, until a hook instructs to stop.
237             sub {
238 76     88   136 my ($hook, $hook_res) = @_;
239 76         144 my ($routine_name_rec, $flow_control) = @$hook_res;
240 76 50       152 $routine_name_rec or return;
241 76         262 for (keys %$routine_name_rec) {
242 300         354 push @{ $routine_names->{$_} }, @{ $routine_name_rec->{$_} };
  300         546  
  300         773  
243             }
244 76         284 $flow_control;
245             },
246 74         348 $target_type, $target_name);
247              
248 74         366 my @routines;
249 74         143 my $is_object = $target_type eq 'object';
250              
251             CREATE_LOGGER_ROUTINES:
252             {
253 74         117 my @routine_name_recs;
  74         106  
254 74 100       134 if ($target_type eq 'package') {
255 40 50       62 push @routine_name_recs, @{ $routine_names->{log_subs} || [] }; # old name, will be removed
  40         183  
256 40 50       66 push @routine_name_recs, @{ $routine_names->{logger_subs} || [] };
  40         138  
257             } else {
258 34 50       70 push @routine_name_recs, @{ $routine_names->{log_methods} || [] }; # old name, will be removed
  34         125  
259 34 50       53 push @routine_name_recs, @{ $routine_names->{logger_methods} || [] };
  34         93  
260             }
261             NAME:
262 74         172 for my $routine_name_rec (@routine_name_recs) {
263 440         971 my ($rname, $lname, $fmtname, $rper_target_conf, $fltname)
264             = @$routine_name_rec;
265 440 100       613 my $lnum; $lnum = $Levels{$lname} if defined $lname;
  440         1001  
266 440 100       851 $fmtname = 'default' if !defined($fmtname);
267              
268 440         634 my ($output_routine, $logger);
269 440         2757 $_outputter_is_null = 0;
270 440         902 local $hook_args{name} = $rname; # compat, deprecated
271 440         780 local $hook_args{routine_name} = $rname;
272 440         689 local $hook_args{level} = $lnum;
273 440         742 local $hook_args{str_level} = $lname;
274 440         545 my $outputter;
275             {
276 440 50       508 $outputter = run_hooks("create_outputter" , \%hook_args, 1, $target_type, $target_name) and last;
  440         826  
277 0         0 $outputter = run_hooks("create_log_routine", \%hook_args, 1, $target_type, $target_name); # old name, will be removed in the future
278             }
279 440 50       828 die "BUG in configuration: No outputter is produced for routine name $rname" unless $outputter;
280              
281             { # enclosing block
282 440 100       551 if ($_outputter_is_null) {
  440         2899  
283              
284             # if outputter is a null outputter (sub {0}) we don't need
285             # to format message, layout message, or care about the
286             # logger routine being a subroutine/object. shortcut here
287             # for faster init.
288              
289 211         293 $logger = $outputter;
290 211         271 last;
291             }
292              
293 229         408 my $formatter = $formatters{$fmtname};
294 229 100       407 my $filter = defined($fltname) ? $filters{$fltname} : undef;
295              
296             # zoom out to see vertical alignments... we have filter(x2) x
297             # formatter+layouter(x3) x OO/non-OO (x2) = 12 permutations. we
298             # create specialized subroutines for each case, for performance
299             # reason.
300 229 0 0 0   352 if ($filter) { if ($formatter) { if ($layouter) { if ($is_object) { $logger = sub { shift; return 0 unless my $per_msg_conf = $filter->(@_); $outputter->($rper_target_conf || $per_target_conf, $layouter->($formatter->(@_), $per_target_conf, $lnum, $lname, $per_msg_conf), $per_msg_conf) }; # has-filter has-formatter has-layouter with-oo
  2 0       3  
  2 50       5  
  0 50       0  
  0 100       0  
  0         0  
  0         0  
  0         0  
301 0 0 0 0   0 } else { $logger = sub { return 0 unless my $per_msg_conf = $filter->(@_); $outputter->($rper_target_conf || $per_target_conf, $layouter->($formatter->(@_), $per_target_conf, $lnum, $lname, $per_msg_conf), $per_msg_conf) }; } # has-filter has-formatter has-layouter not-oo
  0         0  
  0         0  
302 2 0 0 0   3 } else { if ($is_object) { $logger = sub { shift; return 0 unless my $per_msg_conf = $filter->(@_); $outputter->($rper_target_conf || $per_target_conf, $formatter->(@_), $per_msg_conf) }; # has-filter has-formatter no-layouter with-oo
  0 50       0  
  0         0  
  0         0  
  0         0  
303 2 100 33 2   5 } else { $logger = sub { return 0 unless my $per_msg_conf = $filter->(@_); $outputter->($rper_target_conf || $per_target_conf, $formatter->(@_), $per_msg_conf) }; } } # has-filter has-formatter no-layouter not-oo
  2         369400  
  1         8  
304 0 0 0 0   0 } else { if ($is_object) { $logger = sub { shift; return 0 unless my $per_msg_conf = $filter->(@_); $outputter->($rper_target_conf || $per_target_conf, \@_, $per_msg_conf) }; # has-filter no-formatter no-layouter with-oo
  0 0       0  
  0         0  
  0         0  
  0         0  
305 0 0 0 0   0 } else { $logger = sub { return 0 unless my $per_msg_conf = $filter->(@_); $outputter->($rper_target_conf || $per_target_conf, \@_, $per_msg_conf) }; } } # has-filter no-formatter no-layouter not-oo
  0         0  
  0         0  
306 227 0 0 0   394 } else { if ($formatter) { if ($layouter) { if ($is_object) { $logger = sub { shift; $outputter->($rper_target_conf || $per_target_conf, $layouter->($formatter->(@_), $per_target_conf, $lnum, $lname ) ) }; # no-filter has-formatter has-layouter with-oo
  227 50       389  
  0 50       0  
  0         0  
  0         0  
  0         0  
307 0   0 0   0 } else { $logger = sub { $outputter->($rper_target_conf || $per_target_conf, $layouter->($formatter->(@_), $per_target_conf, $lnum, $lname ) ) }; } # no-filter has-formatter has-layouter not-oo
  0         0  
308 227 100 33 21   363 } else { if ($is_object) { $logger = sub { shift; $outputter->($rper_target_conf || $per_target_conf, $formatter->(@_) ) }; # no-filter has-formatter no-layouter with-oo
  89     0   295  
  21     0   87  
  21         65  
309 138   66 49   486 } else { $logger = sub { $outputter->($rper_target_conf || $per_target_conf, $formatter->(@_) ) }; } } # no-filter has-formatter no-layouter not-oo
  28         8189  
310 0 0 0 36   0 } else { if ($is_object) { $logger = sub { shift; $outputter->($rper_target_conf || $per_target_conf, \@_ ) }; # no-filter no-formatter no-layouter with-oo
  0         0  
  0         0  
  0         0  
311 0   0 45   0 } else { $logger = sub { $outputter->($rper_target_conf || $per_target_conf, \@_ ) }; } } } # no-filter no-formatter no-layouter not-oo
  0         0  
312             } # enclosing block
313             L1:
314 440 100       933 my $rtype = $is_object ? 'logger_method' : 'logger_sub';
315 440   66     2498 push @routines, [$logger, $rname, $lnum, $rtype, $rper_target_conf||$per_target_conf];
316             }
317             }
318              
319             CREATE_LEVEL_CHECKER_ROUTINES:
320             {
321 74         145 my @routine_name_recs;
322             my $type;
323 74 100       151 if ($target_type eq 'package') {
324 40 50       69 push @routine_name_recs, @{ $routine_names->{is_subs} || [] }; # old name, will be removed
  40         222  
325 40 50       122 push @routine_name_recs, @{ $routine_names->{level_checker_subs} || [] };
  40         181  
326 40         92 $type = 'level_checker_sub';
327             } else {
328 34 50       50 push @routine_name_recs, @{ $routine_names->{is_methods} || [] }; # old name, will be removed
  34         137  
329 34 50       56 push @routine_name_recs, @{ $routine_names->{level_checker_methods} || [] };
  34         109  
330 34         54 $type = 'level_checker_method';
331             }
332 74         140 for my $routine_name_rec (@routine_name_recs) {
333 438         892 my ($rname, $lname) = @$routine_name_rec;
334 438         675 my $lnum = $Levels{$lname};
335              
336 438         797 local $hook_args{name} = $rname;
337 438         637 local $hook_args{level} = $lnum;
338 438         689 local $hook_args{str_level} = $lname;
339              
340 438         576 my $code_is;
341             {
342 438 50       546 $code_is = run_hooks('create_is_routine' , \%hook_args, 1, $target_type, $target_name) and last; # old name, will be removed
  438         792  
343 438         799 $code_is = run_hooks('create_level_checker', \%hook_args, 1, $target_type, $target_name);
344             }
345 438 50       819 die "BUG in configuration: No level_checker routine is produced for routine name $rname" unless $code_is;
346              
347 438         1637 push @routines, [$code_is, $rname, $lnum, $type, $per_target_conf];
348             }
349             }
350              
351             {
352 74         131 local $hook_args{routines} = \@routines;
  74         107  
  74         177  
353 74         174 local $hook_args{filters} = \%filters;
354 74         249 local $hook_args{formatters} = \%formatters;
355 74         136 local $hook_args{layouter} = $layouter;
356 74         162 run_hooks('before_install_routines', \%hook_args, 0,
357             $target_type, $target_name);
358             }
359              
360 74         335 install_routines($target_type, $target_name, \@routines, 1);
361              
362             {
363 74         125 local $hook_args{routines} = \@routines;
  74         178  
364 74         179 run_hooks('after_install_routines', \%hook_args, 0,
365             $target_type, $target_name);
366             }
367             }
368              
369             1;
370             # ABSTRACT: The bulk of the implementation of Log::ger
371              
372             __END__