File Coverage

blib/lib/Log/Tree.pm
Criterion Covered Total %
statement 128 213 60.0
branch 45 106 42.4
condition 8 30 26.6
subroutine 22 30 73.3
pod 8 8 100.0
total 211 387 54.5


line stmt bran cond sub pod time code
1             package Log::Tree;
2             $Log::Tree::VERSION = '0.18';
3             our $AUTHORITY = 'cpan:TEX';
4             # ABSTRACT: lightweight but highly configurable logging class
5              
6 2     2   48242 use 5.010_000;
  2         6  
7 2     2   1305 use mro 'c3';
  2         3091  
  2         9  
8 2     2   81 use feature ':5.10';
  2         7  
  2         220  
9              
10 2     2   1374 use Moose;
  2         959143  
  2         14  
11 2     2   13522 use namespace::autoclean;
  2         15035  
  2         14  
12              
13             # use IO::Handle;
14             # use autodie;
15             # use MooseX::Params::Validate;
16              
17 2     2   1358 use English qw( -no_match_vars );
  2         5864  
  2         13  
18 2     2   2392 use Log::Dispatch;
  2         16304  
  2         88  
19 2     2   1336 use Log::Dispatch::Screen;
  2         36127  
  2         110  
20 2     2   1309 use Data::Tree '0.16';
  2         82898  
  2         139  
21 2     2   2010 use IO::Interactive::Tiny qw();
  2         19  
  2         6186  
