File Coverage

blib/lib/Log/Any/Adapter/Daemontools/Config.pm
Criterion Covered Total %
statement 208 239 87.0
branch 105 152 69.0
condition 38 82 46.3
subroutine 36 41 87.8
pod 17 20 85.0
total 404 534 75.6


line stmt bran cond sub pod time code
1             package Log::Any::Adapter::Daemontools::Config;
2 24     24   732 use strict;
  24         23  
  24         1117  
3 24     24   69 use warnings;
  24         29  
  24         526  
4              
5             # ABSTRACT: Dynamic configuration settings used by Daemontools logging adapters
6              
7              
8 24     24   77 use Log::Any::Adapter::Util 'numeric_level', ':levels';
  24         28  
  24         5375  
9              
10             # At top of file where lexical scope is the cleanest
11             sub _build_writer_eval_in_clean_scope {
12             # Args: $self, $code, \$err
13 48     48   46 local $@;
14 48         82 my $output= $_[0]->output; # Needs to be in scope
15 48         79 my $format= $_[0]->format; # of the eval
16 48         5951 my $coderef= eval $_[1];
17 48 50       131 ${ $_[2] }= $@ if defined $_[0]; # Save error because $@ is localized
  48         88  
18 48         128 return $coderef;
19             }
20              
21 24     24   109 use Scalar::Util 'weaken', 'refaddr';
  24         27  
  24         5975  
22              
23             # Lazy-load carp, and also remove any Log::Any infrastructure from the trace
24             our @CARP_NOT= qw( Log::Any::Adapter::Base Log::Any::Adapter Log::Any::Proxy Log::Any );
25             sub _carp_exclude {
26 4     4   3 my $i= 1;
27 4         44 ++$i while caller($i) =~ /^Log::Any/;
28 4         8 return $i;
29             }
30 4     4 0 18 sub carp { require Carp; local $Carp::CarpLevel= _carp_exclude; &Carp::carp; }
  4         4  
  4         1093  
31 0     0 0 0 sub croak { require Carp; local $Carp::CarpLevel= _carp_exclude; &Carp::croak; }
  0         0  
  0         0  
