File Coverage

blib/lib/Paranoid/Log.pm
Criterion Covered Total %
statement 172 209 82.3
branch 62 86 72.0
condition 9 23 39.1
subroutine 28 29 96.5
pod 4 4 100.0
total 275 351 78.3


line stmt bran cond sub pod time code
1             # Paranoid::Log -- Log support for paranoid programs
2             #
3             # $Id: lib/Paranoid/Log.pm, 2.10 2022/03/08 00:01:04 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   7225 use 5.008;
  16         47  
35              
36 16     16   78 use strict;
  16         23  
  16         289  
37 16     16   61 use warnings;
  16         33  
  16         362  
38 16     16   63 use vars qw($VERSION @EXPORT @EXPORT_OK %EXPORT_TAGS);
  16         24  
  16         709  
39 16     16   65 use base qw(Exporter);
  16         17  
  16         924  
40 16     16   5791 use Paranoid::Debug qw(:all);
  16         26  
  16         2827  
41 16     16   5554 use Paranoid::Module;
  16         32  
  16         751  
42 16     16   92 use Paranoid::Input;
  16         25  
  16         1419  
43              
44             ($VERSION) = ( q$Revision: 2.10 $ =~ /(\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   90 use constant PL_DEBUG => 0;
  16         24  
  16         695  
56 16     16   73 use constant PL_INFO => 1;
  16         24  
  16         550  
57 16     16   113 use constant PL_NOTICE => 2;
  16         25  
  16         582  
58 16     16   79 use constant PL_WARN => 3;
  16         25  
  16         653  
59 16     16   124 use constant PL_ERR => 4;
  16         33  
  16         756  
60 16     16   94 use constant PL_CRIT => 5;
  16         65  
  16         623  
61 16     16   76 use constant PL_ALERT => 6;
  16         37  
  16         657  
62 16     16   94 use constant PL_EMERG => 7;
  16         19  
  16         688  
63              
64 16     16   68 use constant PL_EQ => '=';
  16         31  
  16         716  
65 16     16   89 use constant PL_NE => '!';
  16         18  
  16         651  
66 16     16   86 use constant PL_GE => '+';
  16         16  
  16         596  
67 16     16   75 use constant PL_LE => '-';
  16         30  
  16         737  
68              
69 16     16   79 use constant PL_LREF => 0;
  16         75  
  16         703  
70 16     16   79 use constant PL_AREF => 1;
  16         25  
  16         766  
71 16     16   81 use constant PL_DREF => 2;
  16         36  
  16         24975  
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   67 my $module = shift;
106 35         64 my $mname = $module;
107 35         66 my ( $sref, $aref, $dref, $rv );
108              
109 35         128 subPreamble( PDLEVEL2, '$', $module );
110              
111             # Was module already loaded (or a load attempted)?
112 35 100       96 if ( exists $loaded{$module} ) {
113              
114             # Yep, so return module status
115 18         71 $rv = $loaded{$module};
116              
117             } else {
118              
119             # Nope, so let's try to load it.
120             #
121             # Is the module name taint-safe?
122 17 50       183 if ( detaint( $mname, 'filename' ) ) {
123              
124             # Yep, so try to load relative to Paranoid::Log
125 17 100 33     142 $rv =
    100          
    50          
    100          
126             $mname eq 'Stderr' ? 1
127             : $mname eq 'Stdout' ? 1
128             : $mname eq 'PDebug' ? 1
129             : loadModule( "Paranoid::Log::$mname", '' )
130             && eval "Paranoid::Log::${mname}::init();"
131             && eval "\$sref = \\&Paranoid::Log::${mname}::logMsg;"
132             && eval "\$aref = \\&Paranoid::Log::${mname}::addLogger;"
133             && eval "\$dref = \\&Paranoid::Log::${mname}::delLogger;"
134             ? 1
135             : 0;
136              
137             # If that failed, try to load it directly
138 17 100       253 unless ($rv) {
139 1 50 0     3 $rv =
140             loadModule( $mname, '' )
141             && eval "${mname}::init();"
142             && eval "\$sref = \\&${mname}::logMsg;"
143             && eval "\$aref = \\&${mname}::addLogger;"
144             && eval "\$dref = \\&${mname}::delLogger;"
145             ? 1
146             : 0;
147             }
148              
149             # Cache & report the results
150 17         131 $loaded{$module} = $rv;
151 17         96 $msubs{$module} = [ $sref, $aref, $dref ];
152 17 100       95 if ($rv) {
153 16         214 pdebug( 'successfully loaded log module for %s',
154             PDLEVEL3, $module );
155             } else {
156 1         4 Paranoid::ERROR =
157             pdebug( 'failed to load log module for %s',
158             PDLEVEL1, $module );
159             }
160              
161             } else {
162              
163             # Module name failed detainting -- report
164 0         0 Paranoid::ERROR =
165             pdebug( 'failed to detaint module name', PDLEVEL1 );
166 0         0 $rv = 0;
167             }
168             }
169              
170 35         223 subPostamble( PDLEVEL2, '$', $rv );
171              
172 35         166 return $rv;
173             }
174              
175             sub _updateDist {
176              
177             # Purpose: Registers logging handles at the appropriate log levels
178             # Returns: Boolean
179             # Usage: $rv = _updateDist();
180              
181 28     28   79 my ( $logger, $level, $scope );
182              
183             # Purge @dist and reinitialize
184 28         142 foreach ( PL_DEBUG .. PL_EMERG ) { $dist[$_] = [] }
  224         378  
185              
186             # Set up the distribution list
187 28         109 foreach $logger ( keys %loggers ) {
188 26         55 ( $level, $scope ) = @{ $loggers{$logger} }{qw(severity scope)};
  26         80  
189 26 100       103 if ( $scope eq PL_EQ ) {
    100          
    50          
190 16         29 push @{ $dist[$level] }, $logger;
  16         56  
191             } elsif ( $scope eq PL_GE ) {
192 7         19 foreach ( $level .. PL_EMERG ) {
193 41         42 push @{ $dist[$_] }, $logger;
  41         73  
194             }
195             } elsif ( $scope eq PL_LE ) {
196 0         0 foreach ( PL_DEBUG .. $level ) {
197 0         0 push @{ $dist[$_] }, $logger;
  0         0  
198             }
199             } else {
200 3         5 foreach ( PL_DEBUG .. PL_EMERG ) {
201 24 100       31 push @{ $dist[$_] }, $logger if $level != $_;
  21         33  
202             }
203             }
204             }
205              
206             # Report distribution list
207 28         68 foreach $level ( PL_DEBUG .. PL_EMERG ) {
208 224         279 pdebug( '%s: %s', PDLEVEL3, $level, @{ $dist[$level] } );
  224         412  
209             }
210              
211 28         77 return 1;
212             }
213              
214             sub startLogger {
215              
216             # Purpose: Adds a named logger to our loggers hash.
217             # Returns: True (1) if successful,
218             # False (0) if there are any errors
219             # Usage: $rv = startLogger($name, $mech, $level, $scope, { %mopts });
220              
221 35     35 1 812 my $name = shift;
222 35         69 my $mech = shift;
223 35         66 my $level = shift;
224 35         67 my $scope = shift;
225 35         66 my $mopts = shift;
226 35         64 my $rv = 1;
227              
228 35         135 subPreamble( PDLEVEL3, '$$$$\%', $name, $mech, $level, $scope,
229             $mopts );
230              
231             # Set defaults for optional arguments that were left undefined
232 35 100       100 $level = PL_NOTICE unless defined $level;
233 35 100       105 $scope = PL_GE unless defined $scope;
234              
235             # This is totally unnecessary, but we'll set PDebug to reflect
236             # how it actually operations in case anyone is looking at the
237             # distribution map
238 35 100       99 $level = PL_DEBUG if $mech eq 'PDebug';
239              
240             # Make sure this is a valid named logger
241 35 50 33     227 unless ( defined $name and length $name ) {
242 0         0 Paranoid::ERROR =
243             pdebug( 'invalid log name specified: %s', PDLEVEL1, $name );
244 0         0 $rv = 0;
245             }
246              
247             # Validate log level
248 35 50       114 unless ( scalar grep { $_ == $level } @_levels ) {
  280         449  
249 0         0 Paranoid::ERROR =
250             pdebug( 'invalid log level specified: %s', PDLEVEL1, $level );
251 0         0 $rv = 0;
252             }
253              
254             # Validate scope
255 35 50       91 unless ( scalar grep { $_ eq $scope } @_scopes ) {
  140         240  
256 0         0 Paranoid::ERROR =
257             pdebug( 'invalid log scope specified: %s', PDLEVEL1, $scope );
258 0         0 $rv = 0;
259             }
260              
261             # Make sure the module can be loaded if the log level was valid
262 35 50       146 $rv = _loadModule($mech) if $rv;
263              
264             # Make sure the log entry is unique
265 35 100       270 if ($rv) {
266 34 50       118 if ( exists $loggers{$name} ) {
267 0         0 Paranoid::ERROR = pdebug( 'a logger for %s already exists',
268             PDLEVEL1, $name );
269 0         0 $rv = 0;
270             } else {
271 34 100 66     276 $mopts = {}
272             unless defined $mopts and ref $mopts eq 'HASH';
273 34         407 $loggers{$name} = {
274             name => $name,
275             mechanism => $mech,
276             severity => $level,
277             scope => $scope,
278             options => {%$mopts} };
279             $rv =
280             $mech eq 'Stderr' ? 1
281             : $mech eq 'Stdout' ? 1
282             : $mech eq 'PDebug' ? 1
283 34 100       189 : &{ $msubs{$mech}[PL_AREF] }( %{ $loggers{$name} } );
  32 50       143  
  32 100       114  
284 34 100       104 if ($rv) {
285 22         103 _updateDist();
286             } else {
287 12         48 delete $loggers{$name};
288             }
289             }
290             }
291              
292 35         136 subPostamble( PDLEVEL3, '$', $rv );
293              
294 35         389 return $rv;
295             }
296              
297             sub stopLogger {
298              
299             # Purpose: Deletes a named logger from the hash.
300             # Returns: True (1)
301             # Usage: _delLogger($name);
302              
303 18     18 1 1727 my $name = shift;
304 18         54 my $rv = 1;
305              
306 18         119 pdebug( 'deleting %s logger', PDLEVEL3, $name );
307 18 100       105 if ( exists $loggers{$name} ) {
308 6 50 66     56 unless ( $loggers{$name}{mechanism} eq 'Stderr'
      66        
309             or $loggers{$name}{mechanism} eq 'Stdout'
310             or $loggers{$name}{mechanism} eq 'PDebug' ) {
311             $rv =
312 5         44 &{ $msubs{ $loggers{$name}{mechanism} }[PL_DREF] }(
313 5         12 %{ $loggers{$name} } );
  5         30  
314             }
315 6 50       21 if ($rv) {
316 6         39 delete $loggers{$name};
317 6         42 _updateDist();
318             }
319             }
320              
321 18         130 return $rv;
322             }
323              
324             sub plog {
325              
326             # Purpose: Logs the message to all facilities registered at that level
327             # Returns: True (1) if the message was succesfully logged,
328             # False (0) if there are any errors
329             # Usage: $rv = plog($severity, $message);
330             # Usage: $rv = plog($severity, $message, @pdebugvals);
331              
332 665     665 1 185123 my $level = shift;
333 665         1249 my $message = shift;
334 665         1346 my @margs = @_;
335 665         983 my $rv = 1;
336 665         2587 my %record = (
337             severity => $level,
338             message => $message,
339             );
340 665         1168 my ( $logger, $sref, $plevel );
341              
342 665         2112 subPreamble( PDLEVEL1, '$$;@', $level, $message, @margs );
343              
344             # Validate level and message
345             $rv = 0
346             unless defined $message
347 665 50 50     2510 and scalar grep { $_ == $level } @_levels;
  5320         8436  
348              
349 665 50       1234 if ($rv) {
350              
351             # Trim leading/trailing whitespace and line terminators
352 665         1724 $message =~ s/^[\s\r\n]+//s;
353 665         2672 $message =~ s/[\s\r\n]+$//s;
354              
355             # First, if PDebug was enabled, we'll preprocess messages through
356             # pdebug. *Every* message gets passed since pdebug has its own
357             # mechanism to decide what to display
358 665 100       1866 if ( grep { $loggers{$_}{mechanism} eq 'PDebug' } keys %loggers )
  782         2772  
359             {
360              
361             # Paranoid::Debug uses an escalating scale of verbosity while
362             # this module uses an escalating scale of severity. We can
363             # equate them in an inverse relationship, but we'll also need
364             # to increment the output value since pdebug equates 0 as
365             # disabled.
366             #
367             # Finally, we'll also make it a negative number to
368             # signal pdebug to dive deeper into the call stack to find the
369             # true originator of the message. Otherwise, it would report
370             # plog as the originator, which is less than helpful.
371 30         48 $plevel = ( ( $level ^ 7 ) + 1 ) * -1;
372              
373             # Send it to pdebug, but save the output
374 30         48 $message = pdebug( $message, $plevel, @margs );
375              
376             # Substitute the processed output if we had any substitution
377             # values passed at all
378 30 100       53 $record{message} = $message if scalar @margs;
379              
380             }
381              
382             # Iterate over the @dist level
383 665 100       1479 if ( defined $dist[$level] ) {
384              
385             # Iterate over each logger
386 653         892 foreach $logger ( @{ $dist[$level] } ) {
  653         1817  
387 677 100       1633 next if $loggers{$logger}{mechanism} eq 'PDebug';
388              
389 647 100       1709 if ( $loggers{$logger}{mechanism} eq 'Stderr' ) {
    50          
390              
391             # Special handling for STDERR
392 1         4 $rv = pderror($message);
393              
394             } elsif ( $loggers{$logger}{mechanism} eq 'Stdout' ) {
395              
396             # Special handling for STDOUT
397 0         0 $rv = print STDOUT "$message\n";
398              
399             } else {
400              
401             # Get the sub ref for the logger
402             $sref =
403 646         1466 $msubs{ $loggers{$logger}{mechanism} }[PL_LREF];
404             $rv =
405             defined $sref
406 646 50       1115 ? &$sref( %{ $loggers{$logger} }, %record )
  646         3465  
407             : 0;
408             }
409             }
410             }
411              
412             } else {
413              
414             # Report error
415 0         0 Paranoid::ERROR = pdebug( 'invalid log level(%s) or message(%s)',
416             PDLEVEL1, $level, $message );
417 0         0 $rv = 0;
418             }
419              
420 665         1734 subPostamble( PDLEVEL1, '$', $rv );
421              
422 665         2898 return $rv;
423             }
424             }
425              
426             sub plverbosity {
427              
428             # Purpose: Sets Stdout/Stderr verbosity according to passed level.
429             # Supports levels 1 - 3, with 4 being the most verbose
430             # Returns: Boolean
431             # Usage: $rv = plverbosity($level);
432              
433 0     0 1   my $level = shift;
434 0           my $max = 3;
435 0           my $outidx = PL_NOTICE;
436 0           my $erridx = PL_CRIT;
437 0           my $rv = 1;
438              
439 0           subPreamble( PDLEVEL3, '$', $level );
440              
441             # Make sure a positive integer was passed
442 0 0         $rv = 0 unless $level > -1;
443              
444             # Cap $level
445 0 0         $level = $max if $level > $max;
446              
447             # First, stop any current logging
448 0           foreach ( 0 .. 7 ) {
449 0 0         stopLogger( $_ < 3 ? "Stdout$_" : "Stderr$_" );
450             }
451              
452             # Always enable PL_EMERG/PL_ALERT
453 0 0         if ($level) {
454 0           startLogger( "Stderr6", 'Stderr', PL_ALERT, PL_EQ );
455 0           startLogger( "Stderr7", 'Stderr', PL_EMERG, PL_EQ );
456             }
457              
458             # Enable what's been asked
459 0   0       while ( $rv and $level ) {
460              
461             # Start the levels
462 0           startLogger( "Stdout$outidx", 'Stdout', $outidx, PL_EQ );
463 0           startLogger( "Stderr$erridx", 'Stderr', $erridx, PL_EQ );
464              
465             # Decrement the counters
466 0           $outidx--;
467 0           $erridx--;
468 0           $level--;
469             }
470              
471 0           subPostamble( PDLEVEL3, '$', $rv );
472              
473 0           return $rv;
474             }
475              
476             1;
477              
478             __END__