22              
23             has 'dispatcher' => (
24             'is' => 'ro',
25             'isa' => 'Log::Dispatch',
26             'required' => 0,
27             'lazy' => 1,
28             'builder' => '_init_dispatcher',
29             );
30              
31             has 'filename' => (
32             'is' => 'ro',
33             'isa' => 'Str',
34             'lazy' => 1,
35             'builder' => '_init_filename',
36             );
37              
38             has 'facility' => (
39             'is' => 'ro',
40             'isa' => 'Str',
41             'required' => 1,
42             );
43              
44             has 'recipients' => (
45             'is' => 'rw',
46             'isa' => 'ArrayRef[Str]',
47             );
48              
49             has '_buffer' => (
50             'is' => 'rw',
51             'isa' => 'ArrayRef',
52             'default' => sub { [] },
53             );
54              
55             has 'prefix_caller' => (
56             'is' => 'rw',
57             'isa' => 'Bool',
58             'default' => 1,
59             );
60              
61             has 'prefix_ts' => (
62             'is' => 'rw',
63             'isa' => 'Bool',
64             'default' => 1,
65             );
66              
67             has 'prefix_level' => (
68             'is' => 'rw',
69             'isa' => 'Bool',
70             'default' => 1,
71             );
72              
73             has 'prefix' => (
74             'is' => 'rw',
75             'isa' => 'Str',
76             'default' => q{},
77             );
78              
79             has 'suffix' => (
80             'is' => 'rw',
81             'isa' => 'Str',
82             'default' => q{},
83             );
84              
85             has 'verbosity' => (
86             'is' => 'rw',
87             'isa' => 'Int',
88             'default' => 0,
89             'trigger' => \&_set_level,
90             );
91              
92             has 'loglevels' => (
93             'is' => 'rw',
94             'isa' => 'Data::Tree',
95             'lazy' => 1,
96             'builder' => '_init_loglevels',
97             );
98              
99             has 'severities' => (
100             'is' => 'ro',
101             'isa' => 'ArrayRef',
102             'lazy' => 1,
103             'builder' => '_init_severities',
104             );
105              
106             has 'syslog' => (
107             'is' => 'ro',
108             'isa' => 'Bool',
109             'default' => 0,
110             );
111              
112             has 'config' => (
113             'is' => 'rw',
114             'isa' => 'Config::Yak',
115             'required' => 0,
116             'trigger' => \&_set_config,
117             );
118              
119             sub _init_severities {
120 1     1   27 return [qw(debug info notice warning error critical alert emergency)];
121             }
122              
123             sub _init_loglevels {
124 1     1   2 my $self = shift;
125              
126 1         49 my $Tree = Data::Tree::->new();
127 1         26 $Tree->set( '__LEVEL__', 'debug' );
128              
129 1         114 $self->_update_loglevels();
130              
131 1         23 return $Tree;
132             }
133              
134             sub _update_loglevels {
135 1     1   4 my $self = shift;
136              
137 1 50       47 return unless $self->config();
138             # TODO read config and set apt levels
139             }
140              
141             sub _set_level {
142 0     0   0 my ( $self, $new_value, $old_value ) = @_;
143              
144 0 0       0 if ( $self->dispatcher()->output('Screen') ) {
145 0         0 $self->dispatcher()->output('Screen')->{'min_level'} = $self->_verbosity_to_level($new_value);
146             }
147              
148 0         0 return;
149             }
150              
151             sub _set_config {
152 0     0   0 my ( $self, $new_value, $old_value ) = @_;
153              
154 0         0 $self->_update_loglevels();
155              
156 0         0 return;
157             }
158              
159             sub _verbosity_to_level {
160 0     0   0 my $self = shift;
161 0         0 my $verbosity = shift;
162              
163 0         0 my $level = 7;
164 0         0 my $default_level = 4;
165              
166 0         0 $level = ( $default_level - $verbosity );
167              
168 0 0       0 if ( $level < 0 ) {
    0          
169 0         0 $level = 0;
170             }
171             elsif ( $level > 7 ) {
172 0         0 $level = 7;
173             }
174 0         0 return $level;
175             }
176              
177             sub severity_to_level {
178 16     16 1 14 my $self = shift;
179 16         17 my $sev = shift;
180              
181             # already numeric? so it's a level
182 16 50 33     88 if ( !$sev || $sev =~ m/^\d+$/ ) {
183 0         0 return $sev;
184             }
185              
186 16 100       81 if ( $sev =~ m/debug/i ) {
    100          
    100          
    100          
    100          
    100          
    100          
    50          
187 9         15 return 0;
188             }
189             elsif ( $sev =~ m/info/i ) {
190 1         5 return 1;
191             }
192             elsif ( $sev =~ m/notice/i ) {
193 1         4 return 2;
194             }
195             elsif ( $sev =~ m/warn(?:ing)?/i ) {
196 1         5 return 3;
197             }
198             elsif ( $sev =~ m/err(?:or)?/i ) {
199 1         4 return 4;
200             }
201             elsif ( $sev =~ m/crit(?:ical)?/i ) {
202 1         6 return 5;
203             }
204             elsif ( $sev =~ m/alert/i ) {
205 1         4 return 6;
206             }
207             elsif ( $sev =~ m/emerg(?:ency)/i ) {
208 1         5 return 7;
209             }
210             else {
211 0         0 return 0;
212             }
213             }
214              
215             sub level_to_severity {
216 0     0 1 0 my $self = shift;
217 0         0 my $level = shift;
218              
219             # doesn't look like a level ... so bail out
220 0 0 0     0 if ( !$level || $level !~ m/^\d+$/ ) {
221 0         0 return $level;
222             }
223              
224 0 0       0 if ( $level < 0 ) {
    0          
225 0         0 return 'debug';
226             }
227             elsif ( $level > 7 ) {
228 0         0 return 'emergency';
229             }
230             else {
231 0         0 return $self->severities()->[$level];
232             }
233             }
234              
235             sub get_buffer {
236 0     0 1 0 my $self = shift;
237 0   0     0 my $min_level = shift || 0;
238              
239             # make sure it's a numeric value
240 0         0 $min_level = $self->severity_to_level($min_level);
241              
242 0         0 my @lines = ();
243 0 0       0 if ( $min_level < 1 ) {
244 0         0 @lines = @{ $self->_buffer() };
  0         0  
245             }
246             else {
247              
248             # filter out only those whose severity is important enough
249 0         0 foreach my $line ( @{ $self->_buffer() } ) {
  0         0  
250 0 0       0 if ( $self->severity_to_level( $line->{'level'} ) >= $min_level ) {
251 0         0 push( @lines, $line );
252             }
253             }
254             }
255              
256 0         0 return \@lines;
257             }
258              
259             sub clear_buffer {
260 0     0 1 0 my $self = shift;
261 0         0 $self->_buffer( [] );
262              
263 0         0 return;
264             }
265              
266             # clean up after forking
267             sub forked {
268 0     0 1 0 my $self = shift;
269              
270 0         0 $self->clear_buffer();
271              
272 0         0 return 1;
273             }
274              
275             sub add_to_buffer {
276 8     8 1 11 my $self = shift;
277 8         7 my $obj = shift;
278              
279             # make sure the buffer doesn't get too big
280 8 50       8 if ( @{ $self->_buffer() } > 1_000_000 ) {
  8         236  
281 0         0 shift @{ $self->_buffer() };
  0         0  
282             }
283 8         9 push( @{ $self->_buffer() }, $obj );
  8         209  
284              
285 8         9 return 1;
286             }
287              
288             sub _init_filename {
289 0     0   0 my $self = shift;
290              
291 0         0 my $name = lc( $self->facility() );
292 0         0 $name =~ s/\W/-/g;
293 0         0 $name =~ s/_/-/g;
294 0 0       0 if ( $name !~ m/\.log$/ ) {
295 0         0 $name .= '.log';
296             }
297 0 0       0 if ( -w '/var/log/' ) {
298 0         0 return '/var/log/' . $name;
299             }
300             else {
301 0         0 return '/tmp/' . $name;
302             }
303             }
304              
305             sub _check_filename {
306 1     1   3 my $self = shift;
307 1         1 my $filename = shift;
308              
309 1 50       22 if ( -f $filename ) {
310 0 0       0 if ( -w $filename ) {
311 0         0 return $filename;
312             }
313             else {
314 0         0 return $self->_init_filename();
315             }
316             }
317             else {
318 1         5 my @path = split /\//, $filename;
319 1         1 pop @path;
320 1         3 my $basedir = join '/', @path;
321 1 50       11 if ( -w $basedir ) {
322 1         4 return $filename;
323             }
324             else {
325 0         0 return $self->_init_filename();
326             }
327             }
328             }
329              
330             sub _init_dispatcher {
331 1     1   2 my $self = shift;
332              
333 1         26 my $log = Log::Dispatch::->new();
334              
335             # only log to screen if running interactively
336 1 50 33     98 if(IO::Interactive::Tiny::is_interactive() || $ENV{'LOG_TREE_STDOUT'}) {
337 0         0 $log->add(
338             Log::Dispatch::Screen::->new(
339             name => 'screen',
340             min_level => $self->_verbosity_to_level( $self->verbosity() ),
341             )
342             );
343             }
344              
345 1 50 33     70 if ( $self->syslog() && $self->facility() ) {
346 0         0 require Log::Dispatch::Syslog;
347 0         0 $log->add(
348             Log::Dispatch::Syslog::->new(
349             name => 'syslog',
350             min_level => 'warning',
351             ident => $self->facility(),
352             )
353             );
354             }
355              
356 1 50       42 if ( $self->filename() ) {
357 1         746 require Log::Dispatch::File::Locked;
358 1         2626 $log->add(
359             Log::Dispatch::File::Locked::->new(
360             name => 'file',
361             min_level => 'debug',
362             'mode' => 'append',
363             'close_after_write' => 1,
364             filename => $self->filename(),
365             )
366             );
367             }
368              
369 1 50       250 if ( $self->recipients() ) {
370 0         0 require Log::Dispatch::Email::MailSender;
371             $log->add(
372             Log::Dispatch::Email::MailSender::->new(
373             name => 'email',
374             min_level => 'emerg',
375 0         0 to => join( ',', @{ $self->recipients() } ),
  0         0  
376             subject => $self->facility() . ' - EMERGENCY',
377             )
378             );
379             }
380 1         24 return $log;
381             }
382              
383             # DGR: speeeed
384             ## no critic (RequireArgUnpacking)
385             sub _real_caller {
386              
387             # $_[0] -> self
388             # $_[1] -> calldepth
389 8     8   8 my $max_depth = 255;
390 8         9 my $min_depth = 2;
391 8 50       19 $min_depth += $_[1] if $_[1];
392              
393             # 0 is this sub -> not relevant
394             # 1 is Logger::log -> not relevant
395             # we want to know who called Logger::log (unless its an eval or Try)
396 8         22 foreach my $i ( 1 .. $max_depth ) {
397 16         66 my @c = caller($i);
398 16 100       67 return caller( $i - 1 ) unless @c; # no caller information?
399 8 50       17 next unless $c[0];
400 8 50       18 next if $c[0] eq 'Try::Tiny'; # package Try::Tiny? Skip.
401 8 50       11 next unless $c[3];
402 8 50       31 next if $c[3] eq 'Log::Tree::log';
403 0 0       0 next if $c[3] eq 'Try::Tiny::try'; # calling sub Try::Tiny::try? Skip.
404 0 0       0 next if $c[3] eq '(eval)'; # calling sub some kind of eval? Skip.
405 0 0       0 next if $c[3] =~ m/__ANON__/; # calling sub some kind of anonymous sub? Skip.
406 0         0 return @c;
407             }
408 0         0 return ();
409             }
410             ## use critic
411              
412             # DGR: speeeed
413             ## no critic (RequireArgUnpacking)
414             sub _would_log {
415              
416             # $_[0] -> self
417             # $_[1] -> caller
418             # $_[2] -> level
419              
420 8     8   11 my @cp = ();
421 8 50       15 if ( $_[1] ) {
422 0         0 @cp = split /::/, $_[1];
423             }
424              
425 8         18 while (@cp) {
426 0         0 my $min_sev = $_[0]->loglevels()->get( [ @cp, '__LEVEL__' ] );
427 0 0       0 if ($min_sev) {
428 0         0 my $min_lvl = $_[0]->severity_to_level($min_sev);
429 0 0 0     0 if ( defined($min_lvl) && $_[0]->severity_to_level( $_[2] ) >= $min_lvl ) {
430 0         0 return 1;
431             }
432             }
433 0         0 pop @cp;
434             }
435 8         266 my $min_sev = $_[0]->loglevels()->get('__LEVEL__');
436 8 50       413 if ($min_sev) {
437 8         22 my $min_lvl = $_[0]->severity_to_level($min_sev);
438 8 50 33     26 if ( defined($min_lvl) && $_[0]->severity_to_level( $_[2] ) >= $min_lvl ) {
439 8         18 return 1;
440             }
441             }
442 0         0 return;
443             }
444             ## use critic
445              
446             ## no critic (ProhibitBuiltinHomonyms RequireArgUnpacking)
447             sub log {
448             ## use critic
449 8     8 1 7847 my $self = shift;
450              
451 8         16 my %params = ();
452 8         22 my ( $package, $filename, $line, $subroutine, $hasargs, $wantarray, $evaltext, $is_require, $hints, $bitmask, $hinthash ) = $self->_real_caller();
453 8 50 33     45 if ( $package eq 'main' && $subroutine eq 'Log::Tree::log' ) {
454 8         11 $subroutine = q{};
455             }
456              
457 8 50       22 if ( scalar(@_) % 2 == 0 ) {
458 8         25 %params = @_;
459             }
460             else {
461 0         0 $params{'message'} = 'Incorrect usage of log in ' . $subroutine . '. Args: ' . join( q{ }, @_ );
462 0         0 $params{'level'} = 'error';
463             }
464              
465 8         20 $params{'ts'} = time();
466 8   50     18 $params{'level'} ||= 'debug';
467              
468             # skip messages we don't want to log
469 8 50       18 return unless $self->_would_log( $subroutine, $params{'level'} );
470 8   50     29 $subroutine ||= 'n/a';
471 8 50       24 $params{'caller'} = $subroutine unless $params{'caller'};
472              
473             # resolve any code ref
474 8 50 33     39 if ( $params{'message'} && ref( $params{'message'} ) eq 'CODE' ) {
475 0         0 $params{'message'} = &{ $params{'message'} }();
  0         0  
476             }
477              
478 8         19 $self->add_to_buffer( \%params );
479              
480             # IMPORTANT: Since we add a hash_REF to the buffer, everything we do to the hash itself affects the buffer, too
481             # So if we want to modify the hash given to the dispatcher, but not the one in the buffer we have to create a copy.
482             # Otherwise the buffer is cluttered with information we don't want.
483 8         36 my %params_disp = %params;
484              
485             # we use tabs to separated the fields, so remove any tabs already present
486 8         16 $params_disp{'message'} =~ s/\t/ /g;
487              
488             # prepend log level
489 8 50       202 if ( $self->prefix_level() ) {
490 8         22 $params_disp{'message'} = uc( $params_disp{'level'} ) . "\t" . $params_disp{'message'};
491             }
492              
493             # prepend log message w/ the caller
494 8 50       187 if ( $self->prefix_caller() ) {
495 8         22 $params_disp{'message'} = $params_disp{'caller'} . "\t" . $params_disp{'message'};
496             }
497              
498             # prepend a user-supplied prefix, e.g. [CHILD 24324/234342]
499 8 50       173 if ( $self->prefix() ) {
500 0         0 $params_disp{'message'} = $self->prefix() . "\t" . $params_disp{'message'};
501             }
502              
503             # prepend log message w/ a timestamp
504 8 50       172 if ( $self->prefix_ts() ) {
505 8         264 my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) = localtime( $params{'ts'} );
506 8         15 $year += 1900;
507 8         8 $mon++;
508 8         48 $params_disp{'message'} = sprintf( '%04i.%02i.%02i-%02i:%02i:%02i', $year, $mon, $mday, $hour, $min, $sec ) . "\t" . $params_disp{'message'};
509             }
510            
511             # append a user-supplied suffix
512 8 50       217 if ( $self->suffix() ) {
513 0         0 $params_disp{'message'} = $params_disp{'message'} . "\t" . $self->suffix();
514             }
515              
516 8         13 $params_disp{'message'} .= "\n";
517              
518 8         187 return $self->dispatcher()->log(%params_disp);
519             }
520              
521             around BUILDARGS => sub {
522             my $orig = shift;
523             my $class = shift;
524              
525             if ( @_ == 1 && !ref $_[0] ) {
526             return $class->$orig( facility => $_[0] );
527             }
528             else {
529             return $class->$orig(@_);
530             }
531             };
532              
533             sub BUILD {
534 1     1 1 2 my $self = shift;
535 1         2 my $args = shift;
536              
537 1 50       5 if ( $args->{'filename'} ) {
538 1         5 $self->{'filename'} = $self->_check_filename( $args->{'filename'} );
539             }
540             else {
541 0         0 $self->{'filename'} = $self->_init_filename();
542             }
543              
544 1         30 return 1;
545             }
546              
547 2     2   19 no Moose;
  2         3  
  2         23  
