File Coverage

blib/lib/Log/Any/Adapter/Daemontools/Config.pm
Criterion Covered Total %
statement 200 229 87.3
branch 102 148 68.9
condition 38 82 46.3
subroutine 35 40 87.5
pod 17 20 85.0
total 392 519 75.5


line stmt bran cond sub pod time code
1             package Log::Any::Adapter::Daemontools::Config;
2 24     24   116 use strict;
  24         40  
  24         581  
3 24     24   111 use warnings;
  24         40  
  24         699  
4              
5             # ABSTRACT: Dynamic configuration settings used by Daemontools logging adapters
6              
7              
8 24     24   117 use Log::Any::Adapter::Util 'numeric_level', ':levels';
  24         1140  
  24         8291  
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   104 local $@;
14 48         158 my $output= $_[0]->output; # Needs to be in scope
15 48         137 my $format= $_[0]->format; # of the eval
16 48         7868 my $coderef= eval $_[1];
17 48 50       193 ${ $_[2] }= $@ if defined $_[0]; # Save error because $@ is localized
  48         124  
18 48         205 return $coderef;
19             }
20              
21 24     24   152 use Scalar::Util 'weaken', 'refaddr';
  24         45  
  24         8078  
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   6 my $i= 1;
27 4         68 ++$i while caller($i) =~ /^Log::Any/;
28 4         9 return $i;
29             }
30 4     4 0 21 sub carp { require Carp; local $Carp::CarpLevel= _carp_exclude; &Carp::carp; }
  4         10  
  4         1876  
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   224 %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         379 %log_level_name= reverse %log_level_num;
50            
51             # Add the aliases to the name-to-value mapping
52 24         118 my %aliases= Log::Any::Adapter::Util::log_level_aliases();
53 24         281 for (keys %aliases) {
54 120         27853 $log_level_num{$_}= $log_level_num{ $aliases{$_} };
55             }
56             }
57              
58             sub _parse_log_level {
59 40     40   84 my ($spec, $base, $min, $max)= @_;
60             my $lev= $spec =~ /^-?\d+$/? $spec # plain level
61             : $spec =~ /^([-+])= (-?\d+)$/? $base + "${1}1" * $2 # += notation
62 40 50       240 : $log_level_num{$spec}; # else a level name
    100          
63 40 50       115 defined $lev or croak "Invalid log level '$spec'";
64 40 50       100 $min= EMERGENCY-1 unless defined $min;
65 40 50       129 $lev= $min unless $lev >= $min;
66 40 50       94 $max= TRACE unless defined $max;
67 40 50       100 $lev= $max unless $lev <= $max;
68 40         95 return $lev;
69             }
70              
71             sub log_level {
72 43     43 1 3198 my $self= shift;
73 43 100       138 if (@_) {
74 40 50       134 croak "extra arguments" if @_ > 1;
75 40         128 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       134 if ($l != $self->{log_level_num}) {
79 35         62 $self->{log_level_num}= $l;
80 35         90 $self->_reset_cached_adapters;
81             }
82             }
83 43         167 $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 86     86 1 5442 my $self= shift;
119 86 100       247 if (@_) {
120 3 50       8 croak "extra arguments" if @_ > 1;
121 3 50 66     38 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         8 $self->{output}= $_[0];
124 3         5 delete $self->{_writer_cache};
125 3 50       13 $self->_reset_cached_adapters unless $self->{writer};
126             }
127 86 100       278 return defined $self->{output}? $self->{output} : \*STDOUT;
128             }
129              
130              
131             sub format {
132 97     97 1 7216 my $self= shift;
133 97 100       249 if (@_) {
134 14 50 33     105 @_ == 1 && defined $_[0] && (!ref $_[0] or ref $_[0] eq 'CODE')
      66        
      33        
135             or croak "Expected string or coderef";
136 14 100       29 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         55 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     176 defined $x && ref $x eq 'CODE'
147             or croak "Invalid format (make sure you wrote *code* that returns a string): $err";
148             }
149 14         27 $self->{format}= $_[0];
150 14         21 delete $self->{_writer_cache};
151 14 50       49 $self->_reset_cached_adapters unless $self->{writer};
152             }
153 97 100       313 defined $self->{format}? $self->{format} : '"$level_prefix$_\n"';
154             }
155              
156              
157             sub writer {
158 1     1 1 41 my $self= shift;
159 1 50       4 if (@_) {
160 1 50 33     13 @_ == 1 && (!defined $_[0] || ref $_[0] eq 'CODE')
      33        
161             or croak "Expected coderef or undef";
162 1         2 $self->{writer}= $_[0];
163 1         4 $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 53 my $class= shift;
172 26         109 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     135 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         100 while (@_) {
184 0         0 my ($k, $v)= (shift, shift);
185 0         0 $self->$k($v);
186             }
187 26         83 $self;
188             }
189              
190              
191             our (%env_profile, %argv_profile);
192             BEGIN {
193 24     24   102 $env_profile{1}= { debug => 'DEBUG' };
194 24         60470 $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 36 my $self= shift;
208 17 50 33     161 my $cfg= (@_ == 1 and ref $_[0] eq 'HASH')? $_[0] : { @_ };
209             # Warn on unknown arguments
210 17         60 my @unknown= grep { !$_init_args{$_} } keys %$cfg;
  18         72  
211 17 100       71 carp "Invalid arguments: ".join(', ', @unknown) if @unknown;
212            
213             defined $cfg->{$_} and $self->log_level($cfg->{$_})
214 17   66     168 for qw: level log_level :;
215            
216             defined $cfg->{$_} and $self->log_level_min($cfg->{$_})
217 17   33     137 for qw: min level_min log_level_min :;
218            
219             defined $cfg->{$_} and $self->log_level_max($cfg->{$_})
220 17   33     115 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     96 } for qw: env process_env :;
229            
230             # Optional ARGV parsing
231             defined $cfg->{$_} and do {
232 5         9 my $v= $cfg->{$_};
233             $self->process_argv( %{
234 5 100 33     10 ref $v eq 'HASH'? $v : $argv_profile{$v} || croak "Unknown \"$_\" value $v"
  5         46  
235             } );
236 17   66     130 } for qw: argv process_argv :;
237            
238             # Optional installation of signal handlers
239             defined $cfg->{$_} and do {
240 1   50     5 my $rt= ref($cfg->{$_}) || '';
241             $self->install_signal_handlers( %{
242 1         2 $rt eq 'HASH'? $cfg->{$_}
243 1 50       15 : $rt eq 'ARRAY'? { verbose => $cfg->{$_}[0], quiet => $cfg->{$_}[1] }
    50          
244             : croak "Unknown \"$_\" value $cfg->{$_}"
245             } );
246 17   66     159 } for qw: signals handle_signals install_signal_handlers :;
247            
248             $self->format($cfg->{format})
249 17 50       61 if defined $cfg->{format};
250            
251             defined $cfg->{$_} and $self->output($cfg->{$_})
252 17   33     96 for qw: out output :;
253            
254             $self->writer($cfg->{writer})
255 17 50       64 if defined $cfg->{writer};
256            
257 17         71 $self;
258             }
259              
260              
261             # We lied. This is the actual attribute in the implementation
262 67     67 1 313 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 22 my ($self, $offset)= @_;
269 11 50 33     141 defined $offset && ($offset =~ /^-?\d+$/) && @_ == 2
      33        
