File Coverage

blib/lib/Log/ger/Output/Composite.pm
Criterion Covered Total %
statement 148 168 88.1
branch 38 60 63.3
condition 7 8 87.5
subroutine 8 9 88.8
pod 1 2 50.0
total 202 247 81.7


line stmt bran cond sub pod time code
1             package Log::ger::Output::Composite;
2              
3             our $DATE = '2020-03-04'; # DATE
4             our $VERSION = '0.012'; # VERSION
5              
6 1     1   1986 use strict;
  1         2  
  1         30  
7 1     1   6 use warnings;
  1         2  
  1         24  
8 1     1   5 use Log::ger::Util;
  1         2  
  1         640  
9              
10             # this can be used to override all level settings as it has the highest
11             # precedence.
12             our $Current_Level;
13              
14             sub _get_min_max_level {
15 360     360   717 my $level = shift;
16 360         532 my ($min, $max);
17 360 100       728 if (defined $level) {
18 270 50       665 if (defined $Current_Level) {
    100          
19 0         0 $min = 0;
20 0         0 $max = $Current_Level;
21             } elsif (ref $level eq 'ARRAY') {
22 24         56 $min = Log::ger::Util::numeric_level($level->[0]);
23 24         196 $max = Log::ger::Util::numeric_level($level->[1]);
24 24 100       168 ($min, $max) = ($max, $min) if $min > $max;
25             } else {
26 246         344 $min = 0;
27 246         539 $max = Log::ger::Util::numeric_level($level);
28             }
29             }
30 360         2182 ($min, $max);
31             }
32              
33             sub get_hooks {
34 7     7 0 19297 my %conf = @_;
35              
36             # check arguments
37 7         27 for my $k (keys %conf) {
38 9         21 my $conf = $conf{$k};
39 9 100       36 if ($k eq 'outputs') {
    50          
40 7         21 for my $o (keys %$conf) {
41 7 50       26 for my $oconf (ref $conf->{$o} eq 'ARRAY' ?
42 7         14 @{ $conf->{$o} } : $conf->{$o}) {
43 13         43 for my $k2 (keys %$oconf) {
44 20 50       111 unless ($k2 =~
45             /\A(conf|level|category_level|layout)\z/) {
46 0         0 die "Unknown configuration for output '$o': '$k2'";
47             }
48             }
49             }
50             }
51             } elsif ($k =~ /\A(category_level)\z/) {
52             } else {
53 0         0 die "Unknown configuration: '$k'";
54             }
55             }
56              
57 7         14 my @ospecs;
58             {
59 7         14 my $outputs = $conf{outputs};
  7         14  
60 7         25 for my $oname (sort keys %$outputs) {
61 7         13 my $ospec0 = $outputs->{$oname};
62 7         10 my @ospecs0;
63 7 50       22 if (ref $ospec0 eq 'ARRAY') {
64 7         14 @ospecs0 = map { +{ %{$_} } } @$ospec0;
  13         21  
  13         57  
65             } else {
66 0         0 @ospecs0 = (+{ %{ $ospec0 } });
  0         0  
67             }
68              
69 7 50       43 die "Invalid output name '$oname'"
70             unless $oname =~ /\A\w+(::\w+)*\z/;
71 7         24 my $mod = "Log::ger::Output::$oname";
72 7         36 (my $mod_pm = "$mod.pm") =~ s!::!/!g;
73 7         634 require $mod_pm;
74 7         611 for my $ospec (@ospecs0) {
75 13         26 $ospec->{_name} = $oname;
76 13         23 $ospec->{_mod} = $mod;
77 13         34 push @ospecs, $ospec;
78             }
79             }
80             }
81              
82             return {
83             create_logml_routine => [
84             __PACKAGE__, # key
85             50, # priority
86             sub { # hook
87 1     1   9 no strict 'refs';
  1         2  
  1         561  
88 78     78   71899 require Data::Dmp;
89              
90 78         2285 my %hook_args = @_; # see Log::ger::Manual::Internals/"Arguments passed to hook"
91              
92 78         245 my $target = $hook_args{target};
93 78         146 my $target_arg = $hook_args{target_arg};
94              
95 78         141 my $loggers = [];
96 78         134 my $logger_is_ml = [];
97 78         130 my $layouters = [];
98 78         152 for my $ospec (@ospecs) {
99 138         379 my $oname = $ospec->{_name};
100 138         282 my $mod = "Log::ger::Output::$oname";
101 138 50       198 my $hooks = &{"$mod\::get_hooks"}(%{ $ospec->{conf} || {} })
  138 50       541  
  138         420  
102             or die "Output module $mod does not return any hooks";
103             my @hook_args = (
104             target => $hook_args{target},
105             target_arg => $hook_args{target_arg},
106             init_args => $hook_args{init_args},
107 138         2164 );
108 138         217 my $res;
109             {
110 138 50       331 if ($hooks->{create_logml_routine}) {
  138         373  
111 0         0 $res = $hooks->{create_logml_routine}->[2]->(
112             @hook_args);
113 0 0       0 if ($res->[0]) {
114 0         0 push @$loggers, $res->[0];
115 0         0 push @$logger_is_ml, 1;
116 0         0 last;
117             }
118             }
119 138         310 push @hook_args, (level => 60, str_level => 'trace');
120 138 50       279 if ($hooks->{create_log_routine}) {
121 138         369 $res = $hooks->{create_log_routine}->[2]->(
122             @hook_args);
123 138 50       1754 if ($res->[0]) {
124 138         251 push @$loggers, $res->[0];
125 138         219 push @$logger_is_ml, 0;
126 138         224 last;
127             }
128             }
129 0         0 die "Output module $mod does not produce logger in ".
130             "its create_logml_routine nor create_log_routine ".
131             "hook";
132             }
133 138 100       299 if ($ospec->{layout}) {
134 6         14 my $lname = $ospec->{layout}[0];
135 6   50     14 my $lconf = $ospec->{layout}[1] || {};
136 6         13 my $lmod = "Log::ger::Layout::$lname";
137 6         35 (my $lmod_pm = "$lmod.pm") =~ s!::!/!g;
138 6         687 require $lmod_pm;
139 6 50       4481 my $lhooks = &{"$lmod\::get_hooks"}(%$lconf)
  6         28  
140             or die "Layout module $lmod does not return ".
141             "any hooks";
142             $lhooks->{create_layouter}
143 6 50       87 or die "Layout module $mod does not declare ".
144             "layouter";
145             my @lhook_args = (
146             target => $hook_args{target},
147             target_arg => $hook_args{target_arg},
148             init_args => $hook_args{init_args},
149 6         17 );
150 6 50       17 my $lres = $lhooks->{create_layouter}->[2]->(
151             @lhook_args) or die "Hook from layout module ".
152             "$lmod does not produce layout routine";
153 6 50       42 ref $lres->[0] eq 'CODE'
154             or die "Layouter from layout module $lmod ".
155             "is not a coderef";
156 6         34 push @$layouters, $lres->[0];
157             } else {
158 132         471 push @$layouters, undef;
159             }
160             }
161 78 50       208 unless (@$loggers) {
162 0         0 $Log::ger::_logger_is_null = 1;
163 0         0 return [sub {0}];
  0         0  
164             }
165              
166             # put the data that are mentioned in string-eval'ed code in a
167             # package so they are addressable
168 78         109 my $varname = do {
169 78         116 my $suffix;
170 78 50       168 if ($hook_args{target} eq 'package') {
171 78         132 $suffix = $hook_args{target_arg};
172             } else {
173 0         0 ($suffix) = "$hook_args{target_arg}" =~ /\(0x(\w+)/;
174             }
175 78         180 "Log::ger::Stash::OComposite_$suffix";
176             };
177             {
178 1     1   7 no strict 'refs';
  1         2  
  1         854  
  78         109  
179 78         127 ${$varname} = [];
  78         786  
180 78         151 ${$varname}->[0] = $loggers;
  78         221  
181 78         130 ${$varname}->[1] = $layouters;
  78         182  
182 78         127 ${$varname}->[2] = $hook_args{init_args};
  78         171  
183             }
184              
185             # generate our logger routine
186 78         256 my $logger;
187             {
188 78         106 my @src;
  78         112  
189 78         129 push @src, "sub {\n";
190 78         111 push @src, " my (\$ctx, \$lvl, \$msg) = \@_;\n";
191              
192 78         216 for my $i (0..$#ospecs) {
193 138         227 my $ospec = $ospecs[$i];
194 138         373 push @src, " # output #$i: $ospec->{_name}\n";
195 138         219 push @src, " {\n";
196              
197             # filter by output's category_level and category-level
198 138 100 100     469 if ($ospec->{category_level} || $conf{category_level}) {
199 42         76 push @src, " my \$cat = \$ctx->{category} || ".
200             "'';\n";
201              
202 42         70 my @cats;
203 42 100       91 if ($ospec->{category_level}) {
204 30         44 for my $cat (keys %{$ospec->{category_level}}) {
  30         90  
205 102         179 my $clevel = $ospec->{category_level}{$cat};
206 102         205 push @cats, [$cat, 1, $clevel];
207             }
208             }
209 42 100       101 if ($conf{category_level}) {
210 24         33 for my $cat (keys %{$conf{category_level}}) {
  24         73  
211 120         187 my $clevel = $conf{category_level}{$cat};
212 120         250 push @cats, [$cat, 2, $clevel];
213             }
214             }
215              
216 42         148 for my $cat (sort {
217 378 50 100     1075 length($b->[0]) <=> length($a->[0]) ||
218             $a->[0] cmp $b->[0] ||
219             $a->[1] <=> $b->[1]} @cats) {
220 222         516 push @src, " if (\$cat eq ".Data::Dmp::dmp($cat->[0])." || index(\$cat, ".Data::Dmp::dmp("$cat->[0]\::").") == 0) { ";
221 222         12320 my ($min_level, $max_level) =
222             _get_min_max_level($cat->[2]);
223 222         704 push @src, "if (\$lvl >= $min_level && ".
224             "\$lvl <= $max_level) { goto L } else { last }";
225 222         486 push @src, " }\n";
226             }
227 42         134 push @src, "\n";
228             }
229              
230             # filter by output level
231             my ($min_level, $max_level) = _get_min_max_level(
232 138         355 $ospec->{level});
233 138 100       352 if (defined $min_level) {
234 48         137 push @src, " if (\$lvl >= $min_level && ".
235             "\$lvl <= $max_level) { goto L } else { last }\n";
236             }
237              
238             # filter by general level
239 138         249 push @src, " if (\$Log::ger::Current_Level >= \$lvl) { goto L } else { last }\n";
240              
241             # run output's log routine
242 138 50       271 if ($logger_is_ml->[$i]) {
243 0         0 push @src, " L: if (\$$varname\->[1][$i]) { \$$varname\->[0][$i]->(\$ctx, \$lvl, \$$varname\->[1][$i]->(\$msg, \$$varname\->[2], \$lvl, Log::ger::Util::string_level(\$lvl))) } else { \$$varname\->[0][$i]->(\$ctx, \$lvl, \$msg) }\n";
244             } else {
245 138         687 push @src, " L: if (\$$varname\->[1][$i]) { \$$varname\->[0][$i]->(\$ctx, \$$varname\->[1][$i]->(\$msg, \$$varname\->[2], \$lvl, Log::ger::Util::string_level(\$lvl))) } else { \$$varname\->[0][$i]->(\$ctx, \$msg) }\n";
246             }
247 138         266 push @src, " }\n";
248 138         350 push @src, " # end output #$i\n\n";
249             } # for ospec
250              
251 78         144 push @src, "};\n";
252 78         390 my $src = join("", @src);
253 78 50       185 if ($ENV{LOG_LOG_GER_OUTPUT_COMPOSITE_CODE}) {
254 0         0 print STDERR "Log::ger::Output::Composite logger source code: <<$src>>\n";
255             }
256              
257 78         24545 $logger = eval $src;
258             }
259 78         445 [$logger];
260 7         144 }] # hook record
261             };
262             }
263              
264             sub set_level {
265 0     0 1   $Current_Level = Log::ger::Util::numeric_level(shift);
266 0           Log::ger::Util::reinit_all_targets();
267             }
268              
269             1;
270             # ABSTRACT: Composite output
271              
272             __END__