=pod
=head1 NAME
Merror - OOP errorhandling with stacktracing ability
=head1 VERSION
1.4
=head1 SYNOPSIS
use Merror;
function create_error {
my $obj = Merror->new(stackdepth => 16);
#indicate that an error happened
$obj->error(1);
#set an error code
$obj->ec(-1);
#set an error description
$obj->et("This is an error message");
return($obj);
}
my $error_obj = create_error();
#check if an error occured
if($error_obj->error()) {
#print out the errorcode (-1)
print("Errorcode: " . $obj->ec() ."\n");
#print out the error description (This is an error message)
print("Error description: " . $obj->et() . "\n");
#print out the captured stacktrace
$obj->stacktrace();
} else {
print("No error occured\n");
}
=head1 DESCRIPTION
Merror gives you the ability to C<throw> and catch errors in an OOP way. That means if you dont catch errors probably your code will continue running.
One C<big> feature of Merror is that it captures a stacktrace when an error occured that you can print out.
=head1 METHODS
=over 4
=item B<new(option =>>B< value, ...)>
Constructor.
Example:
my $obj = Merror->new( stackdepth => 64 );
the following options are available:
=over 4
=item stackdepth
Number defining the maximum tracing depth of a stacktrace.
# example: define tracingdepth of 64
stackdepth => 64
=item errorfile
Name of a file containing you errormapping, See section ERRORFILE for more information about the syntax of the file.
# example: define errorfile C</etc/merror/testerrors>
errorfile => C</etc/merror/testerrors>
=item ramusage
If set to an value other than undef or false the complete mapping of a given errorfile will be held in RAM instead of parsing it new every time you call an errorcode or error descrption.
# example: define ramusage
ramusage => 1
=back
=item B<error(indicate)>
Call this method with 1 as parameter C<indicate> to indicate that an error happened. You can fetch the error state by calling this method without any paramter. It returns 1 if an error occured or 0 if not.
Example:
my $obj = Merror->new(stackdepth => 10, errorfile => C</etc/merror/errorfile>, ramusage=>0);
...
#if you formerly called $obj->error(1) than this query whill return 1
if($obj->error()( {
...
}
=item B<ec(errorCode)>
If you call this method with a number this will set the errorcode. Call it without any parameter to get the last errorcode back.
Example:
# set error code -255
$obj->ec(-255);
# print out the last errorcode
print($obj->ec()."\n");
=item B<et(errorDescription)>
Call this method with a string paramter and this string will be set as the error description. Call it without any parameter to get back the last error description.
Example:
# set error description
$obj->et("Fatal unresolvable error occured");
# print out the last error description
print($obj->et()."\n");
=item B<mappedError(errorCode)>
This method searches the errorfile for errorCode and sets the errorcode and error description from the mapping
Example:
# we got the following mapping in our errorfile: 24: Could not connect to given host
# set error code and description depending on the mapping
$obj->mappedError(24);
# print out the errorcode: 24
print($obj->ec()."\n");
# print out the error description: Could not connect to given host
print($obj->et()."\n");
=item B<stacktrace>
Prints out the caputed stacktrace.
=item B<return_stacktrace>
Returns the captured stacktrace as an array where every element is one level ov the stacktrace
Example:
my @st = $obj->return_stacktrace();
foreach (@st) {
print("$_\n");
}
=item B<merror_copy(destinationStructure)>
You can treat this method as an copy-operator for Merror structures. It will copy the complete internal state ob the calling object into an hash reference indicated by C<destionationStructure>
Example:
use Merror;
my $obj = Merror->new();
$obj->ec(13);
$obj->et("Test error");
# will print out: 13
print($obj->ec()."\n");
# will print out: Test error
print($obj->et()."\n");
# now copy the internal state
my $obj2 = {};
$obj->merror_copy($obj2);
# will print out: 13
print($obj2->ec()."\n");
# will print out: Test error
print($obj2->et()."\n");
=back
=head1 ERRORFILE
By defining a file in the constructor parameter C<errorfile> you have the ability to use this file for your error descriptions.
The syntax of every line of the file is:
[Errorcode]: [Errordescription]
-255: Unknown Error occurred
Lines starting with a # will be ignored.
Every line will be parsed through this regular expression:
/^(\d{1,})\s{0,}:\s{0,}(.*)/
=head1 BUGS
None known
=head1 ACKNOWLEDGEMENTS
If you find any bugs or got some feature you wish to have implemented please register at C<mantis.markus-mazurczak.de>.
=head1 COPYRIGHT
See README.
=head1 AVAILABILITY
You can allways get the latest version from CPAN.
=head1 AUTHOR
Markus Mazurczak <coding@markus-mazurczak.de>
=cut
package Merror;
our $VERSION = '1.4';
use strict;
use warnings;
sub new {
my $invocant = shift;
my %opts = @_;
my $class = ref($invocant) || $invocant;
my $self = {
ERROR => 0,
EC => 0,
ET => "",
STACKDEPTH => ($opts{stackdepth} || 64),
ERRORFILE => ($opts{errorfile} || undef),
RAMUSAGE => ($opts{ramusage} || 0),
ERRMAPPING => { },
STACK => { },
};
bless $self, $class;
if(defined($self->{ERRORFILE}) && !-r $self->{ERRORFILE}) {
$self->{ERROR} = 1;
$self->{EC} = -255;
$self->{ET} = "Could not read configured error mapping file: " . $self->{ERRORFILE};
} elsif(defined($self->{ERRORFILE}) && $self->{RAMUSAGE} != 0) {
$self->parseErrorFile();
}
return($self);
}
sub error {
my $self = shift;
if(@_) {
$self->{ERROR} = 1;
fillstack($self);
}
else { return($self->{ERROR}); }
}
sub ec {
my $self = shift;
if(@_) { $self->{EC} = $_[0]; }
else { $self->{ERROR}=0;return($self->{EC}); }
}
sub et {
my $self = shift;
if(@_) { $self->{ET} = $_[0]; }
else { $self->{ERROR}=0;return($self->{ET}); }
}
sub mappedError {
my $self = shift;
my $errorcode = shift;
if(!defined($errorcode) || !defined($self->{ERRORFILE})) {
$self->ec(-255);
$self->et("Undefined error occured");
} else {
$self->ec($errorcode);
$self->et($self->mapErrorCode($errorcode));
}
}
sub stacktrace {
my $self = shift;
$self->{ERROR}=0;
foreach my $stacklevel(sort(keys(%{$self->{STACK}}))) {
print("Level: $stacklevel -- File: ");
print($self->{STACK}->{$stacklevel}->{FILE}." -- Pkg: ");
print($self->{STACK}->{$stacklevel}->{PKGNAME}." -- Sub: ");
print($self->{STACK}->{$stacklevel}->{SUBROUTINE}." -- Line: ");
print($self->{STACK}->{$stacklevel}->{LINE}."\n");
}
}
sub return_stacktrace {
my $self = shift;
my @stack_array;
my $counter = 0;
$self->{ERROR}=0;
foreach my $stacklevel(sort(keys(%{$self->{STACK}}))) {
$stack_array[$counter] = "Level: $stacklevel -- File: ";
$stack_array[$counter + 1] = $self->{STACK}->{$stacklevel}->{FILE}." -- Pkg: ";
$stack_array[$counter + 2] = $self->{STACK}->{$stacklevel}->{PKGNAME}." -- Sub: ";
$stack_array[$counter + 3] = $self->{STACK}->{$stacklevel}->{SUBROUTINE}." -- Line: ";
$stack_array[$counter + 4] = $self->{STACK}->{$stacklevel}->{LINE}."\n";
$counter += 5;
}
return(@stack_array);
}
sub merror_copy {
my ($self, $to) = @_;
if($self->{EC}) { $to->{EC} = $self->{EC}; }
if($self->{ET}) { $to->{ET} = $self->{ET}; }
if($self->{ERROR}) { $to->{ERROR} = $self->{ERROR}; }
if($self->{STACKDEPTH}) { $to->{STACKDEPTH} = $self->{STACKDEPTH}; }
if($self->{STACK}) { $to->{STACK} = $self->{STACK}; }
if($self->{ERRORFILE}) { $to->{ERRORFILE} = $self->{ERRORFILE}; }
if($self->{RAMUSAGE}) { $to->{RAMUSAGE} = $self->{RAMUSAGE}; }
if($self->{ERRMAPPING}) { $to->{ERRMAPPING} = $self->{ERRMAPPING}; }
}
# private method
# Captures the stacktrace with a max depth of $self->{STACKDEPTH}
sub fillstack {
my $self = shift;
for(my $i=0; $i<$self->{STACKDEPTH}; $i++) {
if(!defined(caller($i))) { return; }
my ($pkgname, $file, $line, $subroutine,) = caller($i);
$self->{STACK}->{$i}->{PKGNAME} = ($pkgname || "");
$self->{STACK}->{$i}->{FILE} = ($file || "");
$self->{STACK}->{$i}->{LINE} = ($line || "");
$self->{STACK}->{$i}->{SUBROUTINE} = ($subroutine || "");
}
}
# private method
# If user wants to parse the complete error mapping file and save it into ram than this function is called
sub parseErrorFile {
my $self = shift;
open(MAPFILE, $self->{ERRORFILE}) or die("This should never happen (function: parseErrorFile, file: Merror.pm).");
while(my $line = <MAPFILE>) {
chomp($line);
next if($line =~ /^#/ || $line =~ /^\s{0,}$/);
my ($number, $desc) = ($line =~ /^(\d{1,})\s{0,}:\s{0,}(.*)/);
if(!defined($number) || !defined($desc) || $number =~ /^\s{1,}$/ || $desc =~ /^\s{1,}$/) {
$self->{ERROR} = 1;
$self->{EC} = -254;
$self->{ET} = "Syntax error in error mapping file (".$self->{ERRORFILE}.") in line: $.";
return;
}
$self->{ERRMAPPING}->{$number} = $desc;
}
close(MAPFILE);
}
# private method
# Returns the error description of the given errorcode. If errorcode is undef than an undefined error will be returned.
sub mapErrorCode {
my $self = shift;
my $errorcode = shift;
if(!defined($errorcode)) {
return("Undefined error occured");
}
if($self->{RAMUSAGE} != 0) {
return($self->{ERRMAPPING}->{$errorcode} || "Undefined error occured");
}
open(MAPFILE, $self->{ERRORFILE}) or die("This should never happen (function: parseErrorFile, file: Merror.pm).");
while(my $line = <MAPFILE>) {
chomp($line);
next if($line =~ /^#/ || $line =~ /^\s{0,}$/);
my ($number, $desc) = ($line =~ /^(\d{1,})\s{0,}:\s{0,}(.*)/);
if($number =~ /^\s{1,}$/ || $desc =~ /^\s{1,}$/) {
return("Undefined error occured");
}
if("$errorcode" eq "$number") {
return($desc);
}
}
close(MAPFILE);
return("Undefined error occured");
}
1;