270             or die "Expected offset integer";
271 11         34 $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 5661 my ($self, %spec)= @_;
278             # Warn on unknown arguments
279 11         24 my @unknown= grep { !$_process_env_args{$_} } keys %spec;
  11         35  
280 11 50       33 carp "Invalid arguments: ".join(', ', @unknown) if @unknown;
281            
282 11 100 66     45 if (defined $spec{log_level} && defined $ENV{$spec{log_level}}) {
283 4         12 $self->log_level($ENV{$spec{log_level}});
284             }
285 11 100 66     60 if (defined $spec{debug} && defined $ENV{$spec{debug}}) {
286 7         23 $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 10 my ($class, $level)= @_;
292 7 100       35 $level+= 6 if $level =~ /^-?\d+$/;
293 7         24 $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 37 my $self= shift;
300 11         34 my $ofs= $self->parse_log_level_opts(array => \@ARGV, @_);
301 11 100       54 $self->log_level_adjust($ofs)
302             if $ofs;
303 11         44 1;
304             }
305              
306              
307             sub _make_regex_list {
308 215 100   215   424 return () unless defined $_[0];
309 209 100       1772 return qr/^\Q$_[0]\E$/ unless ref $_[0];
310 66 100       186 return map { _make_regex_list($_) } @{ $_[0] } if ref $_[0] eq 'ARRAY';
  116         197  
  58         110  
311 8 50       29 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   193 my @list= _make_regex_list(@_);
316 99 100       315 return @list == 0? qr/\0^/ # a regex that doesn't match anything
    100          
317             : @list == 1? $list[0]
318 58         937 : qr/@{[ join '|', @list ]}/;
319             }
320             sub parse_log_level_opts {
321 33     33 1 9122 my ($class, %spec)= @_;
322             # Warn on unknown arguments
323 33         98 my @unknown= grep { !$_process_argv_args{$_} } keys %spec;
  170         335  
324 33 50       98 carp "Invalid arguments: ".join(', ', @unknown) if @unknown;
325            
326 33 50       95 defined $spec{array} or croak "Parameter 'array' is required";
327 33         80 my $stop= _combine_regex( $spec{stop} );
328 33         76 my $verbose= _combine_regex( $spec{verbose} );
329 33         105 my $quiet= _combine_regex( $spec{quiet} );
330 33         77 my $level_ofs= 0;
331            
332 33         42 my $parse;
333             $parse= sub {
334 42     42   63 my $array= $_[0];
335 42         132 for (my $i= 0; $i < @$array; $i++) {
336 83 100       312 last if $array->[$i] =~ $stop;
337 81 100 66     644 if ($array->[$i] =~ /^-[^-=][^-=]+$/ and $spec{bundle}) {
    100          
    100          
338             # Un-bundle the arguments
339 9         42 my @un_bundled= map { "-$_" } split //, substr($array->[$i], 1);
  25         60  
340 9         17 my $len= @un_bundled;
341             # Then filter them as usual
342 9         52 $parse->(\@un_bundled);
343             # Then re-bundle them, if altered
344 9 100 66     56 if ($spec{remove} && $len != @un_bundled) {
345 4 100       12 if (@un_bundled) {
346 1         3 $array->[$i]= '-' . join('', map { substr($_,1) } @un_bundled);
  1         7  
347             } else {
348 3         12 splice( @$array, $i--, 1 );
349             }
350             }
351             }
352             elsif ($array->[$i] =~ $verbose) {
353 32         41 $level_ofs++;
354 32 100       149 splice( @$array, $i--, 1 ) if $spec{remove};
355             }
356             elsif ($array->[$i] =~ $quiet) {
357 21         26 $level_ofs--;
358 21 100       98 splice( @$array, $i--, 1 ) if $spec{remove};
359             }
360             }
361 33         163 };
362              
363 33         81 $parse->( $spec{array} );
364 33         154 return $level_ofs;
365             }
366              
367              
368             my %_handle_signal_args= ( verbose => 1, quiet => 1 );
369             sub install_signal_handlers {
370 1     1 1 5 my ($self, %spec)= @_;
371             # Warn on unknown arguments
372 1         3 my @unknown= grep { !$_handle_signal_args{$_} } keys %spec;
  2         6  
373 1 50       4 carp "Invalid arguments: ".join(', ', @unknown) if @unknown;
374            
375 1     1   6 $SIG{ $spec{verbose} }= sub { $self->log_level_adjust(1); }
376 1 50       33 if $spec{verbose};
377            
378 2     2   17 $SIG{ $spec{quiet} }= sub { $self->log_level_adjust(-1); }
379 1 50       20 if $spec{quiet};
380             }
381              
382              
383             sub compiled_writer {
384 39     39 1 123 my $self= shift;
385 39 100 66     334 $self->{writer} || ($self->{_writer_cache} ||= $self->_build_writer_cache)
386             }
387              
388             # This method combines the output and format settings into a writer.
389             sub _build_writer_cache {
390 35     35   60 my $self= shift;
391 35         93 my $code= "sub { \n" . $self->_build_writer_code . "\n}";
392 35         70 my $err;
393 35 50       124 my $writer= $self->_build_writer_eval_in_clean_scope( $code, \$err )
394             or croak "Compilation of log writer failed: $err\nSource code is: $code";
395 35         256 return $writer;
396             }
397              
398             # separate from _build_writer_cache so that test cases (and maybe subclasses)
399             # can inspect the generated code.
400             sub _build_writer_code {
401 35     35   67 my $self= shift;
402 35         162 my $format= $self->format;
403 35         72 my $code= ' my ($adapter, $level, $message)= @_;'."\n"
404             . ' $message =~ s/\n+$//;'."\n";
405            
406 35 100       139 if ($format =~ /\$\{?category(\W|$)/) {
407 1         2 $code .= ' my $category= $adapter->category;'."\n";
408             }
409 35 100       204 if ($format =~ /\$\{?(package|file|line|file_brief)(\W|$)/) {
410 6         12 $code .= ' my ($package,$file,$line);'."\n"
411             .' { my $i= 0; do { ($package, $file, $line)= caller(++$i) } while $package =~ /^Log::Any/; };'."\n";
412            
413 6 100       24 $code .= ' my $file_brief= $file;'."\n"
414             .' $file_brief =~ s|.*[\\/]lib[\\/]||;'."\n"
415             if $format =~ /\$\{?file_brief(\W|$)/;
416             }
417 35 100       158 if ($format =~ /\$\{?level_prefix(\W|$)/) {
418 22         54 $code .= ' my $level_prefix= ($level eq "info")? "" : "$level: ";'."\n";
419             }
420 35         99 my $output= $self->output;
421 35 100       183 if (ref $output eq 'GLOB') {
    100          
    50          
422 33         79 $code .= ' print $output (';
423             } elsif (ref($output)->can('print')) {
424 1         3 $code .= ' $output->print(';
425             } elsif (ref $output eq 'CODE') {
426 1         3 $code .= ' $output->(';
427             } else {
428 0         0 croak "Unhandled type of output: $output";
429             }
430            
431 35 100       105 if (ref $format eq 'CODE') {
432             # Closure over '$format', rather than deparsing the coderef
433 1         3 $code .= ' map {; $format->($adapter, $level) } split /\n/, $message)';
434             } else {
435 34         111 $code .= ' map {; '.$format.' } split /\n/, $message)';
436             }
437              
438 35         106 return $code;
439             }
440              
441             # "Cached Adapters" is conceptually a field of the config object, but then
442             # it shows a giant mess if/when you Dump() the object, so I'm using this trick
443             # to keep the list attached to the package instead of the object.
444             # Object destructor cleans up the list.
445             our %_cached_adapters;
446              
447             # Holds a list of weak references to Adapter instances which have cached values from this config
448             sub _cached_adapters {
449             # Use refaddr in case someone subclasses and gets creative with re-blessing objects
450 92   100 92   706 $_cached_adapters{refaddr $_[0]} ||= [];
451             }
452             sub DESTROY {
453 2     2   1910 delete $_cached_adapters{refaddr $_[0]};
454             }
455              
456             # Called by an adapter after it caches things from this config to ask that it
457             # be notified about any changes.
458             sub _register_cached_adapter {
459 39     39   96 my ($self, $adapter)= @_;
460 39         90 my $cache= $self->_cached_adapters;
461 39         115 push @$cache, $adapter;
462 39         199 weaken( $cache->[-1] );
463             }
464              
465             # Inform all the Adapters who have cached our settings that the cache is invalid.
466             sub _reset_cached_adapters {
467 53     53   81 my $self= shift;
468 53         140 my $cache= $self->_cached_adapters;
469 53         145 $_->_uncache_config for grep { defined } @$cache;
  16         102  
470 53         141 @$cache= ();
471             }
472              
473             1;
474              
475             __END__