File Coverage

blib/lib/Bio/Phylo/Util/Logger.pm
Criterion Covered Total %
statement 73 131 55.7
branch 23 64 35.9
condition 5 18 27.7
subroutine 16 26 61.5
pod 16 17 94.1
total 133 256 51.9


line stmt bran cond sub pod time code
1             package Bio::Phylo::Util::Logger;
2 57     57   350 use strict;
  57         136  
  57         1671  
3 57     57   284 use base 'Exporter';
  57         105  
  57         6233  
4 57     57   69510 use Term::ANSIColor;
  57         390738  
  57         4463  
5 57     57   473 use Bio::Phylo::Util::Exceptions 'throw';
  57         113  
  57         2396  
6 57     57   3168 use Bio::Phylo::Util::CONSTANT qw'/looks_like/';
  57         110  
  57         18773  
7              
8             our ( %VERBOSITY, $PREFIX, %STYLE );
9             our $STYLE = 'detailed';
10             our $COLORED = 1; # new default: we use colors
11             our $TRACEBACK = 0;
12             our @EXPORT_OK = qw(DEBUG INFO WARN ERROR FATAL VERBOSE);
13             our %EXPORT_TAGS = ( 'simple' => [@EXPORT_OK], 'levels' => [@EXPORT_OK] );
14             our %COLORS = (
15             'DEBUG' => 'blue',
16             'INFO' => 'green',
17             'WARN' => 'yellow',
18             'ERROR' => 'bold red',
19             'FATAL' => 'red',
20             );
21              
22             BEGIN {
23            
24             # compute the path to the root of Bio::Phylo,
25             # use that as the default prefix
26 57     57   234 my $package = __PACKAGE__;
27 57         113 my $file = __FILE__;
28 57         287 $package =~ s/::/\//g;
29 57         155 $package .= '.pm';
30 57         845 $file =~ s/\Q$package\E$//;
31 57         158 $PREFIX = $file;
32            
33             # set verbosity to 2, i.e. warn
34 57   50     461 $VERBOSITY{'*'} = $ENV{'BIO_PHYLO_VERBOSITY'} || 2;
35            
36             # define verbosity styles
37 57         76332 %STYLE = (
38             'simple' => '${level}: $message',
39             'detailed' => '$level $sub [$file $line] - $message',
40             );
41             }
42              
43             {
44             my %levels = ( FATAL => 0, ERROR => 1, WARN => 2, INFO => 3, DEBUG => 4 );
45             my @listeners = ( sub {
46             my ( $string, $level ) = @_;
47             if ( $COLORED and -t STDERR ) {
48             print STDERR colored( $string, $COLORS{$level} );
49             }
50             else {
51             print STDERR $string;
52             }
53             } ); # default
54              
55             # dummy constructor that dispatches to VERBOSE(),
56             # then returns the package name
57             sub new {
58 200     200 1 841 my $class = shift;
59 200 50       767 $class->VERBOSE(@_) if @_;
60 200         605 return $class;
61             }
62              
63             # set additional listeners
64             sub set_listeners {
65 2     2 1 10 my ( $class, @args ) = @_;
66 2         4 for my $arg (@args) {
67 2 100       5 if ( looks_like_instance $arg, 'CODE' ) {
68 1         3 push @listeners, $arg;
69             }
70             else {
71 1         5 throw 'BadArgs' => "$arg not a CODE reference";
72             }
73             }
74 1         3 return $class;
75             }
76            
77             # this is never called directly. rather, messages are dispatched here
78             # by the DEBUG() ... FATAL() subs below
79             sub LOG ($$) {
80 229139     229139 0 341912 my ( $message, $level ) = @_;
81            
82             # probe the call stack
83 229139         808194 my ( $pack2, $file2, $line2, $sub ) = caller( $TRACEBACK + 2 );
84 229139         689252 my ( $pack1, $file, $line, $sub1 ) = caller( $TRACEBACK + 1 );
85            
86             # cascade verbosity from global to local
87 229139         382721 my $verbosity = $VERBOSITY{'*'}; # global
88 229139 50       379027 $verbosity = $VERBOSITY{$pack1} if exists $VERBOSITY{$pack1}; # package
89 229139 50 33     577527 $verbosity = $VERBOSITY{$sub} if $sub and exists $VERBOSITY{$sub}; # sub
90            
91             # verbosity is higher than the current caller, proceed
92 229139 50       450009 if ( $verbosity >= $levels{$level} ) {
93              
94             # strip the prefix from the calling file's path
95 0 0       0 if ( index($file, $PREFIX) == 0 ) {
96 0         0 $file =~ s/^\Q$PREFIX\E//;
97             }
98            
99             # select one of the templates
100 0         0 my $string;
101 0         0 my $s = $STYLE{$STYLE};
102 0         0 $string = eval "qq[$s\n]";
103            
104             # dispatch to the listeners
105 0         0 $_->( $string, $level, $sub, $file, $line, $message ) for @listeners;
106             }
107             }
108            
109             # these subs both return their verbosity constants and, if
110             # provided with a message, dispatch the message to LOG()
111 0 0   0 1 0 sub FATAL (;$) { LOG $_[0], 'FATAL' if $_[0]; $levels{'FATAL'} }
  0         0  
112 0 0   0 1 0 sub ERROR (;$) { LOG $_[0], 'ERROR' if $_[0]; $levels{'ERROR'} }
  0         0  
113 4 50   4 1 18 sub WARN (;$) { LOG $_[0], 'WARN' if $_[0]; $levels{'WARN'} }
  4         6  
114 61084 50   61084 1 147207 sub INFO (;$) { LOG $_[0], 'INFO' if $_[0]; $levels{'INFO'} }
  61084         77668  
115 168061 100   168061 1 389323 sub DEBUG (;$) { LOG $_[0], 'DEBUG' if $_[0]; $levels{'DEBUG'} }
  168061         214457  
116              
117             sub PREFIX {
118 0     0 1 0 my ( $class, $prefix ) = @_;
119 0 0       0 $PREFIX = $prefix if $prefix;
120 0         0 return $PREFIX;
121             }
122              
123             sub VERBOSE {
124 14 100 66 14 1 121 shift if ref $_[0] or $_[0] eq __PACKAGE__;
125 14 50       58 if (@_) {
126 14         64 my %opt = looks_like_hash @_;
127 14         39 my $level = $opt{'-level'};
128            
129             # verbosity is specified
130 14 50       79 if ( defined $level ) {
131              
132             # check validity
133 14 50 25     94 if ( $level > 4 xor $level < 0 ) {
134 0         0 throw 'OutOfBounds' => "'-level' can be between 0 and 4, not $level";
135             }
136            
137             # verbosity is specified for one or more packages
138 14 50       80 if ( my $class = $opt{'-class'} ) {
    50          
139 0 0       0 if ( ref $class eq 'ARRAY' ) {
140 0         0 for my $c ( @{ $class } ) {
  0         0  
141 0         0 $VERBOSITY{$c} = $level;
142 0         0 INFO "Changed verbosity for class $c to $level";
143             }
144             }
145             else {
146 0         0 $VERBOSITY{$class} = $level;
147 0         0 INFO "Changed verbosity for class $class to $level";
148             }
149             }
150            
151             # verbosity is specified for one or more methods
152             elsif ( my $method = $opt{'-method'} ) {
153 0 0       0 if ( ref $method eq 'ARRAY' ) {
154 0         0 for my $m ( @{ $method } ) {
  0         0  
155 0         0 $VERBOSITY{$m} = $level;
156 0         0 INFO "Changed verbosity for method $m to $level";
157             }
158             }
159             else {
160 0         0 $VERBOSITY{$method} = $level;
161 0         0 INFO "Changed verbosity for method $method to $level";
162             }
163             }
164            
165             # verbosity is set globally
166             else {
167 14         32 $VERBOSITY{'*'} = $level;
168 14         85 INFO "Changed global verbosity to $VERBOSITY{'*'}";
169             }
170             }
171            
172             # log to a file
173 14 50       56 if ( $opt{'-file'} ) {
174 0 0       0 open my $fh, '>>', $opt{'-file'} or throw 'FileError' => $!;
175 0     0   0 __PACKAGE__->set_listeners(sub { print $fh shift });
  0         0  
176             }
177            
178             # log to a handle
179 14 50       57 if ( $opt{'-handle'} ) {
180 0         0 my $fh = $opt{'-handle'};
181 0     0   0 __PACKAGE__->set_listeners(sub { print $fh shift });
  0         0  
182             }
183            
184             # log to listeners
185 14 50       48 if ( $opt{'-listeners'} ) {
186 0         0 __PACKAGE__->set_listeners(@{$opt{'-listeners'}});
  0         0  
187             }
188            
189             # update the prefix
190 14 50       45 if ( $opt{'-prefix'} ) {
191 0         0 __PACKAGE__->PREFIX($opt{'-prefix'});
192             }
193            
194             # set logstyle
195 14 50       48 if ( $opt{'-style'} ) {
196 0         0 my $s = lc $opt{'-style'};
197 0 0       0 if ( exists $STYLE{$s} ) {
198 0         0 $STYLE = $s;
199             }
200             }
201            
202             # turn colors on/off. default is on.
203 14 50       57 $COLORED = !!$opt{'-colors'} if defined $opt{'-colors'};
204             }
205 14         39 return $VERBOSITY{'*'};
206             }
207            
208             # Change the terminal to a predefined color. For example to make sure that
209             # an entire exception (or part of it) is marked up as FATAL, or so that the
210             # output from an external command is marked up as DEBUG.
211             sub start_color {
212 0     0 1 0 my ( $self, $level, $handle ) = @_;
213 0 0       0 $handle = \*STDERR if not $handle;
214 0 0 0     0 if ( $COLORED and -t $handle ) {
215 0         0 print $handle color $COLORS{$level};
216             }
217 0         0 return $COLORS{$level};
218             }
219            
220             sub stop_color {
221 0     0 1 0 my ( $self, $handle ) = @_;
222 0 0       0 $handle = \*STDERR if not $handle;
223 0 0 0     0 if ( $COLORED and -t $handle ) {
224 0         0 print $handle color 'reset';
225             }
226 0         0 return $self;
227             }
228            
229             # aliases for singleton methods
230             sub fatal {
231 0     0 1 0 my $self = shift;
232 0         0 $TRACEBACK++;
233 0         0 FATAL shift;
234 0         0 $TRACEBACK--;
235             }
236             sub error {
237 0     0 1 0 my $self = shift;
238 0         0 $TRACEBACK++;
239 0         0 ERROR shift;
240 0         0 $TRACEBACK--;
241             }
242             sub warn {
243 4     4 1 11 my $self = shift;
244 4         8 $TRACEBACK++;
245 4         12 WARN shift;
246 4         11 $TRACEBACK--;
247             }
248             sub info {
249 59072     59072 1 80077 my $self = shift;
250 59072         72407 $TRACEBACK++;
251 59072         107054 INFO shift;
252 59072         89378 $TRACEBACK--;
253             }
254             sub debug {
255 168061     168061 1 231424 my $self = shift;
256 168061         193299 $TRACEBACK++;
257 168061         303932 DEBUG shift;
258 168061         264782 $TRACEBACK--;
259             }
260            
261             # empty destructor so we don't go up inheritance tree at the end
262       0     sub DESTROY {}
263             }
264             1;
265              
266             =head1 NAME
267              
268             Bio::Phylo::Util::Logger - Logger of internal messages of several severity
269             levels
270              
271             =head1 SYNOPSIS
272              
273             use strict;
274             use Bio::Phylo::Util::Logger ':levels'; # import level constants
275             use Bio::Phylo::IO 'parse';
276             use Bio::Phylo::Factory;
277            
278             # Set the verbosity level of the tree class.
279             # "DEBUG" is the most verbose level. All log messages
280             # emanating from the tree class will be
281             # transmitted. For this to work the level constants
282             # have to have been imported!
283             use Bio::Phylo::Forest::Tree 'verbose' => DEBUG; # note: DEBUG is not quoted!
284            
285             # Create a file handle for logger to write to.
286             # This is not necessary, by default the logger
287             # writes to STDERR, but sometimes you will want
288             # to write to a file, as per this example.
289             open my $fh, '>', 'parsing.log' or die $!;
290            
291             # Create a logger object.
292             my $fac = Bio::Phylo::Factory->new;
293             my $logger = $fac->create_logger;
294            
295             # Set the verbosity level of the set_name
296             # method in the base class. Messages coming
297             # from this method will be transmitted.
298             $logger->VERBOSE(
299             '-level' => DEBUG, # note, not quoted, this is a constant!
300             '-method' => 'Bio::Phylo::set_name', # quoted, otherwise bareword error!
301             );
302            
303             # 'Listeners' are subroutine references that
304             # are executed when a message is transmitted.
305             # The first argument passed to these subroutines
306             # is the log message. This particular listener
307             # will write the message to the 'parsing.log'
308             # file, if the $fh file handle is still open.
309             $logger->set_listeners(
310             sub {
311             my ($msg) = @_;
312             if ( $fh->opened ) {
313             print $fh $msg;
314             }
315             }
316             );
317              
318             # Now parse a tree, and see what is logged.
319             my $tree = parse(
320             '-format' => 'newick',
321             '-string' => do { local $/; },
322             )->first;
323              
324             # Cleanly close the log handle.
325             close $fh;
326            
327             __DATA__
328             ((((A,B),C),D),E);
329              
330             The example above will write something like the following to the log file:
331              
332             INFO Bio::Phylo::Forest::Tree::new [Bio/Phylo/Forest/Tree.pm, 99] - constructor called for 'Bio::Phylo::Forest::Tree'
333             INFO Bio::Phylo::set_name [Bio/Phylo.pm, 281] - setting name 'A'
334             INFO Bio::Phylo::set_name [Bio/Phylo.pm, 281] - setting name 'B'
335             INFO Bio::Phylo::set_name [Bio/Phylo.pm, 281] - setting name 'C'
336             INFO Bio::Phylo::set_name [Bio/Phylo.pm, 281] - setting name 'D'
337             INFO Bio::Phylo::set_name [Bio/Phylo.pm, 281] - setting name 'E'
338              
339             =head1 DESCRIPTION
340              
341             This class defines a logger, a utility object for logging messages.
342             The other objects in Bio::Phylo use this logger to give detailed feedback
343             about what they are doing at per-class, per-method, user-configurable log levels
344             (DEBUG, INFO, WARN, ERROR and FATAL). These log levels are constants that are
345             optionally exported by this class by passing the ':levels' argument to your
346             'use' statement, like so:
347              
348             use Bio::Phylo::Util::Logger ':levels';
349              
350             If for some reason you don't want this behaviour (i.e. because there is
351             something else by these same names in your namespace) you must use the fully
352             qualified names for these levels, i.e. Bio::Phylo::Util::Logger::DEBUG and
353             so on.
354              
355             The least verbose is level FATAL, in which case only 'fatal' messages are shown.
356             The most verbose level, DEBUG, shows debugging messages, including from internal
357             methods (i.e. ones that start with underscores, and special 'ALLCAPS' perl
358             methods like DESTROY or TIEARRAY). For example, to monitor what the root class
359             is doing, you would say:
360              
361             $logger->( -class => 'Bio::Phylo', -level => DEBUG )
362              
363             To define global verbosity you can omit the -class argument. To set verbosity
364             at a more granular level, you can use the -method argument, which takes a
365             fully qualified method name such as 'Bio::Phylo::set_name', such that messages
366             originating from within that method's body get a different verbosity level.
367              
368             =head1 METHODS
369              
370             =head2 CONSTRUCTOR
371              
372             =over
373              
374             =item new()
375              
376             Constructor for Logger.
377              
378             Type : Constructor
379             Title : new
380             Usage : my $logger = Bio::Phylo::Util::Logger->new;
381             Function: Instantiates a logger
382             Returns : a Bio::Phylo::Util::Logger object
383             Args : -level => Bio::Phylo::Util::Logger::INFO (DEBUG/INFO/WARN/ERROR/FATAL)
384             -class => a package (or array ref) for which to set verbosity (optional)
385             -method => a sub name (or array ref) for which to set verbosity (optional)
386             -file => a file to which to append logging messages
387             -listeners => array ref of subs that handle logging messages
388             -prefix => a path fragment to strip from the paths in logging messages
389            
390              
391             =back
392              
393             =head2 VERBOSITY LEVELS
394              
395             =over
396              
397             =item FATAL
398              
399             Rarely happens, usually an exception is thrown instead.
400              
401             =item ERROR
402              
403             If this happens, something is seriously wrong that needs to be addressed.
404              
405             =item WARN
406              
407             If this happens, something is seriously wrong that needs to be addressed.
408              
409             =item INFO
410              
411             If something weird is happening, turn up verbosity to this level as it might
412             explain some of the assumptions the code is making.
413              
414             =item DEBUG
415              
416             This is very verbose, probably only useful if you write core Bio::Phylo code.
417              
418             =back
419              
420             =head2 LOGGING METHODS
421              
422             =over
423              
424             =item debug()
425              
426             Prints argument debugging message, depending on verbosity.
427              
428             Type : logging method
429             Title : debug
430             Usage : $logger->debug( "debugging message" );
431             Function: prints debugging message, depending on verbosity
432             Returns : invocant
433             Args : logging message
434              
435             =item info()
436              
437             Prints argument informational message, depending on verbosity.
438              
439             Type : logging method
440             Title : info
441             Usage : $logger->info( "info message" );
442             Function: prints info message, depending on verbosity
443             Returns : invocant
444             Args : logging message
445              
446             =item warn()
447              
448             Prints argument warning message, depending on verbosity.
449              
450             Type : logging method
451             Title : warn
452             Usage : $logger->warn( "warning message" );
453             Function: prints warning message, depending on verbosity
454             Returns : invocant
455             Args : logging message
456              
457             =item error()
458              
459             Prints argument error message, depending on verbosity.
460              
461             Type : logging method
462             Title : error
463             Usage : $logger->error( "error message" );
464             Function: prints error message, depending on verbosity
465             Returns : invocant
466             Args : logging message
467              
468             =item fatal()
469              
470             Prints argument fatal message, depending on verbosity.
471              
472             Type : logging method
473             Title : fatal
474             Usage : $logger->fatal( "fatal message" );
475             Function: prints fatal message, depending on verbosity
476             Returns : invocant
477             Args : logging message
478              
479             =item set_listeners()
480              
481             Adds listeners to send log messages to.
482              
483             Type : Mutator
484             Title : set_listeners()
485             Usage : $logger->set_listeners( sub { warn shift } )
486             Function: Sets additional listeners to log to (e.g. a file)
487             Returns : invocant
488             Args : One or more code references
489             Comments: On execution of the listeners, the @_ arguments are:
490             $log_string, # the formatted log string
491             $level, # log level, i.e DEBUG, INFO, WARN, ERROR or FATAL
492             $subroutine, # the calling subroutine
493             $filename, # filename where log method was called
494             $line, # line where log method was called
495             $msg # the unformatted message
496              
497             =item start_color()
498              
499             Changes color of output stream to that of specified logging level. This so that for
500             example all errors are automatically marked up as 'FATAL', or all output generated
501             by an external program is marked up as 'DEBUG'
502              
503             Type : Mutator
504             Title : start_color()
505             Usage : $logger->start_color( 'DEBUG', \*STDOUT )
506             Function: Changes color of output stream
507             Returns : color name
508             Args : Log level whose color to use,
509             (optional) which stream to change, default is STDERR
510              
511             =item stop_color()
512              
513             Resets the color initiated by start_color()
514              
515             Type : Mutator
516             Title : stop_color()
517             Usage : $logger->stop_color( \*STDOUT )
518             Function: Changes color of output stream
519             Returns : color name
520             Args : (Optional) which stream to reset, default is STDERR
521              
522              
523             =item PREFIX()
524              
525             Getter and setter of path prefix to strip from source file paths in messages.
526             By default, messages will have a field such as C<[$PREFIX/Bio/Phylo.pm, 280]>,
527             which indicates the message was sent from line 280 in file Bio/Phylo.pm inside
528             path $PREFIX. This is done so that your log won't be cluttered with
529             unnecessarily long paths. To find out what C<$PREFIX> is set to, call the
530             PREFIX() method on the logger, and to change it provide a path argument
531             relative to which the paths to source files will be constructed.
532              
533             Type : Mutator/Accessor
534             Title : PREFIX()
535             Usage : $logger->PREFIX( '/path/to/bio/phylo' )
536             Function: Sets/gets $PREFIX
537             Returns : Verbose level
538             Args : Optional: a path
539             Comments:
540              
541             =item VERBOSE()
542              
543             Setter for the verbose level. This comes in five levels:
544              
545             FATAL = only fatal messages (though, when something fatal happens, you'll most
546             likely get an exception object),
547            
548             ERROR = errors (hopefully recoverable),
549            
550             WARN = warnings (recoverable),
551            
552             INFO = info (useful diagnostics),
553            
554             DEBUG = debug (almost every method call)
555              
556             Without additional arguments, i.e. by just calling VERBOSE( -level => $level ),
557             you set the global verbosity level. By default this is 2. By increasing this
558             level, the number of messages quickly becomes too great to make sense out of.
559             To focus on a particular class, you can add the -class => 'Some::Class'
560             (where 'Some::Class' stands for any of the class names in the Bio::Phylo
561             release) argument, which means that messages originating from that class will
562             have a different (presumably higher) verbosity level than the global level.
563             By adding the -method => 'Fully::Qualified::method_name' (say,
564             'Bio::Phylo::set_name'), you can change the verbosity of a specific method. When
565             evaluating whether or not to transmit a message, the method-specific verbosity
566             level takes precedence over the class-specific level, which takes precedence
567             over the global level.
568              
569             Type : Mutator
570             Title : VERBOSE()
571             Usage : $logger->VERBOSE( -level => $level )
572             Function: Sets/gets verbose level
573             Returns : Verbose level
574             Args : -level => 4 # or lower
575            
576             # optional, or any other class
577             -class => 'Bio::Phylo'
578            
579             # optional, fully qualified method name
580             -method' => 'Bio::Phylo::set_name'
581              
582             =back
583              
584             =head1 SEE ALSO
585              
586             There is a mailing list at L
587             for any user or developer questions and discussions.
588              
589             Also see the manual: L and L.
590              
591             =head1 CITATION
592              
593             If you use Bio::Phylo in published research, please cite it:
594              
595             B, B, B, B
596             and B, 2011. Bio::Phylo - phyloinformatic analysis using Perl.
597             I B<12>:63.
598             L
599              
600             =cut