File Coverage

blib/lib/Config/Manager/Report.pm
Criterion Covered Total %
statement 203 328 61.8
branch 50 136 36.7
condition 17 61 27.8
subroutine 17 26 65.3
pod 0 14 0.0
total 287 565 50.8


line stmt bran cond sub pod time code
1              
2             ###############################################################################
3             ## ##
4             ## Copyright (c) 2003 by Steffen Beyer & Gerhard Albers. ##
5             ## All rights reserved. ##
6             ## ##
7             ## This package is free software; you can redistribute it ##
8             ## and/or modify it under the same terms as Perl itself. ##
9             ## ##
10             ###############################################################################
11              
12             package Config::Manager::Report;
13              
14 2     2   970 use strict;
  2         4  
  2         116  
15 2         736 use vars qw( @ISA @EXPORT @ALL @AUX @EXPORT_OK %EXPORT_TAGS $VERSION %SIG
16             $SHOW_ALL $USE_LEADIN $STACKTRACE
17             $LEVEL_TRACE $LEVEL_INFO $LEVEL_WARN $LEVEL_ERROR $LEVEL_FATAL
18             $FROM_HOLD $TO_HLD $TO_OUT $TO_ERR $TO_LOG
19 2     2   11 @TRACE @INFO @WARN @ERROR @FATAL );
  2         4  
20              
21             require Exporter;
22              
23             @ISA = qw(Exporter);
24              
25             @EXPORT = qw();
26              
27             @ALL = qw( $SHOW_ALL $USE_LEADIN $STACKTRACE
28             $LEVEL_TRACE $LEVEL_INFO $LEVEL_WARN $LEVEL_ERROR $LEVEL_FATAL
29             $FROM_HOLD $TO_HLD $TO_OUT $TO_ERR $TO_LOG
30             @TRACE @INFO @WARN @ERROR @FATAL
31             end abort );
32              
33             @AUX = qw( Normalize MakeDir );
34              
35             @EXPORT_OK = (@ALL,@AUX);
36              
37             %EXPORT_TAGS =
38             (
39             all => [@ALL],
40             aux => [@AUX],
41             ALL => [@EXPORT_OK]
42             );
43              
44             $VERSION = '1.7';
45              
46 2     2   11 use Config::Manager::Conf qw( whoami );
  2         4  
  2         109  
47 2     2   1618 use Symbol;
  2         2013  
  2         8842  
