File Coverage

blib/lib/Paranoid/Log.pm
Criterion Covered Total %
statement 178 217 82.0
branch 62 86 72.0
condition 9 23 39.1
subroutine 28 29 96.5
pod 4 4 100.0
total 281 359 78.2


line stmt bran cond sub pod time code
1             # Paranoid::Log -- Log support for paranoid programs
2             #
3             # $Id: lib/Paranoid/Log.pm, 2.08 2020/12/31 12:10:06 acorliss Exp $
4             #
5             # This software is free software. Similar to Perl, you can redistribute it
6             # and/or modify it under the terms of either:
7             #
8             # a) the GNU General Public License
9             # as published by the
10             # Free Software Foundation ; either version 1
11             # , or any later version
12             # , or
13             # b) the Artistic License 2.0
14             # ,
15             #
16             # subject to the following additional term: No trademark rights to
17             # "Paranoid" have been or are conveyed under any of the above licenses.
18             # However, "Paranoid" may be used fairly to describe this unmodified
19             # software, in good faith, but not as a trademark.
20             #
21             # (c) 2005 - 2020, Arthur Corliss (corliss@digitalmages.com)
22             # (tm) 2008 - 2020, Paranoid Inc. (www.paranoid.com)
23             #
24             #####################################################################
25              
26             #####################################################################
27             #
28             # Environment definitions
29             #
30             #####################################################################
31              
32             package Paranoid::Log;
33              
34 10     10   5938 use 5.008;
  10         31  
35              
36 10     10   50 use strict;
  10         17  
  10         183  
37 10     10   43 use warnings;
  10         17  
  10         273  
38 10     10   43 use vars qw($VERSION @EXPORT @EXPORT_OK %EXPORT_TAGS);
  10         23  
  10         563  
39 10     10   49 use base qw(Exporter);
  10         19  
  10         789  
40 10     10   4319 use Paranoid::Debug qw(:all);
  10         36  
  10         1914  
41 10     10   4774 use Paranoid::Module;
  10         21  
  10         569  
42 10     10   77 use Paranoid::Input;
  10         13  
  10         983  
43              
44             ($VERSION) = ( q$Revision: 2.08 $ =~ /(\d+(?:\.\d+)+)/sm );
45              
46             @EXPORT = qw(
47             PL_DEBUG PL_INFO PL_NOTICE PL_WARN
48             PL_ERR PL_CRIT PL_ALERT PL_EMERG
49             PL_EQ PL_NE PL_GE PL_LE
50             startLogger stopLogger plog plverbosity
51             );
52             @EXPORT_OK = (@EXPORT);
53             %EXPORT_TAGS = ( all => [@EXPORT_OK], );
54              
55 10     10   73 use constant PL_DEBUG => 0;
  10         21  
  10         518  
56 10     10   247 use constant PL_INFO => 1;
  10         26  
  10         478  
57 10     10   60 use constant PL_NOTICE => 2;
  10         20  
  10         388  
58 10     10   59 use constant PL_WARN => 3;
  10         11  
  10         392  
59 10     10   53 use constant PL_ERR => 4;
  10         19  
  10         406  
60 10     10   54 use constant PL_CRIT => 5;
  10         20  
  10         570  
61 10     10   64 use constant PL_ALERT => 6;
  10         19  
  10         546  
62 10     10   70 use constant PL_EMERG => 7;
  10         74  
  10         595  
63              
64 10     10   66 use constant PL_EQ => '=';
  10         12  
  10         453  
65 10     10   57 use constant PL_NE => '!';
  10         19  
  10         485  
66 10     10   59 use constant PL_GE => '+';
  10         13  
  10         490  
67 10     10   60 use constant PL_LE => '-';
  10         18  
  10         440  
68              
69 10     10   55 use constant PL_LREF => 0;
  10         12  
  10         450  
70 10     10   58 use constant PL_AREF => 1;
  10         21  
  10         433  
71 10     10   54 use constant PL_DREF => 2;
  10         20  
  10         19109  
