00001 package MMAgic::Log;
00002 #
00003 # MMAgic/Log.pm
00004 #
00005 # Object representing an output log stream.
00006 # Output is formatted with type tags and timestamps.
00007 #
00008
00009 use strict;
00010 use warnings;
00011
00012 use Exporter;
00013 use POSIX;
00014 use UNIVERSAL qw(isa);
00015
00016 use MMAgic::Log::Indent;
00017 use MMAgic::Output;
00018 use MMAgic::Trace;
00019
00020 our @ISA = qw(Exporter MMAgic::Output);
00021 our @EXPORT = qw(Log);
00022
00023 our $hashkey;
00024 our $signals;
00025 our $special;
00026 our @stack;
00027
00028 ###########################################################################
00029 ###########################################################################
00030 sub new ($;%) #
00031 #
00032 {
00033 my ($class, %parms) = @_;
00034 my $self = new MMAgic::Output(%parms);
00035
00036 return undef
00037 unless isa($self, 'MMAgic::Output');
00038
00039 bless $self, $class;
00040
00041 $self->{code} = 'T';
00042 $self->{dGMT} = $parms{GMT} || $ENV{MMAgic_Log_GMT};
00043 $self->{dFmt} = $parms{timeOnly} || $ENV{MMAgic_Log_timeOnly}
00044 ? '%H:%M:%S' : '%Y/%m/%d %H:%M:%S';
00045
00046 if ($self->{handle} && ! $self->{noFlush})
00047 {
00048 # suceeded, turn on auto-flushing:
00049 my $oldFH = select($self->{handle});
00050
00051 $| = 1;
00052 select($oldFH);
00053 }
00054
00055 $self
00056 }
00057
00058 ###########################################################################
00059 sub Log # [ $class ]
00060 #
00061 {
00062 unshift @stack, new MMAgic::Log(output => \*STDERR)
00063 unless @stack;
00064
00065 $stack[0]
00066 }
00067
00068 ###########################################################################
00069 sub pushLog # [ $class, ] $log | %init
00070 #
00071 {
00072 my $log = shift;
00073
00074 # Get rid of optional class argument:
00075 $log = shift if isa($log, __PACKAGE__) && ! ref $log;
00076
00077 # See which argument form we have:
00078 unless (isa($log, __PACKAGE__))
00079 {
00080 # Must create Log object from parameters:
00081 my %init = ( $log, @_ );
00082 my $type = $init{logType};
00083
00084 if ($type)
00085 {
00086 $type = "MMAgic::Log::$type";
00087
00088 eval "use $type";
00089
00090 die "Unable to load $type:\n $@\n"
00091 if $@;
00092 }
00093
00094 $type = __PACKAGE__
00095 unless $type;
00096
00097 $log = $type->new(%init);
00098 }
00099
00100 unshift @stack, $log
00101 if isa($log, __PACKAGE__);
00102
00103 $stack[0]
00104 }
00105
00106 ###########################################################################
00107 sub popLog # [ $class ]
00108 #
00109 {
00110 shift @stack
00111 }
00112
00113 ###########################################################################
00114 ###########################################################################
00115 sub handle # [ $class ] [ @which ]
00116 # Capture warn and/or die handlers and redirect to current log.
00117 # This would be a permanent fix, the user being responsible for
00118 # backing the out if necessary.
00119 #
00120 {
00121 shift if isa($_[0], __PACKAGE__);
00122
00123 #!# my $auto = Log->method(@_)->block;
00124
00125 map {
00126 #!# Log->trace('Which: ', $_);
00127
00128 if (/^warn/i)
00129 {
00130 $SIG{__WARN__} = \&handleWarn;
00131 }
00132
00133 elsif (/^die/i)
00134 {
00135 $SIG{__DIE__} = \&handleDie;
00136 }
00137
00138 else
00139 {
00140 Log->warn('Unable to Log ', $_, ' messages');
00141 }
00142 } @_ ? @_ : qw(warn die);
00143 }
00144
00145 ###########################################################################
00146 sub handleWarn #
00147 # Redirect warnings to log file.
00148 #
00149 {
00150 Log->warning(shift);
00151 }
00152
00153 ###########################################################################
00154 sub handleDie #
00155 # Redirect die messages to log file.
00156 #
00157 {
00158 # Unfortunately, the $SIG{__DIE__} handler is somewhat broken,
00159 # we don't want to fire if we're wrapped in an eval somewhere:
00160 my $up = 0;
00161
00162 while (1)
00163 {
00164 my ($pkg, $file, $line, $sub) = caller($up++);
00165
00166 last unless $pkg;
00167
00168 return if $file eq '(eval)' || $sub eq '(eval)';
00169 }
00170
00171 Log->critical(shift);
00172
00173 # die "DIE: Final error message in MMAgic::Log repository!\n"
00174 die "\n"
00175 }
00176
00177 ###########################################################################
00178 ###########################################################################
00179 sub starter # $self
00180 #
00181 {
00182 my $self = shift;
00183 my $now = $self->timestamp($self->{msgT});
00184
00185 $self->SUPER::starter;
00186 $self->process("$now [$self->{msgC}] ");
00187 $self->{msgC} = '+';
00188 $self
00189 }
00190
00191 ###########################################################################
00192 sub timestamp # [ $clasSelf ] [ $when ]
00193 #
00194 {
00195 my ($self, $when) = @_;
00196
00197 if (! isa($self, __PACKAGE__))
00198 {
00199 $when = $self;
00200 $self = Log;
00201 }
00202
00203 unless ($when && int($when))
00204 {
00205 $when = $self->{msgT};
00206 $when = time unless $when && int($when);
00207 }
00208
00209 return strftime
00210 ($self->{dFmt},
00211 $self->{dGMT} ? gmtime($when) : localtime($when));
00212 }
00213
00214 ###########################################################################
00215 ###########################################################################
00216 sub block # [ $clasSelf, ] [ $incr ]
00217 # Returns 'auto-indent' object.
00218 # Indent is started and then removed when the object is destroyed.
00219 #
00220 {
00221 my $target = shift;
00222
00223 $target = Log unless isa($target, __PACKAGE__);
00224
00225 new MMAgic::Log::Indent($target, @_)
00226 }
00227
00228 ###########################################################################
00229 sub push # $clasSelf [, $incr]
00230 #
00231 {
00232 my $target = shift;
00233
00234 $target = Log unless isa($target, __PACKAGE__);
00235 $target->pushIndent(shift);
00236 }
00237
00238 ###########################################################################
00239 sub pop # $clasSelf
00240 #
00241 {
00242 my $target = shift;
00243
00244 $target = Log unless isa($target, __PACKAGE__);
00245 $target->popIndent;
00246 }
00247
00248 ###########################################################################
00249 sub withIndent # $clasSelf, $sub, @args
00250 #
00251 {
00252 my $target = shift;
00253 my $sub = shift;
00254
00255 $target = Log unless isa($target, __PACKAGE__);
00256 $target->push;
00257
00258 my $result = eval { &$sub(@_); };
00259 my $error = $@;
00260
00261 $target->pop;
00262
00263 die $error if $error;
00264
00265 $result
00266 }
00267
00268 ###########################################################################
00269 ###########################################################################
00270 sub flush # $self
00271 # Flush pending output.
00272 # Override the basic MMAgic::Output method which prints nasty messages.
00273 #
00274 {
00275 # Still can't actually do anything...
00276 shift
00277 }
00278
00279 ###########################################################################
00280 sub message # $self, [ timestamp, ] $code, ...
00281 #
00282 {
00283 my $self = shift;
00284
00285 $self = Log unless ref($self) eq __PACKAGE__;
00286
00287 my $strt = $self->{msgT};
00288
00289 $self->{msgC} = '?';
00290 $self->{msgT} = shift;
00291
00292 if ($self->{msgT} !~ /\D/) # check for integer
00293 {
00294 # Timestamp was actually provided:
00295 $self->{msgC} = shift || $self->{code};
00296 }
00297
00298 else
00299 {
00300 # No timestamp, was flag instead:
00301 $self->{msgC} = $self->{msgT};
00302 $self->{msgT} = time;
00303 }
00304
00305 $self->newLine;
00306
00307 my @args = @_; # don't know why this is necessary,
00308 # but can't just use @_ inside of eval
00309 eval
00310 {
00311 $self->print(@args);
00312 $self->newLine;
00313 };
00314
00315 $self->{msgT} = $strt;
00316 $self->flush;
00317 $self
00318 }
00319
00320 ###########################################################################
00321 sub append # [ $clasSelf, ] ...
00322 #
00323 {
00324 my $target = shift;
00325
00326 $target = Log unless isa($target, __PACKAGE__);
00327 $target->message('+', @_);
00328 }
00329
00330 ###########################################################################
00331 sub critical # [ $clasSelf, ] ...
00332 #
00333 {
00334 my $target = shift;
00335
00336 $target = Log unless isa($target, __PACKAGE__);
00337 $target->message('C', @_);
00338 }
00339
00340 ###########################################################################
00341 sub error # [ $clasSelf, ] ...
00342 #
00343 {
00344 my $target = shift;
00345
00346 $target = Log unless isa($target, __PACKAGE__);
00347 $target->message('E', @_);
00348 }
00349
00350 ###########################################################################
00351 sub info # [ $clasSelf, ] ...
00352 #
00353 {
00354 my $target = shift;
00355
00356 $target = Log unless isa($target, __PACKAGE__);
00357 $target->message('I', @_);
00358 }
00359
00360 ###########################################################################
00361 sub trace # [ $clasSelf, ] ...
00362 #
00363 {
00364 my $target = shift;
00365
00366 $target = Log unless isa($target, __PACKAGE__);
00367 $target->message('T', @_);
00368 }
00369
00370 ###########################################################################
00371 sub unknown # [ $clasSelf, ] ...
00372 #
00373 {
00374 my $target = shift;
00375
00376 $target = Log unless isa($target, __PACKAGE__);
00377 $target->message('?', @_);
00378 }
00379
00380 ###########################################################################
00381 sub warning # [ $clasSelf, ] ...
00382 #
00383 {
00384 my $target = shift;
00385
00386 $target = Log unless isa($target, __PACKAGE__);
00387 $target->message('W', @_);
00388 }
00389
00390 ###########################################################################
00391 ###########################################################################
00392 sub crash # [ $clasSelf, ] ...
00393 #
00394 {
00395 my $target = shift;
00396
00397 $target = Log unless isa($target, __PACKAGE__);
00398 $target->message('C', @_);
00399
00400 my ($pkg, $file, $line) = caller;
00401
00402 die "Crashed at $file line $line\n"
00403 }
00404
00405 ###########################################################################
00406 sub bail # [ $clasSelf, ] ...
00407 #
00408 {
00409 warn 'Log::bail() deprecated, use Log::crash() instead';
00410
00411 crash(@_)
00412 }
00413
00414 ###########################################################################
00415 sub method # [ $clasSelf, ] ...
00416 #
00417 {
00418 my $target = shift;
00419
00420 $target = Log unless isa($target, __PACKAGE__);
00421
00422 my ($pkg, undef, undef, $sub) = caller(1);
00423 my $flag = 'T';
00424 my $indt = '';
00425
00426 if ($_[0] && $_[0] =~ /^\[(.)\](.*)$/)
00427 {
00428 # Special first argument to specify message flag and indent:
00429 $flag = $1;
00430 $indt = $2;
00431 shift;
00432 }
00433
00434 shift if isa($_[0], $pkg);
00435
00436 local $special = undef;
00437 local $hashkey = undef;
00438
00439 $target->message
00440 ($flag, $indt, $sub, '(',
00441 join(', ', map
00442 {
00443 my $result = defined($_) ? $_ : '<undef>';
00444
00445 if ($special)
00446 {
00447 if ($hashkey)
00448 {
00449 $result = "$hashkey => $result";
00450 undef $hashkey;
00451 }
00452
00453 else
00454 {
00455 $hashkey = $_;
00456 undef $result;
00457 }
00458 }
00459
00460 elsif ($result eq '%')
00461 {
00462 $special = 1;
00463 undef $result;
00464 }
00465
00466 defined($result) ? $result : ( )
00467 } @_), ')'
00468 );
00469 }
00470
00471 ###########################################################################
00472 sub where # [ $line ]
00473 #
00474 {
00475 shift if isa $_[0], __PACKAGE__;
00476
00477 my ($func, $pkg, $stack) = ('eval', undef, 1);
00478
00479 ($pkg, undef, undef, $func) = caller($stack++)
00480 while $func =~ /\b(?:eval|where)\b/;
00481
00482 my $where = "${pkg}::${func}()";
00483
00484 $where .= "\n file @{[ shift ]}" if @_;
00485 $where .= "\n line @{[ shift ]}" if @_;
00486 $where =~ s/^main::
00487 $where
00488 }
00489
00490 ###########################################################################
00491 ###########################################################################
00492 sub signals # [ $clasSelf, ] $flag
00493 # Turn signal capture and redirection on and off.
00494 # Don't know if this actually works.
00495 # And then there's the __DIE__ over-zealous problem.
00496 #
00497 {
00498 my $flag = shift;
00499
00500 $flag = shift if isa($flag, __PACKAGE__);
00501
00502 return if $flag == $signals;
00503
00504 if ($flag)
00505 {
00506 $SIG{__WARN__} = sub { Log->Error (@_) };
00507 $SIG{__DIE__} = sub { Log->Critical(@_) };
00508 }
00509
00510 else
00511 {
00512 delete $SIG{__DIE__};
00513 delete $SIG{__WARN__};
00514 }
00515
00516 $signals = $flag;
00517 }
00518
00519 ###########################################################################
00520 sub display # $self, $item, %param
00521 # Dump an object to a Log.
00522 #
00523 {
00524 my $self = shift;
00525 my $item = shift;
00526 my %param;
00527
00528 if (@_ == 1) {
00529 # Support old-style shortcut which I keep doing...
00530 # it just makes sense most of the time.
00531 $param{prefix} = shift;
00532 $param{prefix} =~ s/\s*$/ => /
00533 unless $param{prefix} =~ /=>\s*/;
00534 } elsif (@_) {
00535 %param = @_;
00536 }
00537
00538 $self->{msgC} = $param{code} || $self->{code};
00539
00540 delete $param{code}
00541 if exists $param{code};
00542
00543 $self->newLine;
00544 $self->SUPER::display($item, %param);
00545 $self->flush;
00546 $self
00547 }
00548
00549 ###########################################################################
00550 ###########################################################################
00551 my $deprecated;
00552
00553 sub reference # $self, $target, %param
00554 # Dump a reference-based object to Log.
00555 #
00556 {
00557 my $self = shift;
00558 my $targ = shift;
00559
00560 Log->warning(__PACKAGE__, '::reference() deprecated in favor of ',
00561 __PACKAGE__, '::display()')
00562 unless $deprecated++;
00563
00564 # Cover for ancient calling convention
00565 # ($self, $target [, $code]):
00566 $self->{msgC} = shift
00567 if @_ == 1;
00568
00569 $self->display(@_, $targ, @_)
00570 }
00571
00572 1