48              
49             #######################
50             ## Public constants: ##
51             #######################
52              
53             $TO_HLD = 0x01;
54             $TO_OUT = 0x02;
55             $TO_ERR = 0x04;
56             $TO_LOG = 0x08;
57             $FROM_HOLD = 0x10;
58              
59             $USE_LEADIN = 0x01;
60             $STACKTRACE = 0x02;
61              
62             $LEVEL_TRACE = 0x00;
63             $LEVEL_INFO = 0x04;
64             $LEVEL_WARN = 0x08;
65             $LEVEL_ERROR = 0x0C;
66             $LEVEL_FATAL = 0x10;
67              
68             $SHOW_ALL = 0x00;
69              
70             @TRACE = ( $TO_LOG , $LEVEL_TRACE + $USE_LEADIN );
71             @INFO = ( $TO_LOG + $TO_OUT, $LEVEL_INFO + $USE_LEADIN );
72             @WARN = ( $TO_LOG + $TO_ERR, $LEVEL_WARN + $USE_LEADIN );
73             @ERROR = ( $TO_LOG + $TO_HLD, $LEVEL_ERROR + $USE_LEADIN );
74             @FATAL = ( $TO_LOG + $TO_HLD, $LEVEL_FATAL + $USE_LEADIN );
75              
76             #######################################
77             ## Internal configuration constants: ##
78             #######################################
79              
80             my $LOGSUFFIX = 'log';
81              
82             my @LOGFILEPATH = ('DEFAULT', 'LOGFILEPATH');
83             my @FULLNAME = ('Person', 'Name');
84              
85             my $RULER = '_' x 78 . "\n";
86             my $HEADER = 'STARTED';
87             my $CMDLINE = 'COMMAND';
88             my $LOGFILE = 'LOGFILE';
89             my $FOOTER = 'ENDED';
90              
91             my @LEADIN =
92             (
93             [ 'TRACE', 'HINT', 'WARNING', 'ERROR', 'EXCEPTION' ], # Singular
94             [ 'TRACES', 'HINTS', 'WARNINGS', 'ERRORS', 'EXCEPTIONS' ] # Plural
95             );
96              
97             my $LINE0 = 'line on hold';
98             my $LINE1 = 'lines on hold';
99              
100             my $STAT_MIN = 1;
101             my $STAT_MAX = 4;
102              
103             my $STARTDEPTH = 0;
104             my $MAXEVALLEN = 0; # 0 = no limit
105              
106             #######################
107             ## Global variables: ##
108             #######################
109              
110             my $Singleton = 0;
111              
112             my @Inventory = ();
113              
114             my $User = (&whoami())[0] || '';
115              
116             my $Count = 0;
117              
118             ########################
119             ## Private functions: ##
120             ########################
121              
122             sub _warn_
123             {
124 0     0   0 my($text) = @_;
125 0         0 $text =~ s!\s+$!!;
126 0         0 Config::Manager::Report->report
127             (
128             $TO_LOG+$TO_ERR, $LEVEL_WARN+$USE_LEADIN, $text
129             )
130             }
131              
132             sub _die_
133             {
134 0     0   0 my($text) = @_;
135 0         0 $text =~ s!\s+$!!;
136 0 0       0 Config::Manager::Report->report
137             (
138             $TO_LOG+$TO_ERR, $LEVEL_FATAL+$USE_LEADIN, $text
139             )
140             if (defined $^S); # no logging during startup
141             }
142              
143             sub _adjust # code "stolen" from Carp.pm:
144             {
145 0     0   0 my($pack,$file,$line,$sub,$hargs,$warray,$eval,$require) = @_;
146              
147 0 0       0 if (defined $eval)
    0          
148             {
149 0 0       0 if ($require)
150             {
151 0         0 $sub = "require $eval";
152             }
153             else
154             {
155 0 0 0     0 if ($MAXEVALLEN && length($eval) > $MAXEVALLEN)
156             {
157 0         0 substr($eval,$MAXEVALLEN) = '...';
158             }
159 0         0 $eval =~ s!([\\\'])!\\$1!g;
160 0         0 $sub = "eval '$eval'";
161             }
162             }
163             elsif ($sub eq '(eval)')
164             {
165 0         0 $sub = 'eval {...}';
166             }
167 0         0 return $sub;
168             }
169              
170             sub _ShortTime
171             {
172 1     1   34 my($s,$m,$h,$dd,$mm,$yy) = localtime(time);
173 1         3 $yy %= 100;
174 1         1 $mm++;
175 1         15 return sprintf("%02d%02d%02d-%02d%02d%02d", $yy,$mm,$dd,$h,$m,$s);
176             }
177              
178             sub _LongTime
179             {
180 2     2   36 my($s,$m,$h,$dd,$mm,$yy) = localtime(time);
181 2         10 $yy += 1900;
182 2         4 $mm = (qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec))[$mm];
183 2         11 return sprintf("%02d-%s-%d %02d:%02d:%02d", $dd,$mm,$yy,$h,$m,$s);
184             }
185              
186             sub _which
187             {
188 8     8   12 my($self) = shift;
189              
190 8 100       15 if (ref $self) { return $self; }
  3         6  
191             else
192             {
193 5 100       13 unless (ref $Singleton)
194             {
195 1 50       4 if (ref ($Singleton = Config::Manager::Report->new(@_)))
196             {
197 1         2 ${$Singleton}{'singleton'} = 1;
  1         1  
198 1         15 $SIG{'__WARN__'} = \&_warn_;
199 1         5 $SIG{'__DIE__'} = \&_die_;
200             }
201             }
202 5         12 return $Singleton;
203             }
204             }
205              
206             sub DESTROY
207             {
208 2     2   3 my($self,$close) = @_;
209 2         3 my($text,$item,$count,$file,$handle);
210              
211 2 100 66     6 return unless (ref $self and keys %{$self});
  2         5  
212 1         5 $text = "\n" . $RULER . "\n $FOOTER: " . _LongTime();
213 1         4 for ( $item = $STAT_MIN; $item <= $STAT_MAX; $item++ )
214             {
215 4 100 66     2 if ((defined ($count = ${${$self}{'stat'}}[$item])) && ($count > 0))
  4         3  
  4         25  
216             {
217 1         3 $text .= " - $count ";
218 1 50       3 if ($count == 1) { $text .= ucfirst(lc($LEADIN[0][$item])); }
  0         0  
219 1         5 else { $text .= ucfirst(lc($LEADIN[1][$item])); }
220             }
221             }
222 1 50       1 if (($count = scalar(@{${$self}{'hold'}})) > 0)
  1         2  
  1         3  
223             {
224 1         3 $text .= " - $count ";
225 1 50       7 if ($count == 1) { $text .= $LINE0; }
  0         0  
226 1         2 else { $text .= $LINE1; }
227             }
228 1         2 $text .= "\n" . $RULER;
229 1         2 $file = ${$self}{'file'};
  1         2  
230 1         2 $handle = ${$self}{'hand'};
  1         1  
231 1         1 ${$self}{'level'} = $SHOW_ALL;
  1         2  
232 1 50       2 if (${$self}{'flag'})
  1         3  
233             {
234 1         4 $self->report($TO_LOG+$TO_OUT,$LEVEL_INFO,"$LOGFILE = '$file'");
235             }
236 1         4 $self->report($TO_LOG,$LEVEL_INFO,$text);
237             # Enable creation of new singleton object if necessary:
238 1 50       1 $Singleton = 0 if (${$self}{'singleton'});
  1         4  
239             # Prevent closing it again at global destruction time:
240 1         1 %{$self} = ();
  1         4  
241 1         2 $text = '';
242 1 50       12 unless (close($handle))
243             {
244 0 0       0 if ($close)
245             {
246 0         0 $text = __PACKAGE__ . "::close(): Can't close logfile '$file': $!";
247             }
248             else
249             {
250 0         0 $text = __PACKAGE__ . "::DESTROY(): Can't close logfile '$file': $!";
251 0         0 print STDERR "$text\n";
252             }
253             }
254 1         5 return $text;
255             }
256              
257 2     2   370 END { &end(); }
258              
259             #######################
260             ## Public functions: ##
261             #######################
262              
263             sub end
264             {
265 2     2 0 9 $SIG{'__WARN__'} = 'DEFAULT';
266 2         51 $SIG{'__DIE__'} = 'DEFAULT';
267 2         3 while (@Inventory)
268             {
269 1         4 pop(@Inventory)->DESTROY();
270             }
271             }
272              
273             sub abort
274             {
275 0     0 0 0 &end();
276 0 0       0 print STDERR @_ if @_;
277 0         0 print STDERR "\n";
278 0         0 exit 1;
279             }
280              
281             sub Normalize
282             {
283 2 50   2 0 5 my $dir = defined $_[0] ? $_[0] : '';
284 2         3 my $drv = '';
285              
286 2 50       14 if ($dir =~ s!^([a-zA-Z]:)!!) { $drv = $1; }
  0 50       0  
287 2         3 elsif ($dir !~ m!^[/\\]!) { $drv = '.'; }
288 2         5 $dir = "/$dir/";
289 2         3 $dir =~ s!\\!/!g;
290 2         2 $dir =~ s!//+!/!g;
291 2         15 while ($dir =~ s!/(?:\./)+!/!g) {};
292 2         5 while ($dir =~ s,/(?!\.\./)[^/]+/\.\./,/,g) {};
293 2         3 $dir =~ s!^/(?:\.\./)+!/!g;
294 2         5 $dir =~ s!^/!!;
295 2         4 $dir =~ s!/$!!;
296              
297 2 100       8 return wantarray ? ($drv,$dir) : "$drv/$dir";
298             }
299              
300             sub MakeDir
301             {
302 1     1 0 13 my($drv,$dir) = Normalize($_[0]);
303 1         2 my(@dir);
304 1         4 local($!);
305              
306 1 50       19 return '' if (-d "$drv/$dir");
307 0         0 @dir = split(/\//, $dir);
308 0         0 $dir = $drv;
309 0         0 while (@dir)
310             {
311 0         0 $dir .= '/' . shift(@dir);
312 0 0       0 unless (-d $dir)
313             {
314 0 0       0 unless (mkdir($dir,0777))
315             {
316 0         0 return "Can't mkdir '$dir': $!";
317             }
318             }
319             }
320 0         0 return '';
321             }
322              
323             #####################
324             ## Public methods: ##
325             #####################
326              
327             sub singleton
328             {
329 1     1 0 1 shift; # discard class name
330 1         5 return _which($Singleton,@_); # trigger creation if necessary
331             }
332              
333             sub new
334             {
335 1   50 1 0 4 my($class) = shift || __PACKAGE__;
336 1   50     7 my($tool) = shift || '';
337 1   50     5 my($path) = shift || '';
338 1   50     5 my($file) = shift || '';
339 1         2 my($err,$name,$user,$handle,$self,$time,$text);
340 1         1 local($_); # because of map()
341              
342 1   33     6 $class = ref($class) || $class;
343 1   50     5 $name = Config::Manager::Conf->get(@FULLNAME) || '';
344 1 50       5 if ($tool =~ /^\s*$/)
345             {
346 1         3 $tool = $0;
347 1         4 $tool =~ s!^.*[/\\]!!;
348 1         3 $tool =~ s!\.+[^\.]*$!!;
349             }
350 1 50       4 if ($path =~ /^\s*$/)
351             {
352 1 50       4 unless (defined ($path = Config::Manager::Conf->get(@LOGFILEPATH)))
353             {
354 0         0 $err = Config::Manager::Conf->error();
355 0         0 $err =~ s!\s+$!!;
356 0         0 return(__PACKAGE__ .
357             "::new(): Can't find log directory in configuration data: $err");
358             }
359             }
360 1         2 $file =~ s!^.*[/\\]!!;
361 1 50       3 if ($file =~ /^\s*$/)
362             {
363 1   50     6 $user = $User || $name || 'unknown';
364 1         4 $user =~ s!\s+!!g;
365 1         2 $path .= "/$tool/$user";
366 1         7 $file = join('-', $tool, $user, _ShortTime(), $$, ++$Count) . '.' . $LOGSUFFIX;
367             }
368 1 50       3 if ($err = MakeDir($path))
369             {
370 0         0 return(__PACKAGE__ .
371             "::new(): Can't create log directory '$path': $err");
372             }
373 1         4 $file = Normalize("$path/$file");
374 1         4 $handle = gensym();
375 1 50       147 unless (open($handle, ">$file"))
376             {
377 0         0 return(__PACKAGE__ .
378             "::new(): Can't open logfile '$file': $!");
379             }
380 1         6 select( ( select($handle), $| = 1 )[0] );
381 1         3 $self = { };
382 1         4 bless($self, $class);
383             # ${$self}{'user'} = $User;
384             # ${$self}{'name'} = $name;
385             # ${$self}{'tool'} = $tool;
386             # ${$self}{'path'} = $path;
387 1         2 ${$self}{'file'} = $file; # logfile name
  1         9  
388 1         1 ${$self}{'hand'} = $handle; # logfile handle
  1         3  
389 1         1 ${$self}{'hold'} = [ ]; # for putting lines on hold
  1         2  
390 1         1 ${$self}{'stat'} = [ ]; # for statistics
  1         3  
391 1         1 ${$self}{'flag'} = 0; # for automatic dump of logfile name
  1         2  
392 1         1 ${$self}{'level'} = $SHOW_ALL;
  1         2  
393             # (for suppressing messages below the indicated level)
394 1         2 $user = $User;
395 1 50 33     12 if (($user !~ /^\s*$/) && ($name !~ /^\s*$/))
396             {
397 0         0 $user = "$name ($user)";
398             }
399             else
400             {
401 1 50       5 if ($user =~ /^\s*$/)
402             {
403 1 50       3 if ($name =~ /^\s*$/) { $user = ""; }
  0         0  
404 1         2 else { $user = $name; }
405             }
406             }
407 1         2 $time = _LongTime();
408 1         10 $text =
409             $RULER .
410             "\n $HEADER: $tool - $time - $user\n" .
411             $RULER .
412             "\n $CMDLINE: " .
413             join(' ', map("'$_'", $^X, $0, @ARGV)) .
414             "\n";
415 1         5 $self->report($TO_LOG,$LEVEL_INFO,$text); # increments stat counters
416 1         2 ${$self}{'stat'} = [ ]; # reset stat counters to zero
  1         2  
417 1         2 push( @Inventory, $self );
418 1         4 return $self;
419             }
420              
421             sub close
422             {
423 0     0 0 0 my($self) = _which(shift);
424              
425 0         0 return __PACKAGE__ . "::close(): invalid logfile object!"
426 0 0 0     0 unless (ref $self and keys %{$self});
427 0         0 return $self->DESTROY(1);
428             }
429              
430             sub report
431             {
432 5     5 0 15 my($self) = _which(shift);
433 5   50     12 my($command) = shift || 0;
434 5   50     11 my($level) = shift || 0;
435 5         5 my($text,$leadin,$indent,$item,$depth,$sub,$file,$handle);
436 0         0 my(@stack,@trace);
437              
438 5 50 33     12 return unless (ref $self and keys %{$self});
  5         15  
439 5 50       11 if ($command & $FROM_HOLD)
440             {
441 0 0       0 return if ($command == $FROM_HOLD + $TO_HLD);
442 0 0       0 return unless (@{${$self}{'hold'}} > 0);
  0         0  
  0         0  
443 0         0 $text = ${$self}{'hold'};
  0         0  
444             }
445             else
446             {
447 5 50       5 return if ($level < ${$self}{'level'});
  5         12  
448 5         5 $leadin = '';
449 5         6 $indent = '';
450 5 100       9 if ($level & $USE_LEADIN)
451             {
452 2         4 $leadin = $LEADIN[0][$level >> 2] . ': ';
453 2         5 $indent = ' ' x length($leadin);
454             }
455 5         7 $text = [ ];
456 5         8 foreach $item (@_)
457             {
458 5         4 push( @{$text}, split(/\n/, $item, -1) );
  5         24  
459             }
460 5         6 foreach $item (@{$text})
  5         8  
461             {
462 16         24 $item = $leadin . $item;
463 16         31 $item =~ s!\s+$!!;
464 16         16 $item .= "\n";
465 16         21 $leadin = $indent;
466             }
467 5         7 @trace = ();
468 5 50       12 if ($level & $STACKTRACE)
469             {
470 0         0 $depth = $STARTDEPTH;
471 0         0 while (@stack = caller($depth++))
472             {
473 0         0 $sub = _adjust(@stack);
474 0         0 push
475             (
476             @trace,
477             $indent . "in $sub\n",
478             $indent . "called at $stack[1] line $stack[2]\n"
479             );
480             }
481             # Comment out next line if stack traces in logfile ONLY:
482             #### push( @{$text}, @trace );
483             }
484             }
485 5 50       7 if ($command & $TO_LOG)
486             {
487 5         11 $file = ${$self}{'file'};
  5         9  
488 5         4 $handle = ${$self}{'hand'};
  5         10  
489             #### unless (print $handle join('', @{$text})) # use this if push above is enabled
490 5 50       6 unless (print $handle join('', @{$text}, @trace)) # use this if push above is disabled
  5         122  
491             {
492 0         0 unshift( @{$text}, __PACKAGE__ . "::report(): Can't print logfile '$file': $!\n" );
  0         0  
493 0         0 $command |= $TO_HLD;
494 0         0 $command |= $TO_ERR;
495             }
496             }
497 5 50       12 if ($command & $TO_ERR)
498             {
499 0 0       0 unless (print STDERR join('', @{$text}))
  0         0  
500             {
501 0         0 $command |= $TO_OUT;
502             }
503             }
504 5 100       9 if ($command & $TO_OUT)
505             {
506 1 50       2 unless (print STDOUT join('', @{$text}))
  1         4  
507             {
508 0         0 $command |= $TO_HLD;
509             }
510             }
511 5 100       9 if ($command & $TO_HLD)
512             {
513 2 50       13 unless ($command & $FROM_HOLD)
514             {
515             #### push( @{${$self}{'hold'}}, @{$text} ); # use this if push above is enabled
516 2         2 push( @{${$self}{'hold'}}, @{$text}, @trace ); # use this if push above is disabled
  2         2  
  2         4  
  2         3  
517             }
518             }
519 5 50       15 if ($command & $FROM_HOLD)
520             {
521 0 0       0 ${$self}{'hold'} = [ ] unless ($command & $TO_HLD);
  0         0  
522             }
523             else
524             {
525 5         4 ${${$self}{'stat'}}[$level >> 2]++;
  5         5  
  5         19  
526             }
527             }
528              
529             sub trace
530             {
531 0     0 0 0 my($self) = _which(shift);
532 0         0 my($first,$depth,$sub,$item);
533 0         0 my(@stack,@trace,@args);
534              
535 0 0 0     0 return unless (ref $self and keys %{$self});
  0         0  
536             # Do nothing if trace unwanted:
537 0 0       0 return if ($LEVEL_TRACE < ${$self}{'level'});
  0         0  
538 0         0 $first = 1;
539 0         0 $depth = 1;
540 0         0 @trace = (); # code "borrowed" from Carp.pm:
541 0         0 while ( do {{ package DB; @stack = caller($depth++) }} )
  0         0  
  0         0  
542             {
543 0         0 $sub = _adjust(@stack);
544 0 0       0 if ($first)
545             {
546 0 0       0 if ($stack[4]) # $hargs
547             {
548 0         0 @args = @DB::args;
549 0         0 foreach $item (@args)
550             {
551 0 0       0 if (defined $item)
552             {
553 0         0 $item = "$item";
554 0         0 $item =~ s!([\\\'])!\\$1!g;
555 0 0       0 $item = "'$item'"
556             unless ($item =~ /^-?(?:[1-9]\d*|0)(?:\.\d+)?$/);
557             # $item =~ s!([\x80-\xFF])!'M-'.chr(ord($1)&0x7F)!eg;
558 0         0 $item =~ s!([\x00-\x1F\x7F])!'^'.chr(ord($1)^0x40)!eg;
  0         0  
559             }
560 0         0 else { $item = "undef"; }
561             }
562 0         0 $sub .= '(' . join(',', @args) . ')';
563             }
564 0         0 else { $sub .= '()'; }
565             }
566 0         0 else { $sub = "in $sub"; }
567 0         0 push
568             (
569             @trace,
570             $sub,
571             "called at $stack[1] line $stack[2]"
572             );
573 0         0 $first = 0;
574             }
575 0         0 $self->report(@TRACE,@trace);
576             }
577              
578             sub level
579             {
580 0     0 0 0 my($self) = _which(shift);
581 0         0 my($level);
582              
583 0 0 0     0 return undef unless (ref $self and keys %{$self});
  0         0  
584 0         0 $level = ${$self}{'level'};
  0         0  
585 0 0       0 if (@_ > 0)
586             {
587 0         0 ${$self}{'level'} = $_[0] + 0;
  0         0  
588             }
589 0         0 return $level;
590             }
591              
592             sub logfile
593             {
594 1     1 0 138 my($self) = _which(shift);
595              
596 1 50 33     4 return undef unless (ref $self and keys %{$self});
  1         4  
597 1         2 return ${$self}{'file'};
  1         3  
598             }
599              
600             sub notify # set flag for notifying user at exit about where logfile lies
601             {
602 1     1 0 3 my($self) = _which(shift);
603 1         2 my($flag);
604              
605 1 50 33     4 return undef unless (ref $self and keys %{$self});
  1         4  
606 1         2 $flag = ${$self}{'flag'};
  1         3  
607 1 50       3 if (@_ > 0)
608             {
609 1 50       15 ${$self}{'flag'} = ($_[0] ? 1 : 0);
  1         3  
610             }
611 1         3 return $flag;
612             }
613              
614             sub ret_hold
615             {
616 0     0 0   my($self) = _which(shift);
617              
618 0 0 0       if (defined wantarray && wantarray)
619             {
620 0 0 0       return () unless (ref $self and keys %{$self});
  0            
621 0           return (@{${$self}{'hold'}});
  0            
  0            
622             }
623             else
624             {
625 0 0 0       return undef unless (ref $self and keys %{$self});
  0            
626 0           return scalar(@{${$self}{'hold'}});
  0            
  0            
627             }
628             }
629              
630             sub clr_hold
631             {
632 0     0 0   my($self) = _which(shift);
633              
634 0 0 0       return unless (ref $self and keys %{$self});
  0            
635 0           ${$self}{'hold'} = [ ];
  0            
636             }
637              
638             1;
639              
640             __END__