File Coverage

blib/lib/Log/ger/Output/Composite.pm
Criterion Covered Total %
statement 155 174 89.0
branch 36 56 64.2
condition 12 18 66.6
subroutine 10 11 90.9
pod 1 3 33.3
total 214 262 81.6


line stmt bran cond sub pod time code
1             package Log::ger::Output::Composite;
2              
3 2     2   963165 use strict;
  2         4  
  2         82  
4 2     2   9 use warnings;
  2         4  
  2         134  
5 2     2   656 use Log::ger::Util;
  2         9270  
  2         2019  
6              
7             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
8             our $DATE = '2024-01-12'; # DATE
9             our $DIST = 'Log-ger-Output-Composite'; # DIST
10             our $VERSION = '0.018'; # VERSION
11              
12             # this can be used to override all level settings as it has the highest
13             # precedence.
14             our $Current_Level;
15              
16             sub meta { +{
17 7     7 0 28716 v => 2,
18             } }
19              
20             sub _debug {
21 14 50   14   48 return unless $ENV{LOG_GER_OUTPUT_COMPOSITE_DEBUG};
22 0         0 warn "[Log::ger::Output::Composite] debug: $_[0]\n";
23             }
24              
25             sub _get_min_max_level {
26 2160     2160   3800 my $level = shift;
27 2160         3112 my ($min, $max);
28 2160 100       4035 if (defined $level) {
29 1620 50       3954 if (defined $Current_Level) {
    100          
30 0         0 $min = 0;
31 0         0 $max = $Current_Level;
32             } elsif (ref $level eq 'ARRAY') {
33 144         466 $min = Log::ger::Util::numeric_level($level->[0]);
34 144         1482 $max = Log::ger::Util::numeric_level($level->[1]);
35 144 100       1267 ($min, $max) = ($max, $min) if $min > $max;
36             } else {
37 1476         1973 $min = 0;
38 1476         3002 $max = Log::ger::Util::numeric_level($level);
39             }
40             }
41 2160         12294 ($min, $max);
42             }
43              
44             sub get_hooks {
45 7     7 0 147 my %plugin_conf = @_;
46              
47             #_debug "In get_hooks()";
48              
49 7         16 my $empty_hashref = {};
50 7         16 my %outputter_get_hooks_cache; # key = "$output $conf", value = result from outputter's get_hooks()
51             my %layouter_get_hooks_cache ; # key = "$output $conf", value = result from layouter's get_hooks()
52              
53             # check arguments
54 7         24 for my $k (keys %plugin_conf) {
55 9         20 my $conf = $plugin_conf{$k};
56 9 100       41 if ($k eq 'outputs') {
    50          
57 7         19 for my $o (keys %$conf) {
58 7 50       27 for my $oconf (ref $conf->{$o} eq 'ARRAY' ?
59 7         21 @{ $conf->{$o} } : $conf->{$o}) {
60 13         32 for my $k2 (keys %$oconf) {
61 20 50       140 unless ($k2 =~
62             /\A(conf|level|category_level|layout)\z/) {
63 0         0 die "Unknown configuration for output '$o': '$k2'";
64             }
65             }
66             }
67             }
68             } elsif ($k =~ /\A(category_level)\z/) {
69             } else {
70 0         0 die "Unknown configuration: '$k'";
71             }
72             }
73              
74 7         19 my @ospecs;
75             {
76 7         13 my $outputs = $plugin_conf{outputs};
  7         16  
77 7         26 for my $oname (sort keys %$outputs) {
78 7         16 my $ospec0 = $outputs->{$oname};
79 7         13 my @ospecs0;
80 7 50       26 if (ref $ospec0 eq 'ARRAY') {
81 7         17 @ospecs0 = map { +{ %{$_} } } @$ospec0;
  13         41  
  13         54  
82             } else {
83 0         0 @ospecs0 = (+{ %{ $ospec0 } });
  0         0  
84             }
85              
86 7 50       52 die "Invalid output name '$oname'"
87             unless $oname =~ /\A\w+(::\w+)*\z/;
88 7         18 my $mod = "Log::ger::Output::$oname";
89 7         42 (my $mod_pm = "$mod.pm") =~ s!::!/!g;
90 7         695 require $mod_pm;
91 7         514 for my $ospec (@ospecs0) {
92 13         39 $ospec->{_name} = $oname;
93 13         27 $ospec->{_mod} = $mod;
94 13         70 push @ospecs, $ospec;
95             }
96             }
97             }
98              
99             return {
100             create_outputter => [
101             __PACKAGE__, # key
102             9, # priority.
103             # we use a high priority to override Log::ger's default hook (at
104             # priority 10) which create null outputter for levels lower than the
105             # general level, since we want to do our own custom level checking.
106             sub { # hook
107 2     2   20 no strict 'refs'; ## no critic: TestingAndDebugging::ProhibitNoStrict
  2         6  
  2         2117  
108 468     468   122684 require Data::Dmp;
109              
110 468         5269 my %hook_args = @_; # see Log::ger::Manual::Internals/"Arguments passed to hook"
111              
112 468         931 my $outputters = [];
113 468         759 my $layouters = [];
114 468         1017 for my $ospec (@ospecs) {
115 828         1755 my $oname = $ospec->{_name};
116 828         1676 my $mod = "Log::ger::Output::$oname";
117 828   33     2360 my $ospec_conf = $ospec->{conf} || $empty_hashref;
118 828         2537 my $cache_key = "$oname $ospec_conf";
119 828   66     2385 my $hooks = $outputter_get_hooks_cache{$cache_key} || do {
120             _debug("calling $oname\'s get_hooks() ...");
121             my $res = &{"$mod\::get_hooks"}(%$ospec_conf)
122             or die "Output module $mod does not return any hooks";
123             $outputter_get_hooks_cache{$cache_key} = $res;
124             $res;
125             };
126             my @hook_args = (
127             routine_name => $hook_args{routine_name},
128             target_type => $hook_args{target_type},
129             target_name => $hook_args{target_name},
130             per_target_conf => $hook_args{per_target_conf},
131 828         2936 );
132 828         1250 my $res;
133             {
134 828         1244 push @hook_args, (level => 60, str_level => 'trace');
  828         1888  
135 828 50       2009 if ($hooks->{create_log_routine}) { # old name, will be removed in the future
136 0         0 $res = $hooks->{create_log_routine}->[2]->(
137             @hook_args);
138 0 0       0 if ($res->[0]) {
139 0         0 push @$outputters, $res->[0];
140 0         0 last;
141             }
142             }
143 828 50       1941 if ($hooks->{create_outputter}) {
144 828         2408 $res = $hooks->{create_outputter}->[2]->(
145             @hook_args);
146 828 50       12032 if ($res->[0]) {
147 828         1645 push @$outputters, $res->[0];
148 828         1472 last;
149             }
150             }
151 0         0 die "Output module $mod does not produce outputter in ".
152             "its create_outputter (or create_log_routine) hook"; # old name create_log_routine will be removed in the future
153             }
154 828 100       1919 if ($ospec->{layout}) {
155 36         52 my $lname = $ospec->{layout}[0];
156 36   33     64 my $lconf = $ospec->{layout}[1] || $empty_hashref;
157 36         48 my $lmod = "Log::ger::Layout::$lname";
158 36         139 (my $lmod_pm = "$lmod.pm") =~ s!::!/!g;
159 36         606 require $lmod_pm;
160 36         2607 my $cache_key = "$lname $lconf";
161 36   66     78 my $lhooks = $layouter_get_hooks_cache{$cache_key} || do {
162             _debug("calling layouter $lname\'s get_hooks() ...");
163             my $res = &{"$lmod\::get_hooks"}(%$lconf)
164             or die "Layout module $lmod does not return any hooks";
165             $layouter_get_hooks_cache{$cache_key} = $res;
166             $res;
167             };
168             $lhooks->{create_layouter}
169 36 50       53 or die "Layout module $mod does not declare ".
170             "layouter";
171             my @lhook_args = (
172             target_type => $hook_args{target_type},
173             target_name => $hook_args{target_name},
174             per_target_conf => $hook_args{per_target_conf},
175 36         77 );
176 36 50       73 my $lres = $lhooks->{create_layouter}->[2]->(
177             @lhook_args) or die "Hook from layout module ".
178             "$lmod does not produce layout routine";
179 36 50       285 ref $lres->[0] eq 'CODE'
180             or die "Layouter from layout module $lmod ".
181             "is not a coderef";
182 36         134 push @$layouters, $lres->[0];
183             } else {
184 792         3259 push @$layouters, undef;
185             }
186             }
187 468 50       1148 unless (@$outputters) {
188 0         0 $Log::ger::_outputter_is_null = 1;
189 0         0 return [sub {0}];
  0         0  
190             }
191              
192             # put the data that are mentioned in string-eval'ed code in a
193             # package so they are addressable
194 468         640 my $varname = do {
195 468         702 my $suffix;
196 468 50       1159 if ($hook_args{target_type} eq 'package') {
197 468         808 $suffix = $hook_args{target_name};
198             } else {
199 0         0 ($suffix) = "$hook_args{target_name}" =~ /\(0x(\w+)/;
200             }
201 468         1168 "Log::ger::Stash::OComposite_$suffix";
202             };
203             {
204 2     2   18 no strict 'refs'; ## no critic: TestingAndDebugging::ProhibitNoStrict
  2         5  
  2         2571  
  468         668  
205 468         732 ${$varname} = [];
  468         5447  
206 468         940 ${$varname}->[0] = $outputters;
  468         1504  
207 468         716 ${$varname}->[1] = $layouters;
  468         1239  
208 468         846 ${$varname}->[2] = $hook_args{per_target_conf};
  468         1155  
209             }
210              
211             # generate our outputter routine
212 468         697 my $composite_outputter;
213             {
214 468         667 my @src;
  468         674  
215 468         839 push @src, "sub {\n";
216 468         714 push @src, " my (\$per_target_conf, \$fmsg, \$per_msg_conf) = \@_;\n";
217 468 50       1792 push @src, " my \$lvl; if (\$per_msg_conf) { \$lvl = \$per_msg_conf->{level} }", (defined $hook_args{level} ? " if (!defined \$lvl) { \$lvl = $hook_args{level} }" : ""), "\n";
218 468         792 push @src, " if (!\$per_msg_conf) { \$per_msg_conf = {level=>\$lvl} }\n"; # since we want to pass level etc to other outputs
219              
220 468         1314 for my $i (0..$#ospecs) {
221 828         1446 my $ospec = $ospecs[$i];
222 828         2231 push @src, " # output #$i: $ospec->{_name}\n";
223 828         1399 push @src, " {\n";
224              
225             # filter by output's category_level and category-level
226 828 100 100     3206 if ($ospec->{category_level} || $plugin_conf{category_level}) {
227 252         382 push @src, " my \$cat = (\$per_msg_conf ? \$per_msg_conf->{category} : undef) || \$per_target_conf->{category} || '';\n";
228 252         349 push @src, " local \$per_msg_conf->{category} = \$cat;\n";
229              
230 252         365 my @cats;
231 252 100       530 if ($ospec->{category_level}) {
232 180         220 for my $cat (keys %{$ospec->{category_level}}) {
  180         610  
233 612         828 my $clevel = $ospec->{category_level}{$cat};
234 612         1036 push @cats, [$cat, 1, $clevel];
235             }
236             }
237 252 100       605 if ($plugin_conf{category_level}) {
238 144         214 for my $cat (keys %{$plugin_conf{category_level}}) {
  144         492  
239 720         1159 my $clevel = $plugin_conf{category_level}{$cat};
240 720         1598 push @cats, [$cat, 2, $clevel];
241             }
242             }
243              
244 252         874 for my $cat (sort {
245 1980 50 100     5550 length($b->[0]) <=> length($a->[0]) ||
246             $a->[0] cmp $b->[0] ||
247             $a->[1] <=> $b->[1]} @cats) {
248 1332         2788 push @src, " if (\$cat eq ".Data::Dmp::dmp($cat->[0])." || index(\$cat, ".Data::Dmp::dmp("$cat->[0]\::").") == 0) { ";
249 1332         65388 my ($min_level, $max_level) =
250             _get_min_max_level($cat->[2]);
251 1332         3538 push @src, "if (\$lvl >= $min_level && ".
252             "\$lvl <= $max_level) { goto LOG } else { last }";
253 1332         2412 push @src, " }\n";
254             }
255 252         996 push @src, "\n";
256             }
257              
258             # filter by output level
259             my ($min_level, $max_level) = _get_min_max_level(
260 828         2565 $ospec->{level});
261 828 100       2201 if (defined $min_level) {
262 288         1258 push @src, " if (\$lvl >= $min_level && ".
263             "\$lvl <= $max_level) { goto LOG } else { last }\n";
264             }
265              
266             # filter by general level
267 828         1452 push @src, " if (\$Log::ger::Current_Level >= \$lvl) { goto LOG } else { last }\n";
268              
269             # run output's log routine
270 828         1445 push @src, " LOG:\n";
271 828         1840 push @src, " if (\$$varname\->[1][$i]) {\n";
272 828         2065 push @src, " \$$varname\->[0][$i]->(\$per_target_conf, \$$varname\->[1][$i]->(\$fmsg, \$$varname\->[2], \$lvl, Log::ger::Util::string_level(\$lvl)), \$per_msg_conf);\n";
273 828         1309 push @src, " } else {\n";
274 828         1611 push @src, " \$$varname\->[0][$i]->(\$per_target_conf, \$fmsg, \$per_msg_conf);\n";
275 828         1343 push @src, " }\n";
276 828         1383 push @src, " }\n";
277 828         2244 push @src, " # end output #$i\n\n";
278             } # for ospec
279              
280 468         944 push @src, "};\n";
281 468         2551 my $src = join("", @src);
282 468 50       1229 if ($ENV{LOG_LOG_GER_OUTPUT_COMPOSITE_CODE}) {
283 0         0 warn "Log::ger::Output::Composite logger source code (target type=$hook_args{target_type} target name=$hook_args{target_name}, routine name=$hook_args{routine_name}): <<$src>>\n";
284             }
285              
286 468         202653 $composite_outputter = eval $src; ## no critic: BuiltinFunctions::ProhibitStringyEval
287             }
288 468         3089 [$composite_outputter];
289 7         246 }] # hook record
290             };
291             }
292              
293             sub set_level {
294 0     0 1   $Current_Level = Log::ger::Util::numeric_level(shift);
295 0           Log::ger::Util::reinit_all_targets();
296             }
297              
298             1;
299             # ABSTRACT: Composite output
300              
301             __END__