72              
73             our @_scopes = ( PL_EQ, PL_NE, PL_GE, PL_LE );
74             our @_levels = (
75             PL_DEBUG, PL_INFO, PL_NOTICE, PL_WARN,
76             PL_ERR, PL_CRIT, PL_ALERT, PL_EMERG,
77             );
78              
79             #####################################################################
80             #
81             # Module code follows
82             #
83             #####################################################################
84              
85             {
86              
87             my %loaded = (); # module => loaded (boolean)
88             my %msubs = (); # module => log sub ref
89             my @dist; # modules to distribute to by log level
90              
91             # This has consists of the name/array key/value pairs. Each associated
92             # array consists of the following entries:
93             # [ $sref, $level, $scope, \%mopts ].
94             my %loggers = ();
95              
96             sub _loadModule {
97              
98             # Purpose: Loads the requested module if it hasn't been already.
99             # Attempts to first load the module as a name relative to
100             # Paranoid::Log, otherwise by itself.
101             # Returns: True (1) if load was successful,
102             # False (0) if there are any errors
103             # Usage: $rv = _loadModule($module);
104              
105 21     21   49 my $module = shift;
106 21         57 my $mname = $module;
107 21         56 my ( $sref, $aref, $dref, $rv );
108              
109 21         71 pdebug( 'entering w/(%s)', PDLEVEL2, $module );
110 21         60 pIn();
111              
112             # Was module already loaded (or a load attempted)?
113 21 100       52 if ( exists $loaded{$module} ) {
114              
115             # Yep, so return module status
116 10         34 $rv = $loaded{$module};
117              
118             } else {
119              
120             # Nope, so let's try to load it.
121             #
122             # Is the module name taint-safe?
123 11 50       48 if ( detaint( $mname, 'filename' ) ) {
124              
125             # Yep, so try to load relative to Paranoid::Log
126 11 100 33     115 $rv =
    100          
    50          
    100          
127             $mname eq 'Stderr' ? 1
128             : $mname eq 'Stdout' ? 1
129             : $mname eq 'PDebug' ? 1
130             : loadModule( "Paranoid::Log::$mname", '' )
131             && eval "Paranoid::Log::${mname}::init();"
132             && eval "\$sref = \\&Paranoid::Log::${mname}::logMsg;"
133             && eval "\$aref = \\&Paranoid::Log::${mname}::addLogger;"
134             && eval "\$dref = \\&Paranoid::Log::${mname}::delLogger;"
135             ? 1
136             : 0;
137              
138             # If that failed, try to load it directly
139 11 100       226 unless ($rv) {
140 1 50 0     4 $rv =
141             loadModule( $mname, '' )
142             && eval "${mname}::init();"
143             && eval "\$sref = \\&${mname}::logMsg;"
144             && eval "\$aref = \\&${mname}::addLogger;"
145             && eval "\$dref = \\&${mname}::delLogger;"
146             ? 1
147             : 0;
148             }
149              
150             # Cache & report the results
151 11         100 $loaded{$module} = $rv;
152 11         93 $msubs{$module} = [ $sref, $aref, $dref ];
153 11 100       44 if ($rv) {
154 10         174 pdebug( 'successfully loaded log module for %s',
155             PDLEVEL3, $module );
156             } else {
157 1         3 Paranoid::ERROR =
158             pdebug( 'failed to load log module for %s',
159             PDLEVEL1, $module );
160             }
161              
162             } else {
163              
164             # Module name failed detainting -- report
165 0         0 Paranoid::ERROR =
166             pdebug( 'failed to detaint module name', PDLEVEL1 );
167 0         0 $rv = 0;
168             }
169             }
170              
171 21         96 pOut();
172 21         91 pdebug( 'leaving w/rv: %s', PDLEVEL2, $rv );
173              
174 21         133 return $rv;
175             }
176              
177             sub _updateDist {
178              
179             # Purpose: Registers logging handles at the appropriate log levels
180             # Returns: Boolean
181             # Usage: $rv = _updateDist();
182              
183 18     18   86 my ( $logger, $level, $scope );
184              
185             # Purge @dist and reinitialize
186 18         106 foreach ( PL_DEBUG .. PL_EMERG ) { $dist[$_] = [] }
  144         267  
187              
188             # Set up the distribution list
189 18         68 foreach $logger ( keys %loggers ) {
190 18         112 ( $level, $scope ) = @{ $loggers{$logger} }{qw(severity scope)};
  18         66  
191 18 100       86 if ( $scope eq PL_EQ ) {
    100          
    50          
192 10         17 push @{ $dist[$level] }, $logger;
  10         50  
193             } elsif ( $scope eq PL_GE ) {
194 5         19 foreach ( $level .. PL_EMERG ) {
195 31         37 push @{ $dist[$_] }, $logger;
  31         64  
196             }
197             } elsif ( $scope eq PL_LE ) {
198 0         0 foreach ( PL_DEBUG .. $level ) {
199 0         0 push @{ $dist[$_] }, $logger;
  0         0  
200             }
201             } else {
202 3         8 foreach ( PL_DEBUG .. PL_EMERG ) {
203 24 100       46 push @{ $dist[$_] }, $logger if $level != $_;
  21         36  
204             }
205             }
206             }
207              
208             # Report distribution list
209 18         46 foreach $level ( PL_DEBUG .. PL_EMERG ) {
210 144         206 pdebug( '%s: %s', PDLEVEL3, $level, @{ $dist[$level] } );
  144         301  
211             }
212              
213 18         82 return 1;
214             }
215              
216             sub startLogger {
217              
218             # Purpose: Adds a named logger to our loggers hash.
219             # Returns: True (1) if successful,
220             # False (0) if there are any errors
221             # Usage: $rv = startLogger($name, $mech, $level, $scope, { %mopts });
222              
223 21     21 1 301 my $name = shift;
224 21         84 my $mech = shift;
225 21         69 my $level = shift;
226 21         51 my $scope = shift;
227 21         33 my $mopts = shift;
228 21         42 my $rv = 1;
229              
230 21         95 pdebug( 'entering w/(%s)(%s)(%s)(%s)(%s)',
231             PDLEVEL3, $name, $mech, $level, $scope, $mopts );
232 21         72 pIn();
233              
234             # Set defaults for optional arguments that were left undefined
235 21 100       59 $level = PL_NOTICE unless defined $level;
236 21 100       60 $scope = PL_GE unless defined $scope;
237              
238             # This is totally unnecessary, but we'll set PDebug to reflect
239             # how it actually operations in case anyone is looking at the
240             # distribution map
241 21 100       57 $level = PL_DEBUG if $mech eq 'PDebug';
242              
243             # Make sure this is a valid named logger
244 21 50 33     164 unless ( defined $name and length $name ) {
245 0         0 Paranoid::ERROR =
246             pdebug( 'invalid log name specified: %s', PDLEVEL1, $name );
247 0         0 $rv = 0;
248             }
249              
250             # Validate log level
251 21 50       88 unless ( scalar grep { $_ == $level } @_levels ) {
  168         360  
252 0         0 Paranoid::ERROR =
253             pdebug( 'invalid log level specified: %s', PDLEVEL1, $level );
254 0         0 $rv = 0;
255             }
256              
257             # Validate scope
258 21 50       57 unless ( scalar grep { $_ eq $scope } @_scopes ) {
  84         164  
259 0         0 Paranoid::ERROR =
260             pdebug( 'invalid log scope specified: %s', PDLEVEL1, $scope );
261 0         0 $rv = 0;
262             }
263              
264             # Make sure the module can be loaded if the log level was valid
265 21 50       108 $rv = _loadModule($mech) if $rv;
266              
267             # Make sure the log entry is uniqe
268 21 100       74 if ($rv) {
269 20 50       80 if ( exists $loggers{$name} ) {
270 0         0 Paranoid::ERROR = pdebug( 'a logger for %s already exists',
271             PDLEVEL1, $name );
272 0         0 $rv = 0;
273             } else {
274 20 100 66     208 $mopts = {}
275             unless defined $mopts and ref $mopts eq 'HASH';
276 20         281 $loggers{$name} = {
277             name => $name,
278             mechanism => $mech,
279             severity => $level,
280             scope => $scope,
281             options => {%$mopts} };
282             $rv =
283             $mech eq 'Stderr' ? 1
284             : $mech eq 'Stdout' ? 1
285             : $mech eq 'PDebug' ? 1
286 20 100       121 : &{ $msubs{$mech}[PL_AREF] }( %{ $loggers{$name} } );
  18 50       105  
  18 100       63  
287 20 100       183 if ($rv) {
288 14         87 _updateDist();
289             } else {
290 6         66 delete $loggers{$name};
291             }
292             }
293             }
294              
295 21         86 pOut();
296 21         102 pdebug( 'leaving w/rv: %s', PDLEVEL3, $rv );
297              
298 21         274 return $rv;
299             }
300              
301             sub stopLogger {
302              
303             # Purpose: Deletes a named logger from the hash.
304             # Returns: True (1)
305             # Usage: _delLogger($name);
306              
307 10     10 1 1485 my $name = shift;
308 10         33 my $rv = 1;
309              
310 10         46 pdebug( 'deleting %s logger', PDLEVEL3, $name );
311 10 100       89 if ( exists $loggers{$name} ) {
312 4 50 66     62 unless ( $loggers{$name}{mechanism} eq 'Stderr'
      66        
313             or $loggers{$name}{mechanism} eq 'Stdout'
314             or $loggers{$name}{mechanism} eq 'PDebug' ) {
315             $rv =
316 3         24 &{ $msubs{ $loggers{$name}{mechanism} }[PL_DREF] }(
317 3         7 %{ $loggers{$name} } );
  3         24  
318             }
319 4 50       24 if ($rv) {
320 4         29 delete $loggers{$name};
321 4         19 _updateDist();
322             }
323             }
324              
325 10         55 return $rv;
326             }
327              
328             sub plog {
329              
330             # Purpose: Logs the message to all facilities registered at that level
331             # Returns: True (1) if the message was succesfully logged,
332             # False (0) if there are any errors
333             # Usage: $rv = plog($severity, $message);
334             # Usage: $rv = plog($severity, $message, @pdebugvals);
335              
336 403     403 1 187700 my $level = shift;
337 403         773 my $message = shift;
338 403         775 my @margs = @_;
339 403         603 my $rv = 1;
340 403         1448 my %record = (
341             severity => $level,
342             message => $message,
343             );
344 403         691 my ( $logger, $sref, $plevel );
345              
346 403         1235 pdebug( 'entering w/(%s)(%s)', PDLEVEL1, $level, $message );
347 403         1071 pIn();
348              
349             # Validate level and message
350             $rv = 0
351             unless defined $message
352 403 50 50     1374 and scalar grep { $_ == $level } @_levels;
  3224         6207  
353              
354 403 50       793 if ($rv) {
355              
356             # Trim leading/trailing whitespace and line terminators
357 403         1087 $message =~ s/^[\s\r\n]+//s;
358 403         1723 $message =~ s/[\s\r\n]+$//s;
359              
360             # First, if PDebug was enabled, we'll preprocess messages through
361             # pdebug. *Every* message gets passed since pdebug has its own
362             # mechanism to decide what to display
363 403 100       1068 if ( grep { $loggers{$_}{mechanism} eq 'PDebug' } keys %loggers )
  526         1727  
364             {
365              
366             # Paranoid::Debug uses an escalating scale of verbosity while
367             # this module uses an escalating scale of severity. We can
368             # equate them in an inverse relationship, but we'll also need
369             # to increment the output value since pdebug equates 0 as
370             # disabled.
371             #
372             # Finally, we'll also make it a negative number to
373             # signal pdebug to dive deeper into the call stack to find the
374             # true originator of the message. Otherwise, it would report
375             # plog as the originator, which is less than helpful.
376 30         53 $plevel = ( ( $level ^ 7 ) + 1 ) * -1;
377              
378             # Send it to pdebug, but save the output
379 30         62 $message = pdebug( $message, $plevel, @margs );
380              
381             # Substitute the processed output if we had any substitution
382             # values passed at all
383 30 100       61 $record{message} = $message if scalar @margs;
384              
385             }
386              
387             # Iterate over the @dist level
388 403 100       970 if ( defined $dist[$level] ) {
389              
390             # Iterate over each logger
391 397         531 foreach $logger ( @{ $dist[$level] } ) {
  397         1018  
392 421 100       969 next if $loggers{$logger}{mechanism} eq 'PDebug';
393              
394 391 100       1083 if ( $loggers{$logger}{mechanism} eq 'Stderr' ) {
    50          
395              
396             # Special handling for STDERR
397 1         4 $rv = pderror($message);
398              
399             } elsif ( $loggers{$logger}{mechanism} eq 'Stdout' ) {
400              
401             # Special handling for STDOUT
402 0         0 $rv = print STDOUT "$message\n";
403              
404             } else {
405              
406             # Get the sub ref for the logger
407             $sref =
408 390         848 $msubs{ $loggers{$logger}{mechanism} }[PL_LREF];
409             $rv =
410             defined $sref
411 390 50       733 ? &$sref( %{ $loggers{$logger} }, %record )
  390         1827  
412             : 0;
413             }
414             }
415             }
416              
417             } else {
418              
419             # Report error
420 0         0 Paranoid::ERROR = pdebug( 'invalid log level(%s) or message(%s)',
421             PDLEVEL1, $level, $message );
422 0         0 $rv = 0;
423             }
424              
425 403         1029 pOut();
426 403         990 pdebug( 'leaving w / rv : %s', PDLEVEL1, $rv );
427              
428 403         1589 return $rv;
429             }
430             }
431              
432             sub plverbosity {
433              
434             # Purpose: Sets Stdout/Stderr verbosity according to passed leve.
435             # Supports levels 1 - 3, with 4 being the most verbose
436             # Returns: Boolean
437             # Usage: $rv = plverbosity($level);
438              
439 0     0 1   my $level = shift;
440 0           my $max = 3;
441 0           my $outidx = PL_NOTICE;
442 0           my $erridx = PL_CRIT;
443 0           my $rv = 1;
444              
445 0           pdebug( 'entering w/(%s)', PDLEVEL3, $level );
446 0           pIn();
447              
448             # Make sure a positive integer was passed
449 0 0         $rv = 0 unless $level > -1;
450              
451             # Cap $level
452 0 0         $level = $max if $level > $max;
453              
454             # First, stop any current logging
455 0           foreach ( 0 .. 7 ) {
456 0 0         stopLogger( $_ < 3 ? "Stdout$_" : "Stderr$_" );
457             }
458              
459             # Always enable PL_EMERG/PL_ALERT
460 0 0         if ($level) {
461 0           startLogger( "Stderr6", 'Stderr', PL_ALERT, PL_EQ );
462 0           startLogger( "Stderr7", 'Stderr', PL_EMERG, PL_EQ );
463             }
464              
465             # Enable what's been asked
466 0   0       while ( $rv and $level ) {
467              
468             # Start the levels
469 0           startLogger( "Stdout$outidx", 'Stdout', $outidx, PL_EQ );
470 0           startLogger( "Stderr$erridx", 'Stderr', $erridx, PL_EQ );
471              
472             # Decrement the counters
473 0           $outidx--;
474 0           $erridx--;
475 0           $level--;
476             }
477              
478 0           pOut();
479 0           pdebug( 'leaving w/rv: %s', PDLEVEL3, $rv );
480              
481 0           return $rv;
482             }
483              
484             1;
485              
486             __END__