# Log::Delayed - Delayed error handling # See copyright, etc in below POD section. ###################################################################### package Log::Delayed; require Exporter; use IO::File; use Carp; use strict; use vars qw($VERSION $Global_Delayed @ISA @EXPORT $Debug); @ISA = qw(Exporter); @EXPORT = qw(die_delayed); $VERSION = '1.424'; ###################################################################### #### Traps END { # Called whenever perl exits. return if !$Global_Delayed; sig_end ($Global_Delayed); } ###################################################################### #### Creation sub new { my $class = shift; my $self = {errors => 0, filename => ".status", status => "Completed\n", # Set to undef if will call completed() later. overwrite => 1, write_if_ok => 1, global => 1, @_}; bless $self, $class; $self->{status} = "%Error: Missing All Finished\n" if !defined $self->{status}; $self->{_noerror_status} = $self->{status}; if ($self->{global}) { $Global_Delayed = $self; $SIG{__DIE__} = \&sig_die; } # Remove the file before we start; in case of a fatal error we don't # want a bad "Completed" message in it. unlink $self->{filename} if ($self->{overwrite} && $self->{filename} && -r $self->{filename}); return $self; } ###################################################################### #### Signals sub sig_end { my $self = (ref $_[0]) ? shift : $Global_Delayed; # Allow method or global calling $? = 10 if (!$? && $self->{errors}); # Exit with bad status if a error was detected $self->write_status() if $self->{filename} && ($self->{write_if_ok} || $self->{errors}); } sub sig_die { die @_ if $^S; return if !$Global_Delayed; my $self = (ref $_[0]) ? shift : $Global_Delayed; # Allow method or global calling $self->die_delayed (@_); exit (20); } ###################################################################### #### Accessors sub global_self { my $self = (ref $_[0]) ? shift : $Global_Delayed; # Allow method or global calling return $self; } sub completed { my $self = (ref $_[0]) ? shift : $Global_Delayed; # Allow method or global calling my $msg = "Completed\n"; if ($#_ >= 0) { $msg = join('',@_); } if (!$self->errors) { $self->{status} = $msg; $self->{_noerror_status} = $self->{status}; print "\tLog::Delayed::completed <= $msg\n" if $Debug; } } sub errors { my $self = (ref $_[0]) ? shift : $Global_Delayed; # Allow method or global calling if ($#_ >= 0) { $self->{errors} = shift; if (!$self->{errors}) { # User cleared errors, so clear status too... $self->{status} = $self->{_noerror_status}; } } return $self->{errors}; } sub status { my $self = (ref $_[0]) ? shift : $Global_Delayed; # Allow method or global calling # New apps should use errors(0) to clear errors, completed() to indicate completion # or die_delayed() to report errors instead of this function if ($#_ >= 0) { my $msg = join('',@_); $self->{status} = $msg; $self->{errors}++ if $msg ne "Completed\n"; # Back compatible, suggest completed instead for new apps. print "\tLog::Delayed::status <= $msg\n" if $Debug; } return $self->{status}; } sub exit_if_error { my $self = (ref $_[0]) ? shift : $Global_Delayed; # Allow method or global calling #END handler will write the status file exit(10) if $self->{errors}; } sub die_delayed { my $self = (ref $_[0]) ? shift : $Global_Delayed; # Allow method or global calling $self->{errors} ++; my $msg = join('',@_); warn $msg; if ($self->{errors} == 1) { $self->{status} = $msg; } } ###################################################################### #### File sub write_status { my $self = (ref $_[0]) ? shift : $Global_Delayed; # Allow method or global calling my %params = (%{$self}, @_); my $filename = $params{filename}; defined $filename or croak "%Error: No filename=> specified, stopped"; print "\tLog::Delayed::write_status, ".($self->errors||"ok")."\n" if $Debug; if ($params{overwrite} || (!-r $filename && $self->errors)) { my $fh = new IO::File (">$filename") or die "%Error: $! $filename\n"; print $fh $params{status}; $fh->close(); } } sub read_status { my $self = (ref $_[0]) ? shift : $Global_Delayed; # Allow method or global calling my %params = (%{$self}, @_); my $filename = $params{filename}; defined $filename or croak "%Error: No filename=> specified, stopped"; my $fh = new IO::File ($filename); return undef if ! $fh; my $wholefile = join('',$fh->getlines()); $fh->close(); return $wholefile; } ###################################################################### #### Package return 1; __END__ =pod =head1 NAME Log::Delayed - Delay error handling and write status file =head1 SYNOPSIS use Log::Delayed; my $Delayed = new Log::Delayed (filename=>"test_dir/.status"); die_delayed ("First error into .status\n"); if ($Delayed->errors()) { print "We got a error\n"; } $Delayed->errors(0); # Clear errors $Delayed->write_status(); my $current_status = $Delayed->read_status(); $Delayed->exit_if_error(); $Delayed->completed() =head1 DESCRIPTION Log::Delayed is used to delay error messages for later logging and exiting. This is useful when parsing files and such, and multiple errors want to be presented to the user before exiting the program. In addition, Log::Delayed optionally makes a status file (.status), which contains the first error detected. This allows calling programs to be passed more useful tracking information than just the shell exit status. =head1 FUNCTIONS =over 4 =item $dly->new New creates a new Log::Delayed object. Parameters are passed by named form. The filename=> parameter specifies the file to be written with the exit message, undef for none; defaults to .status. The global=> boolean parameter forces the $dly->sig_end to be called automatically at program exit, it defaults true. The overwrite=> boolean parameter defaults to true to overwrite any existing status file and write it even if there are no errors. If overwrite=> is clear, the status file will only be written with errors, which is useful to detect the first error across many applications running together. The status=> string has the default status. It defaults to "Completed" for backward compatibility; new applications may wish to undef it and use the completed() call instead. The write_if_ok=> boolean parameter defaults to true to write the .status file if there are no errors to report on exit. If cleared, ok status will not be written. This is useful for forked processes that need to silently exit. =item $dly->completed Call at the end of the normal execution flow to set the status to "Completed\n". Use with the status=>undef parameter in the new call. This allows the status file to indicate if the program didn't complete normally, but also didn't report an error. (Like from calling exit() instead of die().) =item $dly->die_delayed Die_delayed prints any parameters on stderr, then records the error occurrence for later error exiting. If new was called with global=>1 parameter, the exported version of die_delayed may be called without any object. =item $dly->errors Returns the number of errors seen. With a parameter it sets the number of errors seen. =item $dly->exit_if_error exit_if_error exits the program if any errors were detected. =item $dly->global_self Returns the default object, either the one passed, or the default global object - the last one created. =item $dly->read_status read_status reads the filename=> specified with new or this function call. It returns the contents of the file, or undef if no file exists. =item $dly->sig_end sig_end changes the exit status to be bad if any delayed errors were detected, and calls write_status. sig_end is called automatically by the END{} handler if global=>1 was specified with the new constructor. =item $dly->sig_die sig_die records the first error it sees so that write_status will contain Perl related error messages. sig_end is called automatically by the %SIG{__DIE__} handler if global=>1 was specified with the new constructor. =item $dly->write_status write_status writes the filename=> specified with new or this function call with the first error message detected, or "Completed\n" if there were no errors. =back =head1 DISTRIBUTION Log-Detect is part of the L free EDA software tool suite. The latest version is available from CPAN and from L. Copyright 2000-2009 by Wilson Snyder. This package is free software; you can redistribute it and/or modify it under the terms of either the GNU Lesser General Public License Version 3 or the Perl Artistic License Version 2.0. =head1 AUTHORS Wilson Snyder =head1 SEE ALSO L, C in L =cut