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.09 2021/12/28 15:46:49 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 16     16   9001 use 5.008;
  16         57  
35              
36 16     16   82 use strict;
  16         30  
  16         299  
37 16     16   78 use warnings;
  16         61  
  16         669  
38 16     16   105 use vars qw($VERSION @EXPORT @EXPORT_OK %EXPORT_TAGS);
  16         32  
  16         960  
39 16     16   82 use base qw(Exporter);
  16         31  
  16         1260  
40 16     16   6923 use Paranoid::Debug qw(:all);
  16         50  
  16         3161  
41 16     16   7134 use Paranoid::Module;
  16         39  
  16         915  
42 16     16   118 use Paranoid::Input;
  16         38  
  16         1587  
43              
44             ($VERSION) = ( q$Revision: 2.09 $ =~ /(\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 16     16   111 use constant PL_DEBUG => 0;
  16         21  
  16         909  
56 16     16   802 use constant PL_INFO => 1;
  16         61  
  16         917  
57 16     16   104 use constant PL_NOTICE => 2;
  16         23  
  16         710  
58 16     16   81 use constant PL_WARN => 3;
  16         32  
  16         666  
59 16     16   97 use constant PL_ERR => 4;
  16         30  
  16         734  
60 16     16   102 use constant PL_CRIT => 5;
  16         26  
  16         773  
61 16     16   149 use constant PL_ALERT => 6;
  16         47  
  16         900  
62 16     16   98 use constant PL_EMERG => 7;
  16         31  
  16         882  
63              
64 16     16   152 use constant PL_EQ => '=';
  16         48  
  16         778  
65 16     16   93 use constant PL_NE => '!';
  16         24  
  16         844  
66 16     16   121 use constant PL_GE => '+';
  16         26  
  16         885  
67 16     16   108 use constant PL_LE => '-';
  16         38  
  16         724  
68              
69 16     16   91 use constant PL_LREF => 0;
  16         24  
  16         799  
70 16     16   99 use constant PL_AREF => 1;
  16         31  
  16         732  
71 16     16   91 use constant PL_DREF => 2;
  16         33  
  16         31554  
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 35     35   84 my $module = shift;
106 35         94 my $mname = $module;
107 35         69 my ( $sref, $aref, $dref, $rv );
108              
109 35         319 pdebug( 'entering w/(%s)', PDLEVEL2, $module );
110 35         109 pIn();
111              
112             # Was module already loaded (or a load attempted)?
113 35 100       110 if ( exists $loaded{$module} ) {
114              
115             # Yep, so return module status
116 18         54 $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 17 50       93 if ( detaint( $mname, 'filename' ) ) {
124              
125             # Yep, so try to load relative to Paranoid::Log
126 17 100 33     175 $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 17 100       226 unless ($rv) {
140 1 50 0     3 $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 17         194 $loaded{$module} = $rv;
152 17         101 $msubs{$module} = [ $sref, $aref, $dref ];
153 17 100       124 if ($rv) {
154 16         259 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 35         268 pOut();
172 35         160 pdebug( 'leaving w/rv: %s', PDLEVEL2, $rv );
173              
174 35         175 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 28     28   78 my ( $logger, $level, $scope );
184              
185             # Purge @dist and reinitialize
186 28         212 foreach ( PL_DEBUG .. PL_EMERG ) { $dist[$_] = [] }
  224         456  
187              
188             # Set up the distribution list
189 28         118 foreach $logger ( keys %loggers ) {
190 26         134 ( $level, $scope ) = @{ $loggers{$logger} }{qw(severity scope)};
  26         124  
191 26 100       115 if ( $scope eq PL_EQ ) {
    100          
    50          
192 16         31 push @{ $dist[$level] }, $logger;
  16         71  
193             } elsif ( $scope eq PL_GE ) {
194 7         24 foreach ( $level .. PL_EMERG ) {
195 41         48 push @{ $dist[$_] }, $logger;
  41         86  
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       38 push @{ $dist[$_] }, $logger if $level != $_;
  21         42  
204             }
205             }
206             }
207              
208             # Report distribution list
209 28         70 foreach $level ( PL_DEBUG .. PL_EMERG ) {
210 224         326 pdebug( '%s: %s', PDLEVEL3, $level, @{ $dist[$level] } );
  224         501  
211             }
212              
213 28         107 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 35     35 1 785 my $name = shift;
224 35         1216 my $mech = shift;
225 35         110 my $level = shift;
226 35         81 my $scope = shift;
227 35         71 my $mopts = shift;
228 35         68 my $rv = 1;
229              
230 35         156 pdebug( 'entering w/(%s)(%s)(%s)(%s)(%s)',
231             PDLEVEL3, $name, $mech, $level, $scope, $mopts );
232 35         123 pIn();
233              
234             # Set defaults for optional arguments that were left undefined
235 35 100       101 $level = PL_NOTICE unless defined $level;
236 35 100       100 $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 35 100       105 $level = PL_DEBUG if $mech eq 'PDebug';
242              
243             # Make sure this is a valid named logger
244 35 50 33     277 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 35 50       135 unless ( scalar grep { $_ == $level } @_levels ) {
  280         542  
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 35 50       93 unless ( scalar grep { $_ eq $scope } @_scopes ) {
  140         311  
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 35 50       138 $rv = _loadModule($mech) if $rv;
266              
267             # Make sure the log entry is unique
268 35 100       390 if ($rv) {
269 34 50       198 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 34 100 66     412 $mopts = {}
275             unless defined $mopts and ref $mopts eq 'HASH';
276 34         479 $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 34 100       205 : &{ $msubs{$mech}[PL_AREF] }( %{ $loggers{$name} } );
  32 50       175  
  32 100       149  
287 34 100       163 if ($rv) {
288 22         111 _updateDist();
289             } else {
290 12         102 delete $loggers{$name};
291             }
292             }
293             }
294              
295 35         147 pOut();
296 35         108 pdebug( 'leaving w/rv: %s', PDLEVEL3, $rv );
297              
298 35         429 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 18     18 1 2055 my $name = shift;
308 18         49 my $rv = 1;
309              
310 18         85 pdebug( 'deleting %s logger', PDLEVEL3, $name );
311 18 100       113 if ( exists $loggers{$name} ) {
312 6 50 66     79 unless ( $loggers{$name}{mechanism} eq 'Stderr'
      66        
313             or $loggers{$name}{mechanism} eq 'Stdout'
314             or $loggers{$name}{mechanism} eq 'PDebug' ) {
315             $rv =
316 5         44 &{ $msubs{ $loggers{$name}{mechanism} }[PL_DREF] }(
317 5         11 %{ $loggers{$name} } );
  5         34  
318             }
319 6 50       27 if ($rv) {
320 6         52 delete $loggers{$name};
321 6         30 _updateDist();
322             }
323             }
324              
325 18         111 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 665     665 1 206583 my $level = shift;
337 665         1258 my $message = shift;
338 665         1809 my @margs = @_;
339 665         1077 my $rv = 1;
340 665         2749 my %record = (
341             severity => $level,
342             message => $message,
343             );
344 665         1174 my ( $logger, $sref, $plevel );
345              
346 665         2201 pdebug( 'entering w/(%s)(%s)', PDLEVEL1, $level, $message );
347 665         1926 pIn();
348              
349             # Validate level and message
350             $rv = 0
351             unless defined $message
352 665 50 50     2568 and scalar grep { $_ == $level } @_levels;
  5320         10614  
353              
354 665 50       1432 if ($rv) {
355              
356             # Trim leading/trailing whitespace and line terminators
357 665         2048 $message =~ s/^[\s\r\n]+//s;
358 665         3081 $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 665 100       2081 if ( grep { $loggers{$_}{mechanism} eq 'PDebug' } keys %loggers )
  782         2803  
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         63 $plevel = ( ( $level ^ 7 ) + 1 ) * -1;
377              
378             # Send it to pdebug, but save the output
379 30         63 $message = pdebug( $message, $plevel, @margs );
380              
381             # Substitute the processed output if we had any substitution
382             # values passed at all
383 30 100       72 $record{message} = $message if scalar @margs;
384              
385             }
386              
387             # Iterate over the @dist level
388 665 100       1650 if ( defined $dist[$level] ) {
389              
390             # Iterate over each logger
391 653         1007 foreach $logger ( @{ $dist[$level] } ) {
  653         1945  
392 677 100       1582 next if $loggers{$logger}{mechanism} eq 'PDebug';
393              
394 647 100       1903 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 646         1578 $msubs{ $loggers{$logger}{mechanism} }[PL_LREF];
409             $rv =
410             defined $sref
411 646 50       1309 ? &$sref( %{ $loggers{$logger} }, %record )
  646         3341  
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 665         1783 pOut();
426 665         1614 pdebug( 'leaving w / rv : %s', PDLEVEL1, $rv );
427              
428 665         3170 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__