=head1 NAME B - light weight thread aware, chainable, OOP backed exception strings with integrated message and properties and an uncluttered developer controlled stack trace. =head1 SYNOPSIS # -------------------------------------------------------- # making this module available to your code # -------------------------------------------------------- #Note: there are NO automatic exports use Exception::Lite qw(declareExceptionClass isException isChainable); # -------------------------------------------------------- # declare an exception class # -------------------------------------------------------- #no format rule declareExceptionClass($sClass); declareExceptionClass($sClass, $sSuperClass); declareExceptionClass($sClass, $sSuperClass,$bCustom); #with format rule declareExceptionClass($sClass, $aFormatRule); declareExceptionClass($sClass, $sSuperClass, $aFormatRule); declareExceptionClass($sClass, $sSuperClass, $aFormatRule, $bCustom); # -------------------------------------------------------- # create an exception but do not throw it # -------------------------------------------------------- # Note: parameters to new depend whether or not there is a format # rule. See documentation for declareExceptionClass. $e = $sClass->new($sMsg, $prop1 => $val1, ...); #no format rule $e = $sClass->new($prop1 => $val1, ...); #has format rule # -------------------------------------------------------- # throw an exception # -------------------------------------------------------- die $sClass->new($sMsg, $prop1 => $val1, ...); #no format rule die $sClass->new($prop1 => $val1, ...); #has format rule # -------------------------------------------------------- # catching an exception reliably (see below for discussion) # -------------------------------------------------------- eval { .... some code that may die here ... return 1; } or do { my $e=$@; .... handle exception using $e, NOT $@ here ... }; # checking the type of an arbitrary caught exception isChainable($e); # can $e be used as a chained exception? isException($e); # does $e have the above exception methods? isException($e,$sClass) # does $e belong to $sClass or a subclass? # -------------------------------------------------------- # exception methods # -------------------------------------------------------- $e->getMessage(); $e->getProperty($sName); $e->isProperty($sName); $e->replaceProperties($hOverride); $e->getPid(); $e->getPackage(); $e->getTid(); $e->getStackTrace(); $e->getFrameCount(); $e->getFile($i); $e->getLine($i); $e->getSubroutine($i); $e->getArgs($i); $e->getPropagation(); $e->getChained(); # -------------------------------------------------------- # rethrowing exceptions # -------------------------------------------------------- # using original properties and message my $e=$@; # $@ is fragile - see Try::Tiny die $e->rethrow(); $@=$e; die; # same as die $e->rethrow() # overriding original message/properties die $e->rethrow(path=>$altpath, user=>$nameReplacingId); # -------------------------------------------------------- # creation of chained exceptions (one triggered by another) # (new exception with "memory" of what caused it and stack # trace from point of cause to point of capture) # -------------------------------------------------------- die $sClass->new($e, $sMsg, $prop1 => $val1, ...);#no format rule die $sClass->new($e, $prop1 => $val1, ...); #has format rule # -------------------------------------------------------- # print out full message from an exception # -------------------------------------------------------- print $e # print works warn $e # warn works print "$e\n"; # double quotes work my $sMsg=$e."\n"; print $sMsg; # . operator works # -------------------------------------------------------- # global control variables (maybe set on the command line) # -------------------------------------------------------- $Exception::Lite::STRINGIFY #set rule for stringifying messages = 1; # message and file/line where it occured = 2; # 1 + what called what (simplified stack trace) = 3; # 2 + plus any chained exceptions and where message # was caught, if propagated and rethrown = 4; # 3 + arguments given to each call in stack trace = coderef # custom formatting routine $Exception::Lite::TAB # set indentation for stringified # messages, particularly indentation for # call parameters and chained exceptions $Exception::Lite::FILTER = 0 # see stack exactly as Perl does = 1 # remove frames added by eval blocks = coderef # custom filter - see getStackTrace for details # -------------------------------------------------------- # controlling the stack trace from the command line # -------------------------------------------------------- perl -mException::Lite=STRINGIFY=1,FILTER=0,TAB=4 perl -m'Exception::Lite qw(STRINGIFY=1 FILTER=0 TAB=4)' =head1 SPECIAL TOPICS =head2 Localization of error messages Rather than treat the error message and properties as entirely separate entities, it gives you the option to define a format string that will take your property values and insert them automatically into your message. Thus when you generate an exception, you can specify only the properties and have your message automatically generated without any need to repeat the property values in messy C's that clutter up your program. One can localize from the very beginning when one declares the class or later on after the fact if you are dealing with legacy software or developing on an agile module and only implementing what you need now. To localize from the get-go: # myLookupSub returns the arguments to declareException # e.g. ('CopyError', [ 'On ne peut pas copier de %s a %s' , qw(from to)]) declareExceptionClass( myLookupSub('CopyError', $ENV{LANG}) ); # .... later on, exception generation code doesn't need to # know or care about the language. it just sets the properties # error message depends on locale: # en_US: 'Cannot copy A.txt to B.txt' # fr_FR: 'On ne peut pas copier de A.txt a B.txt' # de_DE: 'Kann nicht kopieren von A.txt nach B.txt' die 'CopyError'->new(from => 'A.txt', to => 'B.txt'); Another alternative if you wish to localize from the get-go is to pass a code reference instead of a format rule array. In this case, C will automatically pass the class name to the subroutine and retrieve the value returned. # anothherLookupSub has parameters ($sClass) and returns # a format array, for example: # # %LOCALE_FORMAT_HASH = ( # CopyError => { # en_US => ['Cannot copy %s to %s', qw(from to)] # ,fr_FR => ['On ne peut pas copier de %s a %s', qw(from to)] # ,de_DE => ['Kann nicht kopieren von %s nach %s'' # , qw(from to)] # # AddError => ... # ); # # sub anotherLookupSub { # my ($sClass) = @_; # my $sLocale = $ENV{LANG} # return $LOCALE_FORMAT_HASH{$sClass}{$sLocale}; # } # declareExceptionClass('CopyError', &anotherLookupSub); declareExceptionClass('AddError', &anotherLookupSub); # error message depends on locale: # en_US: 'Cannot copy A.txt to B.txt' # fr_FR: 'On ne peut pas copier de A.txt a B.txt' # de_DE: 'Kann nicht kopieren von A.txt nach B.txt' die CopyError->new(from => 'A.txt', to => 'B.txt'); die AddError->new(path => 'C.txt'); If you need to put in localization after the fact, perhaps for a new user interface you are developing, the design pattern might look like this: # in the code module you are retrofitting would be an exception # that lived in a single language world. declareExceptionClass('CopyError' ['Cannot copy %s to %s', [qw(from to)]); # in your user interface application. if (isException($e, 'CopyError') && isLocale('fr_FR')) { my $sFrom = $e->getProperty('from'); my $sTo = $e->getProperty('to'); warn sprintf('On ne peut pas copier de %s a %s', $sFrom,$sTo); } =head2 Subclassing Semantics To declare a subclass with custom data and methods, use a three step process: * choose an exception superclass. * call C with its C<$bCustom> set to 1 * define a _new(...) method and subclass specific methods in a package block. The choice of superclass follows the rule, "like gives birth to like". Exception superclasses that have formats must have a superclass that also takes a format. Exception subclasses that have no format, must use an exception. When the C<$bCustom> flag is set to true, it might be best to think of C as something like C or C except that there is no implicit BEGIN block. Like both these methods it handles all of the setup details for the class so that you can focus on defining methods and functionality. Wnen C sees the C<$bCustom> flag set to true, it assumes you plan on customizing the class. It will set up inhertance, and generate all the usual method definition for an C class. However, on account of C<$bCustom> being true, it will add a few extra things so that and your custom code can play nicely together: * a special hash reserved for your subclsses data. You can get access to this hash by calling C<_p_getSubclassData()>. * at the end of its C method, it calls C<< $sClass->_new($self) >>. C<_new()> is a method that your subclass defines. It is responsible for doing additional setup of exception data. * it also ensures that the methods are defined so that you can override any standard object exception method. For example, suppose we want to define a subclass that accepts formats: #define a superclass that accepts formats declareExceptionClass('AnyError' , ['Unexpected exception: %s','exception']); # declare Exception subclass declareExceptionClass('TimedException', 'AnyError', $aFormatData,1); { package TimedException; sub _new { my $self = $_[0]; #exception object created by Exception::Lite # do additional setup of properties here my $timestamp=time(); my $hMyData = $self->_p_getSubclassData(); $hMyData->{when} = time(); } sub getWhen { my $self=$_[0]; return $self->_p_getSubclassData()->{when}; } } Now suppose we wish to extend our custom class further. There is no difference in the way we do things just because it is a subclass of a customized C class: # extend TimedException further so that it # # - adds two additional bits of data - the effective gid and uid # at the time the exception was thrown # - overrides getMessage() to include the time, egid, and euid declareExceptionClass('SecureException', 'TimedException' , $aFormatData,1); { package TimedException; sub _new { my $self = $_[0]; #exception object created by Exception::Lite # do additional setup of properties here my $timestamp=time(); my $hMyData = $self->_p_getSubclassData(); $hMyData->{euid} = $>; $hMyData->{egid} = $); } sub getEuid { my $self=$_[0]; return $self->_p_getSubclassData()->{euid}; } sub getEgid { my $self=$_[0]; return $self->_p_getSubclassData()->{egid}; } sub getMessage { my $self=$_[0]; my $sMsg = $self->SUPER::getMessage(); return sprintf("%s at %s, euid=%s, guid=%s", $sMsg , $self->getWhen(), $self->getEuid(), $self->getGuid()); } }