File Coverage

blib/lib/Log/Any/Adapter/Daemontools/Config.pm
Criterion Covered Total %
statement 202 233 86.7
branch 105 152 69.0
condition 40 82 48.7
subroutine 36 41 87.8
pod 17 20 85.0
total 400 528 75.7


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