32              
33              
34             my (%log_level_name, %log_level_num);
35             BEGIN {
36 24     24   191 %log_level_num= (
37             none => EMERGENCY-1,
38             emergency => EMERGENCY,
39             alert => ALERT,
40             critical => CRITICAL,
41             error => ERROR,
42             warning => WARNING,
43             notice => NOTICE,
44             info => INFO,
45             debug => DEBUG,
46             trace => TRACE,
47             all => TRACE
48             );
49 24         269 %log_level_name= reverse %log_level_num;
50            
51             # Add the aliases to the name-to-value mapping
52 24         93 my %aliases= Log::Any::Adapter::Util::log_level_aliases();
53 24         248 for (keys %aliases) {
54 120         19061 $log_level_num{$_}= $log_level_num{ $aliases{$_} };
55             }
56             }
57              
58             sub _parse_log_level {
59 40     40   59 my ($spec, $base, $min, $max)= @_;
60             my $lev= $spec =~ /^-?\d+$/? $spec # plain level
61             : $spec =~ /^([-+])= (-?\d+)$/? $base + "${1}1" * $2 # += notation
62 40 50       177 : $log_level_num{$spec}; # else a level name
    100          
63 40 50       88 defined $lev or croak "Invalid log level '$spec'";
64 40 50       60 $min= EMERGENCY-1 unless defined $min;
65 40 50       90 $lev= $min unless $lev >= $min;
66 40 50       66 $max= TRACE unless defined $max;
67 40 50       95 $lev= $max unless $lev <= $max;
68 40         83 return $lev;
69             }
70              
71             sub log_level {
72 43     43 1 2203 my $self= shift;
73 43 100       95 if (@_) {
74 40 50       74 croak "extra arguments" if @_ > 1;
75 40         90 my $l= _parse_log_level($_[0], $self->{log_level_num}, $self->{log_level_min_num}, $self->{log_level_max_num});
76              
77             # If log level changes, reset the cache
78 40 100       97 if ($l != $self->{log_level_num}) {
79 35         40 $self->{log_level_num}= $l;
80 35         60 $self->_reset_cached_adapters;
81             }
82             }
83 43         112 $log_level_name{ $self->{log_level_num} };
84             }
85              
86             sub log_level_min {
87 0     0 1 0 my $self= shift;
88 0 0       0 if (@_) {
89 0 0       0 croak "extra arguments" if @_ > 1;
90            
91             # If log level changes as a result, reset the cache
92 0         0 $self->{log_level_min_num}= _parse_log_level($_[0], $self->{log_level_min_num}, EMERGENCY-1, $self->{log_level_max_num});
93 0 0       0 if ($self->{log_level_min_num} > $self->{log_level_num}) {
94 0         0 $self->{log_level_num}= $self->{log_level_min_num};
95 0         0 $self->_reset_cached_adapters;
96             }
97             }
98 0         0 $log_level_name{ $self->{log_level_min_num} };
99             }
100              
101             sub log_level_max {
102 0     0 1 0 my $self= shift;
103 0 0       0 if (@_) {
104 0 0       0 croak "extra arguments" if @_ > 1;
105 0         0 $self->{log_level_max_num}= _parse_log_level($_[0], $self->{log_level_max_num}, $self->{log_level_min_num}, TRACE);
106            
107             # If log level changes as a result, reset the cache
108 0 0       0 if ($self->{log_level_max_num} < $self->{log_level_num}) {
109 0         0 $self->{log_level_num}= $self->{log_level_max_num};
110 0         0 $self->_reset_cached_adapters;
111             }
112             }
113 0         0 $log_level_name{ $self->{log_level_max_num} };
114             }
115              
116              
117             sub output {
118 121     121 1 3028 my $self= shift;
119 121 100       246 if (@_) {
120 3 50       6 croak "extra arguments" if @_ > 1;
121 3 50 66     26 defined $_[0] && (ref $_[0] eq 'GLOB' || ref $_[0] eq 'CODE' || ref($_[0])->can('print'))
      33        
122             or croak "Argument must be file handle or coderef";
123 3         4 $self->{output}= $_[0];
124 3         3 delete $self->{_writer_cache};
125 3 50       7 $self->_reset_cached_adapters unless $self->{writer};
126             }
127 121 100       267 return defined $self->{output}? $self->{output} : \*STDOUT;
128             }
129              
130              
131             sub format {
132 97     97 1 3917 my $self= shift;
133 97 100       172 if (@_) {
134 14 50 33     81 @_ == 1 && defined $_[0] && (!ref $_[0] or ref $_[0] eq 'CODE')
      66        
      33        
135             or croak "Expected string or coderef";
136 14 100       24 if (!ref $_[0]) {
137             # Test their supplied code right away, so the error happens in a
138             # place where its easy to fix
139 13         36 my $x= $self->_build_writer_eval_in_clean_scope(
140             "sub { "
141             .' my ($category, $level, $level_prefix, $file, $file_brief, $line, $package);'
142             ." $_[0]; "
143             ."}",
144             \my $err
145             );
146 13 50 33     117 defined $x && ref $x eq 'CODE'
147             or croak "Invalid format (make sure you wrote *code* that returns a string): $err";
148             }
149 14         21 $self->{format}= $_[0];
150 14         15 delete $self->{_writer_cache};
151 14 50       33 $self->_reset_cached_adapters unless $self->{writer};
152             }
153 97 100       186 defined $self->{format}? $self->{format} : '"$level_prefix$_\n"';
154             }
155              
156              
157             sub writer {
158 1     1 1 26 my $self= shift;
159 1 50       3 if (@_) {
160 1 50 33     7 @_ == 1 && (!defined $_[0] || ref $_[0] eq 'CODE')
      33        
161             or croak "Expected coderef or undef";
162 1         1 $self->{writer}= $_[0];
163 1         3 $self->_reset_cached_adapters;
164             }
165             }
166              
167              
168             # Yes, I should use Moo, but in the spirit of Log::Any having no non-core deps,
169             # I decided to do the same.
170             sub new {
171 26     26 1 34 my $class= shift;
172 26         80 my $self= bless {
173             log_level_num => INFO,
174             log_level_min_num => EMERGENCY-1,
175             log_level_max_num => TRACE,
176             }, $class;
177            
178             # Convert hashref to plain key/value list
179 26 50 33     93 unshift @_, %{ shift @_ }
  0         0  
180             if @_ == 1 && ref $_[0] eq 'HASH';
181            
182             # Iterate key/value pairs and call the accessor method for each
183 26         70 while (@_) {
184 0         0 my ($k, $v)= (shift, shift);
185 0         0 $self->$k($v);
186             }
187 26         54 $self;
188             }
189              
190              
191             our (%env_profile, %argv_profile);
192             BEGIN {
193 24     24   85 $env_profile{1}= { debug => 'DEBUG' };
194 24         41256 $argv_profile{1}= {
195             bundle => 1,
196             verbose => qr/^(--verbose|-v)$/,
197             quiet => qr/^(--quiet|-q)$/,
198             stop => '--'
199             };
200             }
201              
202             my %_init_args= map { $_ => 1 } qw(
203             level log_level min level_min log_level_min max level_max log_level_max
204             env argv signals handle_signals install_signal_handlers format out output writer
205             );
206             sub init {
207 17     17 1 21 my $self= shift;
208 17 50 33     125 my $cfg= (@_ == 1 and ref $_[0] eq 'HASH')? $_[0] : { @_ };
209             # Warn on unknown arguments
210 17         44 my @unknown= grep { !$_init_args{$_} } keys %$cfg;
  18         55  
211 17 100       51 carp "Invalid arguments: ".join(', ', @unknown) if @unknown;
212            
213             defined $cfg->{$_} and $self->log_level($cfg->{$_})
214 17   66     123 for qw: level log_level :;
215            
216             defined $cfg->{$_} and $self->log_level_min($cfg->{$_})
217 17   33     78 for qw: min level_min log_level_min :;
218            
219             defined $cfg->{$_} and $self->log_level_max($cfg->{$_})
220 17   33     76 for qw: max level_max log_level_max :;
221              
222             # Optional ENV processing
223             defined $cfg->{$_} and do {
224 0         0 my $v= $cfg->{$_};
225             $self->process_env( %{
226 0 0 0     0 ref $v eq 'HASH'? $v : $env_profile{$v} || croak "Unknown \"$_\" value $v"
  0         0  
227             } );
228 17   33     63 } for qw: env process_env :;
229            
230             # Optional ARGV parsing
231             defined $cfg->{$_} and do {
232 5         8 my $v= $cfg->{$_};
233             $self->process_argv( %{
234 5 100 33     4 ref $v eq 'HASH'? $v : $argv_profile{$v} || croak "Unknown \"$_\" value $v"
  5         41  
235             } );
236 17   66     57 } for qw: argv process_argv :;
237            
238             # Optional installation of signal handlers
239             defined $cfg->{$_} and do {
240 1   50     2 my $rt= ref($cfg->{$_}) || '';
241             $self->install_signal_handlers( %{
242 1         1 $rt eq 'HASH'? $cfg->{$_}
243 1 50       6 : $rt eq 'ARRAY'? { verbose => $cfg->{$_}[0], quiet => $cfg->{$_}[1] }
    50          
244             : croak "Unknown \"$_\" value $cfg->{$_}"
245             } );
246 17   66     93 } for qw: signals handle_signals install_signal_handlers :;
247            
248             $self->format($cfg->{format})
249 17 50       37 if defined $cfg->{format};
250            
251             defined $cfg->{$_} and $self->output($cfg->{$_})
252 17   33     62 for qw: out output :;
253            
254             $self->writer($cfg->{writer})
255 17 50       36 if defined $cfg->{writer};
256            
257 17         36 $self;
258             }
259              
260              
261             # We lied. This is the actual attribute in the implementation
262 67     67 1 207 sub log_level_num { shift->{log_level_num} }
263 0     0 1 0 sub log_level_min_num { shift->{log_level_min_num} }
264 0     0 1 0 sub log_level_max_num { shift->{log_level_max_num} }
265              
266              
267             sub log_level_adjust {
268 11     11 1 14 my ($self, $offset)= @_;
269 11 50 33     116 defined $offset && ($offset =~ /^-?\d+$/) && @_ == 2
      33        
270             or die "Expected offset integer";
271 11         24 $self->log_level( $self->log_level_num + $offset );
272             }
273              
274              
275             my %_process_env_args= ( debug => 1, log_level => 1 );
276             sub process_env {
277 11     11 1 3918 my ($self, %spec)= @_;
278             # Warn on unknown arguments
279 11         21 my @unknown= grep { !$_process_env_args{$_} } keys %spec;
  11         27  
280 11 50       24 carp "Invalid arguments: ".join(', ', @unknown) if @unknown;
281            
282 11 100 66     33 if (defined $spec{log_level} && defined $ENV{$spec{log_level}}) {
283 4         7 $self->log_level($ENV{$spec{log_level}});
284             }
285 11 100 66     43 if (defined $spec{debug} && defined $ENV{$spec{debug}}) {
286 7         13 $self->log_level( $self->debug_level_to_log_level($ENV{$spec{debug}}) );
287             }
288             }
289              
290             sub debug_level_to_log_level {
291 7     7 0 9 my ($class, $level)= @_;
292 7 100       26 $level+= 6 if $level =~ /^-?\d+$/;
293 7         14 $level;
294             }
295              
296              
297             my %_process_argv_args= ( bundle => 1, verbose => 1, quiet => 1, stop => 1, array => 1, remove => 1 );
298             sub process_argv {
299 11     11 1 25 my $self= shift;
300 11         24 my $ofs= $self->parse_log_level_opts(array => \@ARGV, @_);
301 11 100       32 $self->log_level_adjust($ofs)
302             if $ofs;
303 11         26 1;
304             }
305              
306              
307             sub _make_regex_list {
308 215 100   215   266 return () unless defined $_[0];
309 209 100       1168 return qr/^\Q$_[0]\E$/ unless ref $_[0];
310 66 100       102 return map { _make_regex_list($_) } @{ $_[0] } if ref $_[0] eq 'ARRAY';
  116         120  
  58         61  
311 8 50       17 return $_[0] if ref $_[0] eq 'Regexp';
312 0         0 croak "Not a regular expression, string, or array: $_[0]"
313             }
314             sub _combine_regex {
315 99     99   105 my @list= _make_regex_list(@_);
316 99 100       191 return @list == 0? qr/\0^/ # a regex that doesn't match anything
    100          
317             : @list == 1? $list[0]
318 58         570 : qr/@{[ join '|', @list ]}/;
319             }
320             sub parse_log_level_opts {
321 33     33 1 6199 my ($class, %spec)= @_;
322             # Warn on unknown arguments
323 33         64 my @unknown= grep { !$_process_argv_args{$_} } keys %spec;
  170         178  
324 33 50       73 carp "Invalid arguments: ".join(', ', @unknown) if @unknown;
325            
326 33 50       61 defined $spec{array} or croak "Parameter 'array' is required";
327 33         46 my $stop= _combine_regex( $spec{stop} );
328 33         51 my $verbose= _combine_regex( $spec{verbose} );
329 33         68 my $quiet= _combine_regex( $spec{quiet} );
330 33         48 my $level_ofs= 0;
331            
332 33         21 my $parse;
333             $parse= sub {
334 42     42   35 my $array= $_[0];
335 42         94 for (my $i= 0; $i < @$array; $i++) {
336 83 100       215 last if $array->[$i] =~ $stop;
337 81 100 66     414 if ($array->[$i] =~ /^-[^-=][^-=]+$/ and $spec{bundle}) {
    100          
    100          
338             # Un-bundle the arguments
339 9         31 my @un_bundled= map { "-$_" } split //, substr($array->[$i], 1);
  25         38  
340 9         12 my $len= @un_bundled;
341             # Then filter them as usual
342 9         35 $parse->(\@un_bundled);
343             # Then re-bundle them, if altered
344 9 100 66     41 if ($spec{remove} && $len != @un_bundled) {
345 4 100       7 if (@un_bundled) {
346 1         3 $array->[$i]= '-' . join('', map { substr($_,1) } @un_bundled);
  1         4  
347             } else {
348 3         8 splice( @$array, $i--, 1 );
349             }
350             }
351             }
352             elsif ($array->[$i] =~ $verbose) {
353 32         35 $level_ofs++;
354 32 100       94 splice( @$array, $i--, 1 ) if $spec{remove};
355             }
356             elsif ($array->[$i] =~ $quiet) {
357 21         17 $level_ofs--;
358 21 100       60 splice( @$array, $i--, 1 ) if $spec{remove};
359             }
360             }
361 33         114 };
362              
363 33         66 $parse->( $spec{array} );
364 33         92 return $level_ofs;
365             }
366              
367              
368             my %_handle_signal_args= ( verbose => 1, quiet => 1 );
369             sub install_signal_handlers {
370 1     1 1 2 my ($self, %spec)= @_;
371             # Warn on unknown arguments
372 1         2 my @unknown= grep { !$_handle_signal_args{$_} } keys %spec;
  2         3  
373 1 50       2 carp "Invalid arguments: ".join(', ', @unknown) if @unknown;
374            
375 1     1   3 $SIG{ $spec{verbose} }= sub { $self->log_level_adjust(1); }
376 1 50       19 if $spec{verbose};
377            
378 2     2   10 $SIG{ $spec{quiet} }= sub { $self->log_level_adjust(-1); }
379 1 50       10 if $spec{quiet};
380             }
381              
382              
383             sub compiled_writer {
384 39     39 1 74 my $self= shift;
385 39 100 66     220 $self->{writer} || ($self->{_writer_cache} ||= $self->_build_writer_cache)
386             }
387              
388             # This method combines the output and format settings into a writer.
389             # It also ensures the output is using autoflush
390             sub _build_writer_cache {
391 35     35   32 my $self= shift;
392 35         65 my $code= "sub { \n" . $self->_build_writer_code . "\n}";
393 35         37 my $err;
394 35 50       77 my $writer= $self->_build_writer_eval_in_clean_scope( $code, \$err )
395             or croak "Compilation of log writer failed: $err\nSource code is: $code";
396 35         82 $self->_enable_autoflush($self->output);
397 35         146 return $writer;
398             }
399              
400             # separate from _build_writer_cache so that test cases (and maybe subclasses)
401             # can inspect the generated code.
402             sub _build_writer_code {
403 35     35   25 my $self= shift;
404 35         65 my $format= $self->format;
405 35         48 my $code= ' my ($adapter, $level, $message)= @_;'."\n"
406             . ' $message =~ s/\n+$//;'."\n";
407            
408 35 100       97 if ($format =~ /\$\{?category(\W|$)/) {
409 1         3 $code .= ' my $category= $adapter->category;'."\n";
410             }
411 35 100       159 if ($format =~ /\$\{?(package|file|line|file_brief)(\W|$)/) {
412 6         12 $code .= ' my ($package,$file,$line);'."\n"
413             .' { my $i= 0; do { ($package, $file, $line)= caller(++$i) } while $package =~ /^Log::Any/; };'."\n";
414            
415 6 100       15 $code .= ' my $file_brief= $file;'."\n"
416             .' $file_brief =~ s|.*[\\/]lib[\\/]||;'."\n"
417             if $format =~ /\$\{?file_brief(\W|$)/;
418             }
419 35 100       117 if ($format =~ /\$\{?level_prefix(\W|$)/) {
420 22         42 $code .= ' my $level_prefix= ($level eq "info")? "" : "$level: ";'."\n";
421             }
422 35         70 my $output= $self->output;
423 35 100       103 if (ref $output eq 'GLOB') {
    100          
    50          
424 33         49 $code .= ' print $output (';
425             } elsif (ref($output)->can('print')) {
426 1         2 $code .= ' $output->print(';
427             } elsif (ref $output eq 'CODE') {
428 1         2 $code .= ' $output->(';
429             } else {
430 0         0 croak "Unhandled type of output: $output";
431             }
432            
433 35 100       71 if (ref $format eq 'CODE') {
434             # Closure over '$format', rather than deparsing the coderef
435 1         3 $code .= ' map {; $format->($adapter, $level) } split /\n/, $message)';
436             } else {
437 34         73 $code .= ' map {; '.$format.' } split /\n/, $message)';
438             }
439              
440 35         70 return $code;
441             }
442              
443             sub _enable_autoflush {
444 35     35   43 my ($self, $thing)= @_;
445             # This module tries to be very backward-compatible, and not force people to use IO::Handle
446             # if they didn't intend to...
447 35 100       100 if (ref $thing eq 'GLOB') {
    50          
448 33         252 my $prev= select($thing);
449 33         68 $|= 1;
450 33         63 select($prev);
451 33         44 1;
452             }
453             elsif (ref($thing)->can('autoflush')) {
454 0         0 $thing->autoflush(1);
455 0         0 1;
456             }
457             else {
458 2         3 0;
459             }
460             }
461              
462             # "Cached Adapters" is conceptually a field of the config object, but then
463             # it shows a giant mess if/when you Dump() the object, so I'm using this trick
464             # to keep the list attached to the package instead of the object.
465             # Object destructor cleans up the list.
466             our %_cached_adapters;
467              
468             # Holds a list of weak references to Adapter instances which have cached values from this config
469             sub _cached_adapters {
470             # Use refaddr in case someone subclasses and gets creative with re-blessing objects
471 92   100 92   553 $_cached_adapters{refaddr $_[0]} ||= [];
472             }
473             sub DESTROY {
474 2     2   1198 delete $_cached_adapters{refaddr $_[0]};
475             }
476              
477             # Called by an adapter after it caches things from this config to ask that it
478             # be notified about any changes.
479             sub _register_cached_adapter {
480 39     39   42 my ($self, $adapter)= @_;
481 39         58 my $cache= $self->_cached_adapters;
482 39         53 push @$cache, $adapter;
483 39         148 weaken( $cache->[-1] );
484             }
485              
486             # Inform all the Adapters who have cached our settings that the cache is invalid.
487             sub _reset_cached_adapters {
488 53     53   51 my $self= shift;
489 53         83 my $cache= $self->_cached_adapters;
490 53         99 $_->_uncache_config for grep { defined } @$cache;
  16         69  
491 53         97 @$cache= ();
492             }
493              
494             1;
495              
496             __END__