548             __PACKAGE__->meta->make_immutable;
549              
550             1;
551              
552             __END__
553              
554             =pod
555              
556             =encoding UTF-8
557              
558             =head1 NAME
559              
560             Log::Tree - lightweight but highly configurable logging class
561              
562             =head1 SYNOPSIS
563              
564             use Log::Tree;
565              
566             my $logger = Log::Tree::->new('foo');
567             ...
568              
569             =head1 ATTRIBUTES
570              
571             =head2 facility
572              
573             Only mandatory attirbute. Used as the syslog faclity and to auto-construct a suiteable
574             filename for logging to file.
575              
576             =head1 METHODS
577              
578             =head2 add_to_buffer
579              
580             This method is usually not needed from by callers but may be in some rare ocasions
581             that's why it's made part of the public API. It just adds the passed data to the
582             internal buffer w/o logging it in the usual ways.
583              
584             =head2 clear_buffer
585              
586             This method clears the internal log buffer.
587              
588             =head2 forked
589              
590             This method should be called after it has been fork()ed to clear the internal
591             log buffer.
592              
593             =head2 get_buffer
594              
595             Retrieve those entries from the buffer that are gte the given severity.
596              
597             =head2 log
598              
599             Log a message. Takes a hash containing at least "message" and "level".
600              
601             =head2 BUILD
602              
603             Call on instatiation to set this class up.
604              
605             =head2 level_to_severity
606              
607             Translates a numeric level to severity string.
608              
609             =head2 severity_to_level
610              
611             Translates a severity string to a numeric level.
612              
613             =head1 NAME
614              
615             Log::Tree - Lightyweight logging w/ a tree based verbosity configuration
616             similar to Log4perl.
617              
618             =head1 AUTHOR
619              
620             Dominik Schulz <dominik.schulz@gauner.org>
621              
622             =head1 COPYRIGHT AND LICENSE
623              
624             This software is copyright (c) 2012 by Dominik Schulz.
625              
626             This is free software; you can redistribute it and/or modify it under
627             the same terms as the Perl 5 programming language system itself.
628              
629             =cut