File Coverage

blib/lib/Log/ger/Output/Composite.pm
Criterion Covered Total %
statement 152 170 89.4
branch 38 60 63.3
condition 7 8 87.5
subroutine 9 10 90.0
pod 1 3 33.3
total 207 251 82.4


line stmt bran cond sub pod time code
1             package Log::ger::Output::Composite;
2              
3             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
4             our $DATE = '2020-03-11'; # DATE
5             our $DIST = 'Log-ger-Output-Composite'; # DIST
6             our $VERSION = '0.016'; # VERSION
7              
8 1     1   1527 use strict;
  1         2  
  1         23  
9 1     1   4 use warnings;
  1         2  
  1         23  
10 1     1   4 use Log::ger::Util;
  1         2  
  1         513  
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 17188 v => 2,
18             } }
19              
20             sub _get_min_max_level {
21 2160     2160   3074 my $level = shift;
22 2160         2552 my ($min, $max);
23 2160 100       3387 if (defined $level) {
24 1620 50       2933 if (defined $Current_Level) {
    100          
25 0         0 $min = 0;
26 0         0 $max = $Current_Level;
27             } elsif (ref $level eq 'ARRAY') {
28 144         292 $min = Log::ger::Util::numeric_level($level->[0]);
29 144         879 $max = Log::ger::Util::numeric_level($level->[1]);
30 144 100       793 ($min, $max) = ($max, $min) if $min > $max;
31             } else {
32 1476         1694 $min = 0;
33 1476         2452 $max = Log::ger::Util::numeric_level($level);
34             }
35             }
36 2160         10142 ($min, $max);
37             }
38              
39             sub get_hooks {
40 7     7 0 100 my %plugin_conf = @_;
41              
42             # check arguments
43 7         21 for my $k (keys %plugin_conf) {
44 9         26 my $conf = $plugin_conf{$k};
45 9 100       42 if ($k eq 'outputs') {
    50          
46 7         35 for my $o (keys %$conf) {
47 7 50       25 for my $oconf (ref $conf->{$o} eq 'ARRAY' ?
48 7         17 @{ $conf->{$o} } : $conf->{$o}) {
49 13         28 for my $k2 (keys %$oconf) {
50 20 50       95 unless ($k2 =~
51             /\A(conf|level|category_level|layout)\z/) {
52 0         0 die "Unknown configuration for output '$o': '$k2'";
53             }
54             }
55             }
56             }
57             } elsif ($k =~ /\A(category_level)\z/) {
58             } else {
59 0         0 die "Unknown configuration: '$k'";
60             }
61             }
62              
63 7         12 my @ospecs;
64             {
65 7         8 my $outputs = $plugin_conf{outputs};
  7         13  
66 7         22 for my $oname (sort keys %$outputs) {
67 7         13 my $ospec0 = $outputs->{$oname};
68 7         11 my @ospecs0;
69 7 50       31 if (ref $ospec0 eq 'ARRAY') {
70 7         13 @ospecs0 = map { +{ %{$_} } } @$ospec0;
  13         18  
  13         42  
71             } else {
72 0         0 @ospecs0 = (+{ %{ $ospec0 } });
  0         0  
73             }
74              
75 7 50       41 die "Invalid output name '$oname'"
76             unless $oname =~ /\A\w+(::\w+)*\z/;
77 7         23 my $mod = "Log::ger::Output::$oname";
78 7         30 (my $mod_pm = "$mod.pm") =~ s!::!/!g;
79 7         459 require $mod_pm;
80 7         287 for my $ospec (@ospecs0) {
81 13         26 $ospec->{_name} = $oname;
82 13         21 $ospec->{_mod} = $mod;
83 13         28 push @ospecs, $ospec;
84             }
85             }
86             }
87              
88             return {
89             create_outputter => [
90             __PACKAGE__, # key
91             9, # priority.
92             # we use a high priority to override Log::ger's default hook (at
93             # priority 10) which create null outputter for levels lower than the
94             # general level, since we want to do our own custom level checking.
95             sub { # hook
96 1     1   7 no strict 'refs';
  1         1  
  1         500  
97 468     468   93211 require Data::Dmp;
98              
99 468         3034 my %hook_args = @_; # see Log::ger::Manual::Internals/"Arguments passed to hook"
100              
101 468         691 my $outputters = [];
102 468         677 my $layouters = [];
103 468         750 for my $ospec (@ospecs) {
104 828         1283 my $oname = $ospec->{_name};
105 828         1430 my $mod = "Log::ger::Output::$oname";
106 828 50       971 my $hooks = &{"$mod\::get_hooks"}(%{ $ospec->{conf} || {} })
  828 50       2610  
  828         1975  
107             or die "Output module $mod does not return any hooks";
108             my @hook_args = (
109             routine_name => $hook_args{routine_name},
110             target_type => $hook_args{target_type},
111             target_name => $hook_args{target_name},
112             per_target_conf => $hook_args{per_target_conf},
113 828         10631 );
114 828         1033 my $res;
115             {
116 828         949 push @hook_args, (level => 60, str_level => 'trace');
  828         1347  
117 828 50       1350 if ($hooks->{create_log_routine}) { # old name, will be removed in the future
118 0         0 $res = $hooks->{create_log_routine}->[2]->(
119             @hook_args);
120 0 0       0 if ($res->[0]) {
121 0         0 push @$outputters, $res->[0];
122 0         0 last;
123             }
124             }
125 828 50       1377 if ($hooks->{create_outputter}) {
126 828         1783 $res = $hooks->{create_outputter}->[2]->(
127             @hook_args);
128 828 50       8172 if ($res->[0]) {
129 828         1178 push @$outputters, $res->[0];
130 828         1069 last;
131             }
132             }
133 0         0 die "Output module $mod does not produce outputter in ".
134             "its create_outputter (or create_log_routine) hook"; # old name create_log_routine will be removed in the future
135             }
136 828 100       1333 if ($ospec->{layout}) {
137 36         53 my $lname = $ospec->{layout}[0];
138 36   50     67 my $lconf = $ospec->{layout}[1] || {};
139 36         63 my $lmod = "Log::ger::Layout::$lname";
140 36         152 (my $lmod_pm = "$lmod.pm") =~ s!::!/!g;
141 36         708 require $lmod_pm;
142 36 50       3831 my $lhooks = &{"$lmod\::get_hooks"}(%$lconf)
  36         124  
143             or die "Layout module $lmod does not return ".
144             "any hooks";
145             $lhooks->{create_layouter}
146 36 50       419 or die "Layout module $mod does not declare ".
147             "layouter";
148             my @lhook_args = (
149             target_type => $hook_args{target_type},
150             target_name => $hook_args{target_name},
151             per_target_conf => $hook_args{per_target_conf},
152 36         85 );
153 36 50       75 my $lres = $lhooks->{create_layouter}->[2]->(
154             @lhook_args) or die "Hook from layout module ".
155             "$lmod does not produce layout routine";
156 36 50       315 ref $lres->[0] eq 'CODE'
157             or die "Layouter from layout module $lmod ".
158             "is not a coderef";
159 36         150 push @$layouters, $lres->[0];
160             } else {
161 792         2434 push @$layouters, undef;
162             }
163             }
164 468 50       883 unless (@$outputters) {
165 0         0 $Log::ger::_outputter_is_null = 1;
166 0         0 return [sub {0}];
  0         0  
167             }
168              
169             # put the data that are mentioned in string-eval'ed code in a
170             # package so they are addressable
171 468         527 my $varname = do {
172 468         576 my $suffix;
173 468 50       900 if ($hook_args{target_type} eq 'package') {
174 468         672 $suffix = $hook_args{target_name};
175             } else {
176 0         0 ($suffix) = "$hook_args{target_name}" =~ /\(0x(\w+)/;
177             }
178 468         812 "Log::ger::Stash::OComposite_$suffix";
179             };
180             {
181 1     1   7 no strict 'refs';
  1         1  
  1         754  
  468         545  
182 468         651 ${$varname} = [];
  468         4188  
183 468         762 ${$varname}->[0] = $outputters;
  468         1116  
184 468         631 ${$varname}->[1] = $layouters;
  468         858  
185 468         596 ${$varname}->[2] = $hook_args{per_target_conf};
  468         853  
186             }
187              
188             # generate our outputter routine
189 468         596 my $composite_outputter;
190             {
191 468         497 my @src;
  468         586  
192 468         609 push @src, "sub {\n";
193 468         874 push @src, " my (\$per_target_conf, \$fmsg, \$per_msg_conf) = \@_;\n";
194 468 50       1392 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";
195 468         691 push @src, " if (!\$per_msg_conf) { \$per_msg_conf = {level=>\$lvl} }\n"; # since we want to pass level etc to other outputs
196              
197 468         1022 for my $i (0..$#ospecs) {
198 828         1124 my $ospec = $ospecs[$i];
199 828         1692 push @src, " # output #$i: $ospec->{_name}\n";
200 828         1087 push @src, " {\n";
201              
202             # filter by output's category_level and category-level
203 828 100 100     2151 if ($ospec->{category_level} || $plugin_conf{category_level}) {
204 252         343 push @src, " my \$cat = (\$per_msg_conf ? \$per_msg_conf->{category} : undef) || \$per_target_conf->{category} || '';\n";
205 252         329 push @src, " local \$per_msg_conf->{category} = \$cat;\n";
206              
207 252         293 my @cats;
208 252 100       486 if ($ospec->{category_level}) {
209 180         219 for my $cat (keys %{$ospec->{category_level}}) {
  180         472  
210 612         838 my $clevel = $ospec->{category_level}{$cat};
211 612         1092 push @cats, [$cat, 1, $clevel];
212             }
213             }
214 252 100       502 if ($plugin_conf{category_level}) {
215 144         168 for my $cat (keys %{$plugin_conf{category_level}}) {
  144         342  
216 720         934 my $clevel = $plugin_conf{category_level}{$cat};
217 720         1163 push @cats, [$cat, 2, $clevel];
218             }
219             }
220              
221 252         615 for my $cat (sort {
222 2268 50 100     5287 length($b->[0]) <=> length($a->[0]) ||
223             $a->[0] cmp $b->[0] ||
224             $a->[1] <=> $b->[1]} @cats) {
225 1332         2487 push @src, " if (\$cat eq ".Data::Dmp::dmp($cat->[0])." || index(\$cat, ".Data::Dmp::dmp("$cat->[0]\::").") == 0) { ";
226 1332         56579 my ($min_level, $max_level) =
227             _get_min_max_level($cat->[2]);
228 1332         3080 push @src, "if (\$lvl >= $min_level && ".
229             "\$lvl <= $max_level) { goto LOG } else { last }";
230 1332         2096 push @src, " }\n";
231             }
232 252         639 push @src, "\n";
233             }
234              
235             # filter by output level
236             my ($min_level, $max_level) = _get_min_max_level(
237 828         1722 $ospec->{level});
238 828 100       1679 if (defined $min_level) {
239 288         657 push @src, " if (\$lvl >= $min_level && ".
240             "\$lvl <= $max_level) { goto LOG } else { last }\n";
241             }
242              
243             # filter by general level
244 828         1130 push @src, " if (\$Log::ger::Current_Level >= \$lvl) { goto LOG } else { last }\n";
245              
246             # run output's log routine
247 828         971 push @src, " LOG:\n";
248 828         1546 push @src, " if (\$$varname\->[1][$i]) {\n";
249 828         2043 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";
250 828         1020 push @src, " } else {\n";
251 828         1383 push @src, " \$$varname\->[0][$i]->(\$per_target_conf, \$fmsg, \$per_msg_conf);\n";
252 828         1002 push @src, " }\n";
253 828         986 push @src, " }\n";
254 828         1561 push @src, " # end output #$i\n\n";
255             } # for ospec
256              
257 468         699 push @src, "};\n";
258 468         2149 my $src = join("", @src);
259 468 50       1009 if ($ENV{LOG_LOG_GER_OUTPUT_COMPOSITE_CODE}) {
260 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";
261             }
262              
263 468         125381 $composite_outputter = eval $src;
264             }
265 468         2188 [$composite_outputter];
266 7         134 }] # hook record
267             };
268             }
269              
270             sub set_level {
271 0     0 1   $Current_Level = Log::ger::Util::numeric_level(shift);
272 0           Log::ger::Util::reinit_all_targets();
273             }
274              
275             1;
276             # ABSTRACT: Composite output
277              
278             __END__