File Coverage

lib/Su/Log.pm
Criterion Covered Total %
statement 147 177 83.0
branch 75 120 62.5
condition 21 42 50.0
subroutine 24 29 82.7
pod 21 21 100.0
total 288 389 74.0


line stmt bran cond sub pod time code
1             package Su::Log;
2 26     26   8397 use Test::More;
  26         60758  
  26         290  
3 26     26   7556 use Carp;
  26         51  
  26         1820  
4 26     26   3474 use Data::Dumper;
  26         40347  
  26         88066  
5              
6             =pod
7              
8             =head1 NAME
9              
10             Su::Log - A simple Logger which filters output by log level and regexp of the target class name.
11              
12             =head1 SYNOPSYS
13              
14             my $log = Su::Log->new;
15             $log->info("info message.");
16              
17             # Set the log level to output.
18             Su::Log->set_level("trace");
19             $log->trace("trace message.");
20              
21             # Disable logging and nothing output.
22             $log->off(__PACKAGE__);
23             $log->info("info message.");
24              
25             # Clear the logging state.
26             $log->clear(__PACKAGE__);
27              
28             # Enable logging.
29             $log->on(__PACKAGE__);
30             $log->info("info message.");
31              
32             # Clear the logging state.
33             $log->clear(__PACKAGE__);
34              
35             # Set the logging target and log level.
36             $log->on( 'Pkg::LogTarget', 'error' );
37              
38             # Set the logging target by regex.
39             $log->on( qr/Pkg::.*/, 'error' );
40              
41             # Clear the logging state.
42             $log->clear(qr/Pkg::.*/);
43              
44             # Output logs to the file.
45             $log->log_handler('path/to/logfile');
46              
47             =head1 DESCRIPTION
48              
49             Su::Log is a simple Logger module.
50             Su::Log has the following features.
51              
52             =over
53              
54             =item Narrow down the output by log level.
55              
56             =item Narrow down the logging target class.
57              
58             =item Narrow down the output by customized log kind.
59              
60             =item Customize the log handler function.
61              
62             =back
63              
64             =head1 FUNCTIONS
65              
66             =over
67              
68             =cut
69              
70             # Each elements consist of { class => $class, level => $level }
71             our @target_class = ();
72             our @target_tag = ();
73              
74             # Default log level.
75             our $level = "info";
76              
77             # User specified global log level.
78             our $global_log_level;
79              
80             # Elements are String or Regexp of the target class.
81             our @exclusion_class = ();
82              
83             # If you want to use this Log class not as oblect oriented style, but
84             # as function style directly, set current class name to this variable.
85             our $class_name;
86              
87             our $all_on = 0;
88             our $all_off = 0;
89             our $log_handler;
90              
91             BEGIN: {
92              
93             # Set default handler.
94             $log_handler =
95             sub { my $msg = _make_log_string(@_); print $msg; return $msg; };
96              
97             } ## end BEGIN:
98              
99             my $level_hash = {
100             debug => 0,
101             trace => 1,
102             info => 2,
103             warn => 3,
104             error => 4,
105             crit => 5,
106             };
107              
108             =item on()
109              
110             Add the passed module name to the list of the logging tareget.
111             If the parameter is not passed, then set the whole class as logging
112             target.
113              
114             =cut
115              
116             # NOTE: @target_class is a package variable, so shared with other
117             # logger even if you call this method via the specific logger
118             # instance.
119             sub on {
120 9 50 66 9 1 2642 my $self = shift if ( $_[0] eq __PACKAGE__ || ref $_[0] eq __PACKAGE__ );
121 9         17 my $class = shift;
122 9         15 my $level = shift;
123              
124 9 50       22 if ($class) {
125              
126             # diag( "on|" . $class . "|" . $level );
127              
128             # Remove old entry before adding new one.
129 9 100       65 if ( grep { $_->{class} =~ /^$class$/ } @target_class ) {
  4         78  
130              
131             # @target_class = grep { $_->{class} ne /^$class$/ } @target_class;
132 1         2 @target_class = grep { $_->{class} !~ /^$class$/ } @target_class;
  1         14  
133             }
134              
135 9         46 push @target_class, { class => $class, level => $level };
136              
137 9         23 my $bRegex = ref $class eq 'Regexp';
138 9 100       21 if ($bRegex) {
139 1         4 @exclusion_class = grep { $_ ne $class } @exclusion_class;
  0         0  
140             } else {
141 8         79 @exclusion_class = grep !/^$class$/, @exclusion_class;
142             }
143              
144             } else {
145 0         0 $self->{on} = 1;
146             }
147             } ## end sub on
148              
149             =item enable()
150              
151             This method force enable the logging regardless of whether the logging
152             of the target class is enabled or disabled.
153              
154             Internally, this method set the $all_on flag on, and $all_off flag
155             off. To clear this state, call the method L.
156              
157             =cut
158              
159             sub enable {
160 2     2 1 1369 $all_on = 1;
161 2         6 $all_off = 0;
162             }
163              
164             =item off()
165              
166             Disable the logging of the class which name is passed as a parameter.
167              
168             $log->off('Target::Class');
169              
170             If the parameter is omitted, this effects only own instance.
171              
172             $log->off;
173              
174             =cut
175              
176             sub off {
177 13 50 66 13 1 1040 my $self = shift if ( $_[0] eq __PACKAGE__ || ref $_[0] eq __PACKAGE__ );
178 13         21 my $class = shift;
179              
180             # In case of specified the log target.
181 13 100       24 if ($class) {
182              
183             # String parameter.
184 4 100       16 if ( !ref $class ) {
    50          
185 3 50       13 unless ( grep /^$class$/, @exclusion_class ) {
186 3         7 push @exclusion_class, $class;
187             }
188              
189             # Remove the passed class name from log target classes.
190 3         9 @target_class = grep { $_->{class} !~ /^$class$/ } @target_class;
  2         35  
191             } ## end if ( !ref $class )
192             elsif ( ref $class eq 'Regexp' ) {
193              
194 1 50       6 unless ( grep { $_ eq $class } @exclusion_class ) {
  0         0  
195 1         2 push @exclusion_class, $class;
196             }
197              
198             # Remove the passed regex from the log tareget classes.
199 1         5 @target_class = grep { $class ne $_->{class} } @target_class;
  0         0  
200             } ## end elsif ( ref $class eq 'Regexp')
201             } ## end if ($class)
202             else {
203              
204             # diag("off the logging of this instance.");
205              
206             # Instance parameter effects only own instance.
207 9         25 $self->{on} = undef;
208             } ## end else [ if ($class) ]
209             } ## end sub off
210              
211             =item disable()
212              
213             This method force disable the logging regardless of whether the logging
214             of the target class is enabled or disabled.
215              
216             Internally, ths method set the $all_off flag on, and $all_on flag off.
217             To clear this state, call the method L.
218              
219             =cut
220              
221             sub disable {
222              
223 1     1 1 8 $all_off = 1;
224 1         2 $all_on = 0;
225              
226             } ## end sub disable
227              
228             =item clear_all_flags()
229              
230             Clear C<$all_on> and C<$all_off> flags that is set by L
231             or L method.
232              
233             =cut
234              
235             sub clear_all_flags {
236 8     8 1 1650 $all_on = 0;
237 8         129 $all_off = 0;
238             }
239              
240             =item clear()
241              
242             If the parameter is passed, This method clear the state of the passed
243             target that is set by the method L and L.
244              
245             If the parameter is omitted, then clear all of the log settings.
246              
247             =cut
248              
249             sub clear {
250 7 50 66 7 1 3527 my $self = shift if ( ref $_[0] eq __PACKAGE__ || $_[0] eq __PACKAGE__ );
251 7         11 my $class = shift;
252              
253             # Remove the specified expression.
254 7 100       127 if ($class) {
255 3         6 my $bRegex = ref $class eq 'Regexp';
256 3 100       8 if ($bRegex) {
257 2         5 @target_class = grep { $class ne $_->{class} } @target_class;
  1         6  
258 2         6 @exclusion_class = grep { $_ ne $class } @exclusion_class;
  1         8  
259             } else {
260 1         3 @target_class = grep { $_->{class} !~ /^$class$/ } @target_class;
  2         20  
261 1         4 @exclusion_class = grep !/^$class$/, @exclusion_class;
262             }
263              
264             } else {
265              
266             # Clear all condition.
267 4         14 @target_class = ();
268 4         9 @target_tag = ();
269 4         8 @exclusion_class = ();
270 4         10 clear_all_flags();
271             } ## end else [ if ($class) ]
272             } ## end sub clear
273              
274             =item tag_on()
275              
276             Add the passed tag to the target tags list.
277              
278             =cut
279              
280             sub tag_on {
281 0 0   0 1 0 shift if ( $_[0] eq __PACKAGE__ );
282 0         0 my $tag = shift;
283 0         0 push @target_tag, $tag;
284             }
285              
286             =item tag_off()
287              
288             Remove the passed tag from the target tags list.
289              
290             =cut
291              
292             sub tag_off {
293 0 0   0 1 0 shift if ( $_[0] eq __PACKAGE__ );
294 0         0 my $tag = shift;
295 0         0 @target_tag = grep !/^$tag$/, @target_tag;
296             }
297              
298             =item new()
299              
300             Constructor.
301              
302             my $log = new Su::Log->new;
303             my $log = new Su::Log->new($self);
304             my $log = new Su::Log->new('PKG::TargetClass');
305              
306             Instantiate the Logger class. The passed instance or the string of the
307             module name is registered as a logging target class. If the parameter
308             is omitted, then the caller is registered automatically.
309              
310             =cut
311              
312             sub new {
313 163     163 1 5176 my $self = shift;
314 163 100       413 $self = ref $self if ( ref $self );
315 163         222 my $target_class = shift;
316              
317             # If passed argment is a reference of the instance, then extract class name.
318 163         314 my $class_name = ref $target_class;
319              
320             # Else, use passed string as class name.
321 163 100       359 if ( !$class_name ) {
322 152         201 $class_name = $target_class;
323             }
324              
325 163 100       327 if ( !$class_name ) {
326 152         328 $class_name = caller();
327             }
328              
329             # diag("classname:" . $class_name);
330             # diag( Dumper($class_name));
331             # Su::Log->trace( "classname:" . $class_name );
332             # Su::Log->trace( Dumper($class_name) );
333              
334             # Add the caller class to the target list automatically.
335              
336 163         1075 return bless { class_name => $class_name, on => 1, level => $level }, $self;
337             } ## end sub new
338              
339             =item is_target()
340              
341             Determine whether the module is a logging target or not.
342              
343             =cut
344              
345             sub is_target {
346 2537     2537 1 3483 my $self = shift;
347              
348 2537 100       6122 if ($all_on) {
    100          
349              
350 3         14 return { is_target => 1, has_level => undef };
351             } elsif ($all_off) {
352              
353 4         16 return { is_target => 0, has_level => undef };
354             }
355              
356 2530         4046 my $self_class_name = $self;
357 2530 100       4505 if ( ref $self ) {
358 81 50       216 $self_class_name = $self->{class_name} ? $self->{class_name} : $class_name;
359             }
360              
361             #diag("check classname:" . $self->{class_name});
362             # if(! defined($self->{class_name})){
363             # die "Class name not passed to the log instance.";
364             # }
365              
366             #NOTE:Can not trace via trace or something Log class provide. Because recurssion occurs.
367             #diag( @target_class);
368              
369             # diag("grep result:" . (grep /^$self->{class_name}$/, @target_class));
370             # if (index($self->{class_name}, @target_class) != -1){
371             # diag( "exc cls:" . Dumper(@exclusion_class) );
372 2530 100       7949 if (
    100          
373 8 100       101 grep {
374             ref $_ eq 'Regexp'
375             ? $self_class_name =~ /$_/
376             : $self_class_name =~ /^$_$/
377             } @exclusion_class
378             )
379 47         113 {
380 6         15 return 0;
381             } elsif (
382             my @info =
383             grep {
384             my $bRegex = ref $_->{class} eq 'Regexp';
385 47 100       109 if ($bRegex) {
386              
387             # diag('use regex');
388              
389             # Use class field as regexp.
390 1         8 $self_class_name =~ /$_->{class}/;
391             } else {
392              
393             # diag('use str');
394              
395             # Use class field as string,directly.
396 46         397 $self_class_name =~ /^$_->{class}$/;
397             } ## end else [ if ($bRegex) ]
398             } @target_class
399             )
400             {
401              
402             # diag( Dumper(@target_class) );
403             # diag("here2:$info[0]->{class} --- $info[0]->{level}");
404 24         117 return { is_target => 1, has_level => 1, level => $info[0]->{level} };
405             } else {
406              
407             # diag( "here3:" . $self . $self->{on} );
408              
409             # Return the instance flag.
410             # return $self->{on};
411 2500         13316 return { is_target => $self->{on}, has_level => undef };
412             } ## end else [ if ($bRegex) ]
413             } ## end sub is_target
414              
415             =item set_level()
416              
417             Su::Log->set_level("trace");
418              
419             Set the log level which effects instance scope. Default level is B;
420              
421             =cut
422              
423             sub set_level {
424              
425             # The first argment may be reference of object or string of class name.
426 2 50 33 2 1 612 my $self = shift if ( ref $_[0] eq __PACKAGE__ || $_[0] eq __PACKAGE__ );
427 2         4 my $passed_level = shift;
428 2         121 croak "Passed log level is invalid:" . $passed_level
429 2 50       4 if !grep /^$passed_level$/, keys %{$level_hash};
430 2         8 $self->{level} = $passed_level;
431              
432             } ## end sub set_level
433              
434             =item set_global_log_level()
435              
436             Su::Log->set_default_log_level("trace");
437              
438             Set the log level. This setting effects as the package scope variable.
439              
440             To clear the $global_log_level flag, pass undef to this method.
441              
442             =cut
443              
444             sub set_global_log_level {
445              
446             # The first argment may be reference of object or string of class name.
447 5 50 66 5 1 1622 shift if ( ref $_[0] eq __PACKAGE__ || $_[0] eq __PACKAGE__ );
448 5         10 my $passed_level = shift;
449 3         81 croak "Passed log level is invalid:" . $passed_level
450 5 50 66     19 if defined $passed_level && !grep /^$passed_level$/, keys %{$level_hash};
451 5         14 $global_log_level = $passed_level;
452             } ## end sub set_global_log_level
453              
454             =item is_large_level()
455              
456             Return whether the passed log level is larger than the current log level or not.
457             If the second parameter is passed, then compare that value as the current log level.
458              
459             =cut
460              
461             sub is_large_level {
462 73 50   73 1 209 my $self = shift if ( ref $_[0] eq __PACKAGE__ );
463 73 50       164 $self = caller() unless $self;
464              
465             # diag "dumper:" . Dumper( $self->{class_name} );
466 73         91 my $arg = shift;
467              
468             #NOTE:Can not trace via trace command which Log class provides, because recursion occurs.
469             #diag("compare:" . $arg . ":" . $level);
470              
471 73         126 my $compare_target_level = shift;
472              
473             # If the second parameter is passed, use it as compare target directly.
474 73 100       331 unless ($compare_target_level) {
475 64 100       192 if ( defined $global_log_level ) {
    50          
476 3         6 $compare_target_level = $global_log_level;
477             } elsif ( $self->{level} ) {
478 61         96 $compare_target_level = $self->{level};
479             } else {
480 0         0 $compare_target_level = $level;
481             }
482             } ## end unless ($compare_target_level)
483              
484             # diag "[TRACE]compare_target_level:$compare_target_level:arg:$arg\n";
485 73 100       527 return $level_hash->{$arg} >= $level_hash->{$compare_target_level} ? 1 : 0;
486             } ## end sub is_large_level
487              
488             sub _log_method_impl {
489 2537 100   2537   5536 my $self = shift if ( ref $_[0] eq __PACKAGE__ );
490 2537         3307 my $opt_href = shift;
491 2537         4941 my $caller_prefix = $opt_href->{caller};
492 2537         4127 my $method_level = $opt_href->{level};
493 2537 100       5153 my $log_handler = $self->{log_handler} ? $self->{log_handler} : $log_handler;
494 2537 100       4458 my $target_info = is_target( _is_empty($self) ? caller() : $self );
495              
496 2537 100 100     15383 if ( $target_info->{is_target}
497             && $self->is_large_level( $method_level, $target_info->{level} ) )
498             {
499 38         172 return $log_handler->( "[$caller_prefix]", uc("[$method_level]"), @_ );
500             }
501             } ## end sub _log_method_impl
502              
503             =item trace()
504              
505             Log the passed message as trace level.
506              
507             =cut
508              
509             sub trace {
510 2480 50 66 2480 1 32138 my $self = shift if ( ref $_[0] eq __PACKAGE__ || $_[0] eq __PACKAGE__ );
511              
512 2480         7244 my ( $pkg, $file, $line ) = caller;
513              
514 2480         5173 my $caller_prefix = $file . ':L' . $line;
515 2480         10140 $self->_log_method_impl( { caller => $caller_prefix, level => "trace" }, @_ );
516             } ## end sub trace
517              
518             =item info()
519              
520             Log the passed message as info level.
521              
522             =cut
523              
524             sub info {
525 42 50 33 42 1 3348 my $self = shift if ( ref $_[0] eq __PACKAGE__ || $_[0] eq __PACKAGE__ );
526              
527 42         128 my ( $pkg, $file, $line ) = caller;
528              
529 42         93 my $caller_prefix = $file . ':L' . $line;
530 42         363 $self->_log_method_impl( { caller => $caller_prefix, level => "info" }, @_ );
531             } ## end sub info
532              
533             =item warn()
534              
535             Log the passed message as warn level.
536              
537             =cut
538              
539             sub warn {
540 3 50 33 3 1 28 my $self = shift if ( ref $_[0] eq __PACKAGE__ || $_[0] eq __PACKAGE__ );
541 3         10 my ( $pkg, $file, $line ) = caller;
542 3         10 my $caller_prefix = $file . ':L' . $line;
543 3         16 $self->_log_method_impl( { caller => $caller_prefix, level => "warn" }, @_ );
544             } ## end sub warn
545              
546             =item error()
547              
548             Log the passed message as error level.
549              
550             =cut
551              
552             sub error {
553 4 50 33 4 1 700 my $self = shift if ( ref $_[0] eq __PACKAGE__ || $_[0] eq __PACKAGE__ );
554 4         13 my ( $pkg, $file, $line ) = caller;
555 4         11 my $caller_prefix = $file . ':L' . $line;
556 4         18 $self->_log_method_impl( { caller => $caller_prefix, level => "error" }, @_ );
557             } ## end sub error
558              
559             =item crit()
560              
561             Log the passed message as crit level.
562              
563             =cut
564              
565             sub crit {
566 4 50 33 4 1 29 my $self = shift if ( ref $_[0] eq __PACKAGE__ || $_[0] eq __PACKAGE__ );
567 4         14 my ( $pkg, $file, $line ) = caller;
568 4         12 my $caller_prefix = $file . ':L' . $line;
569 4         18 $self->_log_method_impl( { caller => $caller_prefix, level => "crit" }, @_ );
570             } ## end sub crit
571              
572             =item debug()
573              
574             Log the passed message as debug level.
575              
576             =cut
577              
578             sub debug {
579 4 50 33 4 1 511 my $self = shift if ( ref $_[0] eq __PACKAGE__ || $_[0] eq __PACKAGE__ );
580 4         14 my ( $pkg, $file, $line ) = caller;
581 4         10 my $caller_prefix = $file . ':L' . $line;
582 4         18 $self->_log_method_impl( { caller => $caller_prefix, level => "debug" }, @_ );
583              
584             } ## end sub debug
585              
586             =item log()
587              
588             Log the message with the passed tag, if the passed tag is active.
589              
590             my $log = Su::Log->new($self);
591             $log->log("some_tag","some message");
592              
593             =cut
594              
595             sub log {
596 0 0   0 1 0 my $self = shift if ( ref $_[0] eq __PACKAGE__ );
597 0         0 my $tag = shift;
598              
599 0 0       0 my $log_handler = $self->{log_handler} ? $self->{log_handler} : $log_handler;
600 0 0 0     0 if ( is_target( _is_empty($self) ? caller() : $self )
    0          
601             && ( grep /^$tag$/, @target_tag ) )
602             {
603 0         0 my ( $pkg, $file, $line ) = caller;
604 0         0 my $caller_prefix = $file . ':L' . $line;
605 0         0 return $log_handler->( "[$caller_prefix]", "[$tag]", @_ );
606             } ## end if ( is_target( _is_empty...))
607              
608             } ## end sub log
609              
610             =item log_handler()
611              
612             Set the passed method as the log handler of L.
613              
614             $log->log_handler(\&hndl);
615             $log->info("info message");
616              
617             sub hndl{
618             print(join 'custom log handler:', @_);
619             }
620              
621             $log->log_handler(
622             sub {
623             my $level = shift;
624             my $msg = @_;
625             print $F $level . join( ' ', @_ ) . "\n";
626             }
627             );
628              
629             If the passed parameter is string, then automatically the handler is
630             set to output log to the passed file name.
631              
632             =cut
633              
634             sub log_handler {
635 1 50   1 1 8 my $self = shift if ( ref $_[0] eq __PACKAGE__ );
636 1         3 my $handler = shift;
637              
638             # Work as setter method.
639 1 50       4 if ($handler) {
640 1 50       5 if ($self) {
641 1 50       5 if ( ref $handler eq 'CODE' ) {
642 1         3 $self->{log_handler} = $handler;
643             } else {
644              
645             # $handler is passed as log file name.
646 0         0 $self->{log_handler} = _make_default_log_file_handler($handler);
647             }
648             } else {
649              
650             # $log_handler = $handler;
651 0 0       0 if ( ref $handler eq 'CODE' ) {
652 0         0 $log_handler = $handler;
653             } else {
654              
655             # $handler is passed as log file name.
656 0         0 $log_handler = _make_default_log_file_handler($handler);
657             }
658              
659             } ## end else [ if ($self) ]
660             } else {
661              
662             # The param is omitted, just work as a getter method.
663 0         0 return $log_handler;
664             }
665             } ## end sub log_handler
666              
667             =begin comment
668              
669             Return the handler to output log to the log file. Passed parameter is
670             a log file name.
671              
672             =end comment
673              
674             =cut
675              
676             sub _make_default_log_file_handler {
677 0 0   0   0 my $self = shift if ( ref $_[0] eq __PACKAGE__ );
678 0         0 my $log_file_name = shift;
679 0 0       0 open( my $F, '>>', $log_file_name ) or die $!;
680             return sub {
681 0     0   0 my $level = shift;
682 0         0 my $msg = _make_log_string(@_);
683 0         0 print $F $level . $msg;
684 0         0 };
685             } ## end sub _make_default_log_file_handler
686              
687             =begin comment
688              
689             Internal Utility function.
690              
691             =end comment
692              
693             =cut
694              
695             sub _is_empty {
696 2537     2537   2789 my $arg = shift;
697 2537 50       4590 return 1 if ( !$arg );
698 2537 100       5442 if ( ref $arg eq 'HASH' ) {
699 2449 50       2232 return 1 unless ( scalar keys %{$arg} );
  2449         13732  
700             }
701 88         385 return 0;
702             } ## end sub _is_empty
703              
704             =begin comment
705              
706             Add the prefix of time to the passed parameter and return it as a string.
707             The caller information is passed to this method as a parameter.
708              
709             =end comment
710              
711             =cut
712              
713             sub _make_log_string {
714 37     37   1409 my ( $s, $mi, $h, $d, $m, $y ) = ( localtime(time) )[ 0 .. 6 ];
715 37         255 my $date_prefix = sprintf "%04d/%02d/%02d %02d:%02d:%02d", $y + 1900, $m + 1,
716             $d,
717             $h, $mi, $s;
718              
719 37         151 return '[' . $date_prefix . ']' . join( '', @_, "\n" );
720             } ## end sub _make_log_string
721              
722             =pod
723              
724             =back
725              
726             =cut
727