|
=head1 NAME
|
|
|
|
Exception::Lite - light weight exception handling class with smart
|
|
stack tracing, chaining, and localization support.
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
# --------------------------------------------------------
|
|
# making this module available to your code
|
|
# --------------------------------------------------------
|
|
|
|
#Note: there are NO automatic exports
|
|
|
|
use Exception::Lite qw(declareExceptionClass
|
|
isException
|
|
isChainable
|
|
onDie
|
|
onWarn);
|
|
|
|
# imports only: declareExceptionClass isException isChainable
|
|
use Exception::Lite qw(:common);
|
|
|
|
# imports all exportable methods listed above
|
|
use Exception::Lite qw(:all);
|
|
|
|
|
|
# --------------------------------------------------------
|
|
# declare an exception class
|
|
# --------------------------------------------------------
|
|
|
|
# no format rule
|
|
declareExceptionClass($sClass);
|
|
declareExceptionClass($sClass, $sSuperClass);
|
|
|
|
# with format rule
|
|
declareExceptionClass($sClass, $aFormatRule);
|
|
declareExceptionClass($sClass, $sSuperClass, $aFormatRule);
|
|
|
|
# with customized subclass
|
|
declareExceptionClass($sClass, $sSuperClass, 1);
|
|
declareExceptionClass($sClass, $aFormatRule, 1);
|
|
declareExceptionClass($sClass, $sSuperClass, $aFormatRule, 1);
|
|
|
|
# --------------------------------------------------------
|
|
# throw an exception
|
|
# --------------------------------------------------------
|
|
|
|
die $sClass->new($sMsg, $prop1 => $val1, ...); #no format rule
|
|
die $sClass->new($prop1 => $val1, ...); #has format rule
|
|
|
|
#-or-
|
|
|
|
$e = $sClass->new($sMsg, $prop1 => $val1, ...); #no format rule
|
|
$e = $sClass->new($prop1 => $val1, ...); #has format rule
|
|
|
|
die $e;
|
|
|
|
# --------------------------------------------------------
|
|
# catch and test an exception
|
|
# --------------------------------------------------------
|
|
|
|
# Note: for an explanation of why we don't use if ($@)... here,
|
|
# see Catching and Rethrowing exceptions below
|
|
|
|
eval {
|
|
.... some code that may die here ...
|
|
return 1;
|
|
} or do {
|
|
my $e=$@;
|
|
|
|
if (isException($e, 'Class1')) {
|
|
... do something ...
|
|
} elsif (isExcption($e, 'Class2')) {
|
|
... do something else ...
|
|
}
|
|
};
|
|
|
|
isException($e); # does $e have the above exception methods?
|
|
isException($e,$sClass) # does $e belong to $sClass or a subclass?
|
|
|
|
# --------------------------------------------------------
|
|
# getting information about an exception object
|
|
# --------------------------------------------------------
|
|
|
|
$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
|
|
|
|
$@=$e; die; # pure Perl way (reset $@ in case wiped out)
|
|
|
|
die $e->rethrow(); # same thing, but a little less cryptic
|
|
|
|
|
|
# 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)
|
|
# --------------------------------------------------------
|
|
|
|
isChainable($e); # can $e be used as a chained exception?
|
|
|
|
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)'
|
|
|
|
# --------------------------------------------------------
|
|
# built in exception classes
|
|
# --------------------------------------------------------
|
|
|
|
# generic wrapper for converting exception strings and other
|
|
# non-Exception::Lite exceptions into exception objects
|
|
|
|
Exception::Class::Any->new($sMessageText);
|
|
|
|
To assist in debugging and testing, this package also includes
|
|
two methods that set handlers for die and warn. These methods
|
|
should I<only> be used temporarily during active debugging. They
|
|
should not be used in production software, least they interfere
|
|
with the way other programmers using your module wish to do their
|
|
debugging and testing.
|
|
|
|
# --------------------------------------------------------
|
|
# force all exceptions/warnings to use Exception::Lite to
|
|
# print out messages and stack traces
|
|
# --------------------------------------------------------
|
|
|
|
# $stringify is the value for EXCEPTION::Lite::STRINGIFY
|
|
# that you want to use locally to print out messages. It
|
|
# will have no effect outside of the die handler
|
|
|
|
Exception::Lite::onDie($stringify);
|
|
Exception::Lite::onWarn($stringify);
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
The C<Exception::Lite> class provides an easy and very light weight
|
|
way to generate context aware exceptions. It was developed because
|
|
the exception modules on CPAN as of December,2010 were heavy on
|
|
features I didn't care for and did not have the features I most
|
|
needed to test and debug code efficiently.
|
|
|
|
=head2 Features
|
|
|
|
This module provides a light weight but powerful exception class
|
|
that
|
|
|
|
=over
|
|
|
|
=item *
|
|
|
|
provides an uncluttered stack trace that clearly shows what
|
|
called what and what exception triggered what other exception.
|
|
It significantly improves on the readability of the stack trace
|
|
dumps provided by C<carp> and other exception modules on
|
|
CPAN (as of 12/2010). For further discussion and a sample, see
|
|
L</More intelligent stack trace>.
|
|
|
|
=item *
|
|
|
|
gives the user full control over the amount of debugging
|
|
information displayed when exceptions are thrown.
|
|
|
|
=item *
|
|
|
|
permits global changes to the amount of debugging information
|
|
displayed via the command line.
|
|
|
|
=item *
|
|
|
|
closely integrates exception classes, messages, and properties
|
|
so that they never get out of sync with one another. This in
|
|
turn eliminates redundant coding and helps reduce the cost of
|
|
writing,validating and maintaining a set of exceptions.
|
|
|
|
=item *
|
|
|
|
is easy to retrofit with native language support, even if this
|
|
need appears late in the development process.This makes it
|
|
suitable for use with agile development strategies.
|
|
|
|
=item *
|
|
|
|
act like strings in string context but are in fact objects with
|
|
a class hierarchy and properties.They can be thrown and rethrown
|
|
with standard Perl syntax. Like any object, they can be uniquely
|
|
identified in numeric context where they equal their reference
|
|
address (the value returned by C<Scalar::Util::refaddr()>.
|
|
|
|
=item *
|
|
|
|
does not interfere with signal handlers or the normal Perl syntax
|
|
and the assumptions of Perl operators.
|
|
|
|
=item *
|
|
|
|
can be easily extended and subclassed
|
|
|
|
=back
|
|
|
|
=head2 Lightweight how?
|
|
|
|
Despite these features C<Exception::Lite> maintains its "lite"
|
|
status by
|
|
|
|
=over
|
|
|
|
=item *
|
|
|
|
using only core modules
|
|
|
|
=item *
|
|
|
|
generating tiny exception classes (30-45LOC per class).
|
|
|
|
=item *
|
|
|
|
eliminating excess baggage by customizing generated classes to
|
|
reflect the actual needs of exception message generation. For
|
|
instance an exception wrapped around a fixed string message would
|
|
omit code for message/property integration and would be little
|
|
more than a string tied to a stack trace and property hash.
|
|
|
|
=item *
|
|
|
|
storing only the minimum amount of stack trace data needed to
|
|
generate exception messages and avoiding holding onto references
|
|
from dead stack frames. (Note: some CPAN modules hold onto
|
|
actual variables from each frame, possibly interfering with
|
|
garbage collection).
|
|
|
|
=item *
|
|
|
|
doing all its work, including class generation and utilities in
|
|
a single file that is less than half the size of the next smallest
|
|
similarly featured all-core exception class on CPAN (support for
|
|
both properties and a class heirarchy). C<Exception::Lite>
|
|
contains about 400 lines when developer comments are excluded). The
|
|
next smallest all core module is L<Exception::Base|Exception::Base>
|
|
which clocks in at just over 1000 lines after pod and developer
|
|
comments are excluded).
|
|
|
|
=item *
|
|
|
|
avoiding a heavy-weight base class. Code shared by
|
|
C<Exception::Lite> classes are stored in function calls that total
|
|
230 or so lines of code relying on nothing but core modules. This
|
|
is significantly less code than is needed by the two CPAN packages
|
|
with comparable features. The all core
|
|
L<Exception::Base|Exception::Base> class contains 700+ lines of
|
|
code. The base class of L<Exception::Class|Exception::Class> has
|
|
200 lines of its own but drags in two rather large non-core
|
|
modules as dependencies: L<Devel::StackTrace|Devel::StackTrace>
|
|
L<Class::Data::Inheritable|Class::Data::Inheritable>.
|
|
|
|
=back
|
|
|
|
C<Exception::Lite> has more features (chaining, message/property
|
|
integration) but less code due to the following factors:
|
|
|
|
=over
|
|
|
|
=item *
|
|
|
|
working with Perl syntax rather than trying to replace it.
|
|
|
|
=item *
|
|
|
|
using a light approach to OOP - exception classes have just enough
|
|
and no more OO features than are needed to be categorized by a
|
|
class, participate in a class heirarchy and to have properties.
|
|
|
|
=item *
|
|
|
|
respecting separation of concerns. C<Exception::Lite> focuses
|
|
on the core responsibility of an exception and leaves the bulk of
|
|
syntax creation (e.g. Try/Catch) to specialist modules like
|
|
L<Try::Tiny|Try::Tiny>. Other modules try to double as
|
|
comprehensive providers of exception related syntactic sugar.
|
|
|
|
=item *
|
|
|
|
not trying to be the only kind of exception that an application
|
|
uses.
|
|
|
|
=back
|
|
|
|
=head1 USAGE
|
|
|
|
=head2 Defining Exception Classes
|
|
|
|
C<Exception::Lite> provides two different ways to define messages.
|
|
The first way, without a format rule, lets you compose a freeform
|
|
message for each exception. The second way, with a format rule,
|
|
lets you closely integrate messages and properties and facilitates
|
|
localization of messages for any packages using your software.
|
|
|
|
=head3 Defining freeform messages
|
|
|
|
If you want to compose a free form message for each and every
|
|
exception, the class declaration is very simple:
|
|
|
|
declareExceptionClass($sClass);
|
|
declareExceptionClass($sClass, $sSuperClass);
|
|
|
|
# with customized subclass
|
|
declareExceptionClass($sClass, $sSuperClass, 1);
|
|
|
|
C<$sClass> is the name of the exception class.
|
|
|
|
C<$sSuperClass> is the name of the superclass, if there is one.
|
|
The superclass can be any class created by C<Exception::Lite>. It
|
|
can also be any role class, i.e. a class that has methods but no
|
|
object data of its own.
|
|
|
|
The downside of this simple exception class is that there is
|
|
absolutely no integration of your messages and any properties that
|
|
you assign to the exception. If you would like to see your property
|
|
values included in the message string,consider using a formatted
|
|
message instead.
|
|
|
|
=head3 Defining formatted messages
|
|
|
|
If you wish to include property values in your messages, you need
|
|
to declare a formatted message class. To do this, you define a
|
|
format rule and pass it to the constructor:
|
|
|
|
$aFormatRule = ['Cannot copy %s to %s', qw(from to) ];
|
|
|
|
declareExceptionClass($sClass, $aFormatRule);
|
|
declareExceptionClass($sClass, $sSuperClass, $aFormatRule);
|
|
|
|
# with customized subclass
|
|
declareExceptionClass($sClass, $aFormatRule, 1);
|
|
declareExceptionClass($sClass, $sSuperClass, $aFormatRule, 1);
|
|
|
|
Format rules are nothing more than a sprintf message string
|
|
followed by a list of properties in the same order as the
|
|
placeholders in the message string. Later on when an exception
|
|
is generated, the values of the properties will replace the
|
|
property names. Some more examples of format rules:
|
|
|
|
|
|
$aFormatRule = ['Illegal argument <%s>: %s', qw(arg reason)];
|
|
declareExceptionClass('BadArg', $aFormatRule);
|
|
|
|
$aFormatRule = ['Cannot open file <%s>> %s', qw(file reason)];
|
|
declareExceptionClass('OpenFailed', $aFormatRule);
|
|
|
|
$sFormatRule = ['Too few %s, must be at least %s', qw(item min)];
|
|
declareExceptionClass('TooFewWidgets', $aFormatRule);
|
|
|
|
|
|
Later on when you throw an exception you can forget about the message
|
|
and set the properties, the class will do the rest of the work:
|
|
|
|
die BadArg->new(arg=>$sPassword, reason=>'Too few characters');
|
|
|
|
|
|
open(my $fh, '>', $sFile)
|
|
or die OpenFailed->new(file=>$sFile, reason=>$!);
|
|
|
|
And still later when you catch the exception, you have two kinds
|
|
of information for the price of one:
|
|
|
|
# if you catch BadArg
|
|
|
|
$e->getProperty('arg') # mine
|
|
$e->getProperty('reason') # too few characters
|
|
$e->getMessage() # Illegal argument <mine>: too few characters
|
|
|
|
|
|
# if you catch OpenFailed
|
|
|
|
$e->getProperty('file') # foo.txt
|
|
$e->getProperty('reason') # path not found
|
|
$e->getMessage() # Cannot open <foo.txt>: path not found
|
|
|
|
|
|
=head2 Creating and throwing exceptions
|
|
|
|
When it comes times to create an exception, you create and
|
|
throw it like this (C<$sClass> is a placeholder for the name of
|
|
your exception class);
|
|
|
|
|
|
die $sClass->new($sMsg, prop1 => $val1, ...); #no format rule
|
|
die $sClass->new(prop1 => $val1, ...); #has format rule
|
|
|
|
#-or-
|
|
|
|
$e = $sClass->new($sMsg, prop1 => $val1, ...); #no format rule
|
|
$e = $sClass->new(prop1 => $val1, ...); #has format rule
|
|
|
|
die $e;
|
|
|
|
|
|
For example:
|
|
|
|
# Freeform exceptions (caller composes message, has message
|
|
# parameter ($sMsg) before the list of properties)
|
|
|
|
close $fh or die UnexpectedException
|
|
->new("Couldn't close file handle (huh?): $!");
|
|
|
|
die PropertySettingError("Couldn't set property"
|
|
, prop=>foo, value=>bar);
|
|
|
|
# Formatted exceptions (no $sMsg parameter)
|
|
|
|
if (length($sPassword) < 8) {
|
|
die BadArg->new(arg=>$sPassword, reason=>'Too few characters');
|
|
}
|
|
|
|
open(my $fh, '>', $sFile)
|
|
or die OpenFailed->new(file=>$sFile, reason=>$!);
|
|
|
|
In the above examples the order of the properties does not matter.
|
|
C<Exception::Lite> is using the property names, not the order of
|
|
the properties to find the right value to plug into the message
|
|
format string.
|
|
|
|
=head2 Catching and testing exceptions
|
|
|
|
In Perl there are two basic ways to work with exceptions:
|
|
|
|
* native Perl syntax
|
|
|
|
* Java like syntax (requires non-core modules)
|
|
|
|
=head3 Catching exceptions the Java way
|
|
|
|
Java uses the following idiom to catch exceptions:
|
|
|
|
try {
|
|
.... some code here ...
|
|
} catch (SomeExceptionClass e) {
|
|
... error handling code here ...
|
|
} catch (SomeOtherExceptionClass e) {
|
|
... error handling code here ...
|
|
} finally {
|
|
... cleanup code here ...
|
|
}
|
|
|
|
There are several CPAN modules that provide some sort of syntactic
|
|
sugar so that you can emulate java syntax. The one recommended
|
|
for C<Exception::Lite> users is L<Try::Tiny|Try::Tiny>.
|
|
L<Try::Tiny|Try::Tiny> is an elegant class that concerns itself
|
|
only with making it possible to use java-like syntax. It can be
|
|
used with any sort of exception.
|
|
|
|
Some of the other CPAN modules that provide java syntax also
|
|
require that you use their exception classes because the java like
|
|
syntax is part of the class definition rather than a pure
|
|
manipulation of Perl syntax.
|
|
|
|
|
|
=head3 Catching exceptions the Perl way
|
|
|
|
The most reliable and fastest way to catch an exception is to use
|
|
C< eval/do >:
|
|
|
|
eval {
|
|
...
|
|
return 1;
|
|
} or do {
|
|
# save $@ before using it - it can easily be clobbered
|
|
my $e=$@;
|
|
|
|
... do something with the exception ...
|
|
|
|
warn $e; #use $e as a string
|
|
warn $e->getMessage(); # use $e as an object
|
|
}
|
|
|
|
|
|
The C<eval> block ends with C<return 1;> to insure that successful
|
|
completion of the eval block never results in an undefined value.
|
|
In certain cases C<undef> is a valid return value for a statement,
|
|
We don't want to enter the C<do> block for any reason other than
|
|
a thrown exception.
|
|
|
|
C< eval/do > is both faster and more reliable than the C< eval/if>
|
|
which is commonly promoted in Perl programming tutorials:
|
|
|
|
# eval ... if
|
|
|
|
eval {...};
|
|
if ($@) {....}
|
|
|
|
It is faster because the C<do> block is executed if and only
|
|
if the eval fails. By contrast the C<if> must be evaluated both
|
|
in cases of succes and failure.
|
|
|
|
C< eval/do > is more reliable because the C<do> block is guaranteed
|
|
to be triggered by any die, even one that accidentally throws undef
|
|
or '' as the "exception". If an exception is thrown within the C<eval>
|
|
block, it will always evaluate to C<undef> therefore triggering the
|
|
C<do> block.
|
|
|
|
On the other hand we can't guarentee that C<$@> will be defined
|
|
even if an exception is thrown. If C<$@> is C<0>, C<undef>, or an
|
|
empty string, the C<if> block will never be entered. This happens
|
|
more often then many programmers realize. When eval exits the
|
|
C< eval > block, it calls destructors of any C<my> variables. If
|
|
any of those has an C< eval > statement, then the value of C<$@> is
|
|
wiped clean or reset to the exception generated by the destructor.
|
|
|
|
Within the C<do> block, it is a good idea to save C<$@> immediately
|
|
into a variable before doing any additional work. Any subroutine
|
|
you call might also clobber it. Even built-in commands that don't
|
|
normally set C<$@> can because Perl lets a programmer override
|
|
built-ins with user defined routines and those user define routines
|
|
might set C<$@> even if the built-in does not.
|
|
|
|
=head3 Testing exceptions
|
|
|
|
Often when we catch an exception we want to ignore some, rethrow
|
|
others, and in still other cases, fix the problem. Thus we need a
|
|
way to tell what kind of exception we've caught. C<Exception::Lite>
|
|
provides the C<isException> method for this purpose. It can be
|
|
passed any exception, including scalar exceptions:
|
|
|
|
# true if this exception was generated by Exception::Line
|
|
isException($e);
|
|
|
|
|
|
# true if this exception belongs to $sClass. It may be a member
|
|
# of the class or a subclass. C<$sClass> may be any class, not
|
|
# just an Exception::Lite generated class. You can even use this
|
|
# method to test for string (scalar) exceptions:
|
|
|
|
isException($e,$sClass);
|
|
|
|
isException($e,'Excption::Class');
|
|
isException($e, 'BadArg');
|
|
isException($e, '');
|
|
|
|
And here is an example in action. It converts an exception to a
|
|
warning and determines how to do it by checing the class.
|
|
|
|
|
|
eval {
|
|
...
|
|
return 1;
|
|
} or do {
|
|
my $e=$@;
|
|
if (Exception::Lite::isException($e)) {
|
|
|
|
# get message w/o stack trace, "$e" would produce trace
|
|
warn $e->getMessage();
|
|
|
|
} elsif (Exception::Lite::isException('Exception::Class') {
|
|
|
|
# get message w/o stack trace, "$e" would produce trace
|
|
warn $e->message();
|
|
|
|
} elsif (Exception::Lite::isException($e,'')) {
|
|
|
|
warn $e;
|
|
}
|
|
}
|
|
|
|
=head2 Rethrowing exceptions
|
|
|
|
Perl doesn't have a C<rethrow> statement. To reliably rethrow an
|
|
exception, you must set C<$@> to the original exception (in case it
|
|
has been clobbered during the error handling process) and then call
|
|
C<die> without any arguments.
|
|
|
|
eval {
|
|
...
|
|
return 1;
|
|
} or do {
|
|
my $e=$@;
|
|
|
|
# do some stuff
|
|
|
|
# rethrow $e
|
|
$@=$e; die;
|
|
}
|
|
|
|
The above code will cause the exception's C<PROPAGATE> method to
|
|
record the file and line number where the exception is rethrown.
|
|
See C<getLine>, C<getFile>, and C<getPropagation> in the class
|
|
reference below for more information.
|
|
|
|
As this Perl syntax is not exactly screaming "I'm a rethrow",
|
|
C<Exception::Lite> provides an alternative and hopefully more
|
|
intuitive way of propagating an exception. There is no magic here,
|
|
it just does what perl would do had you used the normal syntax,
|
|
i.e. call the exception's C<PROPAGATE> method.
|
|
|
|
eval {
|
|
...
|
|
return 1;
|
|
} or do {
|
|
my $e=$@;
|
|
|
|
# rethrow $e
|
|
die $e->rethrow();
|
|
}
|
|
|
|
=head2 Chaining Messages
|
|
|
|
As an exception moves up the stack, its meaning may change. For
|
|
example, suppose a subroutine throws the message "File not open".
|
|
The immediate caller might be able to use that to try and open
|
|
a different file. On the other hand, if the message gets thrown
|
|
up the stack, the fact that a file failed to open might not
|
|
have any meaning at all. That higher level code only cares that
|
|
the data it needed wasn't available. When it notifies the user,
|
|
it isn't going to say "File not found", but "Can't run market
|
|
report: missing data feed.".
|
|
|
|
When the meaning of the exception changes, it is normal to throw
|
|
a new exception with a class and message that captures the new
|
|
meaning. However, if this is all we do, we lose the original
|
|
source of the problem.
|
|
|
|
Enter chaining. Chaining is the process of making one exception
|
|
"know" what other exception caused it. You can create a new
|
|
exception without losing track of the original source of the
|
|
problem.
|
|
|
|
To chain exceptions is simple: just create a new exception and
|
|
pass the caught exception as the first parameter to C<new>. So
|
|
long as the exception is a non-scalar, it will be interpreted
|
|
as a chained exception and not a property name or message text
|
|
(the normal first parameter of C<new>).
|
|
|
|
Chaining is efficient, especially if the chained exception is
|
|
another C<Exception::Lite> exception. It does not replicate
|
|
the stack trace. Rather the original stack trace is shorted to
|
|
include only the those fromes frome the time it was created to
|
|
the time it was chained.
|
|
|
|
Any non-scalar exception can be chained. To test whether or not
|
|
a caught exception is chainable, you can use the method
|
|
C<isChainable>. This method is really nothing more than
|
|
a check to see if the exception is a non-scalar, but it helps
|
|
to make your code more self documenting if you use that method
|
|
rather than C<if (ref($e))>.
|
|
|
|
If an exception isn't chainable, and you still want to chain
|
|
it, you can wrap the exception in an exception class. You
|
|
can use the built-in C<Exception::Class::Any> or any class of
|
|
your own choosing.
|
|
|
|
#-----------------------------------------------------
|
|
# define some classes
|
|
#-----------------------------------------------------
|
|
|
|
# no format rule
|
|
declareExceptionClass('HouseholdDisaster');
|
|
|
|
# format rule
|
|
declareExceptionClass('ProjectDelay'
|
|
, ['The project was delayed % days', qw(days)]);
|
|
|
|
#-----------------------------------------------------
|
|
# chain some exceptins
|
|
#-----------------------------------------------------
|
|
|
|
eval {
|
|
.... some code here ...
|
|
return 1;
|
|
} or do {
|
|
my $e=$@;
|
|
if (Exception::Lite::isChainable($e)) {
|
|
if (Exception::Lite::isException($e, 'FooErr') {
|
|
die 'SomeNoFormatException'->new($e, "Caught a foo");
|
|
} else {
|
|
die 'SomeFormattedException'->new($e, when => 'today');
|
|
}
|
|
} elsif ($e =~ /fire/) {
|
|
die 'Exception::Lite::Any'->new($e);
|
|
die 'SomeFormattedException'->new($e, when => 'today');
|
|
} else {
|
|
# rethrow it since we can't chain it
|
|
$@=$e; die;
|
|
}
|
|
}
|
|
|
|
=head2 Reading Stack Traces
|
|
|
|
At its fullest level of detail, a stack trace looks something
|
|
like this:
|
|
|
|
Exception! Mayhem! and then ...
|
|
|
|
thrown at file Exception/Lite.t, line 307
|
|
in main::weKnowBetterThanYou, pid=24986, tid=1
|
|
@_=('ARRAY(0x83a8a90)'
|
|
,'rot, rot, rot'
|
|
,'Wikerson brothers'
|
|
,'triculous tripe'
|
|
,'There will be no more talking to hoos who are not!'
|
|
,'black bottom birdie'
|
|
,'from the three billionth flower'
|
|
,'Mrs Tucanella returns with uncles and cousins'
|
|
,'sound off! sound off! come make yourself known!'
|
|
,'Apartment 12J'
|
|
,'Jo Jo the young lad'
|
|
,'the whole world was saved by the smallest of all'
|
|
)
|
|
reached via file Exception/Lite.t, line 281
|
|
in main::notAWhatButAWho
|
|
@_=()
|
|
reached via file Exception/Lite.t, line 334 in main::__ANON__
|
|
@_=()
|
|
reached via file Exception/Lite.t, line 335 in <package: main>
|
|
@ARGV=()
|
|
|
|
Triggered by...
|
|
Exception! Horton hears a hoo!
|
|
rethrown at file Exception/Lite.t, line 315
|
|
|
|
thrown at file Exception/Lite.t, line 316
|
|
in main::horton, pid=24986, tid=1
|
|
@_=('15th of May'
|
|
,'Jungle of Nool'
|
|
,'a small speck of dust on a small clover'
|
|
,'a person's a person no matter how small'
|
|
)
|
|
reached via file Exception/Lite.t, line 310 in main::hoo
|
|
@_=('Dr Hoovey'
|
|
,'hoo-hoo scope'
|
|
,'Mrs Tucanella'
|
|
,'Uncle Nate'
|
|
)
|
|
reached via file Exception/Lite.t, line 303
|
|
in main::weKnowBetterThanYou
|
|
@_=('ARRAY(0x83a8a90)'
|
|
,'rot, rot, rot'
|
|
,'Wikerson brothers'
|
|
,'triculous tripe'
|
|
,'There will be no more talking to hoos who are not!'
|
|
,'black bottom birdie'
|
|
,'from the three billionth flower'
|
|
,'Mrs Tucanella returns with uncles and cousins'
|
|
,'sound off! sound off! come make yourself known!'
|
|
,'Apartment 12J'
|
|
,'Jo Jo the young lad'
|
|
,'the whole world was saved by the smallest of all'
|
|
)
|
|
|
|
|
|
=over
|
|
|
|
=item *
|
|
|
|
lines begining with "thrown" indicate a line where a new exception
|
|
was thrown. If an exception was chained, there might be multiple
|
|
such lines.
|
|
|
|
=item *
|
|
|
|
lines beginning with "reached via" indicate the path travelled
|
|
I<down> to the point where the exception was thrown. This is the
|
|
code that was excuted before the exception was triggered.
|
|
|
|
=item *
|
|
|
|
lines beginning with "rethrown at" indicate the path travelled
|
|
I<up> the stack by the exception I<after> it was geenerated. Each
|
|
line indicates a place where the exception was caught and rethrown.
|
|
|
|
=item *
|
|
|
|
lines introduced with "Triggered by" are exceptions that were
|
|
chained together. The original exception is the last of the
|
|
triggered exceptions. The original line is the "thrown" line
|
|
for the original exception.
|
|
|
|
=item *
|
|
|
|
C<@_> and <C@ARGV> below a line indicates what is left of the
|
|
parameters passed to a method, function or entry point routine.
|
|
In ideal circumstances they are the parameters passed to the
|
|
subroutine mentioned in the line immediately above C<@_>. In
|
|
reality, they can be overwritten or shifted away between the
|
|
point when the subroutine started and the line was reached.
|
|
|
|
Note: if you use L<Getopt::Long> to process C<@ARGV>, C<@ARGV>
|
|
will be empty reduced to an empty array. If this bothers you, you
|
|
can localize <@ARGV> before calling C<GetOptions>, like this:
|
|
|
|
my %hARGV;
|
|
{
|
|
local @ARGV = @ARGV;
|
|
GetOptions(\%hARGV,...);
|
|
}
|
|
|
|
=item *
|
|
|
|
pid is the process id where the code was running
|
|
|
|
=item *
|
|
|
|
tid is the thread id where the code was running
|
|
|
|
=back
|
|
|
|
=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<sprintf>'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<Exception::Lite> 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 Controlling verbosity and stack tracing
|
|
|
|
You don't need to print out the fully verbose stack trace and in
|
|
fact, by default you won't. The default setting, prints out
|
|
only what called what. To make it easier to see what called what,
|
|
it leaves out all of the dumps of C<@_> and C<@ARGV>.
|
|
|
|
If you want more or less verbosity or even an entirely different
|
|
trace, C<Exception::Lite> is at your sevice. It provides a variety
|
|
of options for controlling the output of the exception:
|
|
|
|
* Adjusting the level of debugging information when an exception is
|
|
thrown by setting C<$Exception::Lite::STRINGIFY>
|
|
in the program or C<-mException::Lite=STRINGIFY=level> on the
|
|
command line. This can be set to either a verbosity level or to
|
|
an exception stringification routine of your own choosing.
|
|
|
|
* Control which stack frames are displayed by setting
|
|
C<$Exception::Lite::FILTER>. By default, only calls within named
|
|
and anonymous subroutines are displayed in the stack trace. Perl
|
|
sometimes creates frames for blocks of code within a subroutine.
|
|
These are omitted by default. If you want to see them, you can
|
|
turn filterin off. Alternatively you can set up an entirely
|
|
custon stack filtering rule by assigning a code reference to
|
|
C<$Exception::Lite::FILTER>.
|
|
|
|
* By default, exceptions store and print a subset of the data
|
|
available for each stack frame. If you would like to display
|
|
richer per-frame information, you can do that too. See below
|
|
for details.
|
|
|
|
=head3 Verbosity level
|
|
|
|
The built-in rules for displaying exceptions as strings offer five
|
|
levels of detail.
|
|
|
|
* 0: Just the error message
|
|
|
|
* 1: the error message and the file/line number where it occured
|
|
along with pid and tid.
|
|
|
|
* 2: the error message and the calling sequence from the point where
|
|
the exception was generated to the package or script entry point
|
|
The calling sequence shows only file, line number and the name
|
|
of the subroutine where the exception was generated. It is not
|
|
cluttered with parameters, making it easy to scan.
|
|
|
|
* 3: similar to 2, except that propagation and chained exceptions
|
|
are also displayed.
|
|
|
|
* 4: same as 3, except that the state of C<@_> or C<@ARGV> at the
|
|
time the exception was thrown is also displayed. usually this
|
|
is the parameters that were passed in, but it may include several
|
|
leading C<undef> if C<shift> was used to process the parameter
|
|
list.
|
|
|
|
Here are some samples illustrating different level of debugging
|
|
information and what happens when the filter is turned off
|
|
|
|
#---------------------------------------------------
|
|
#Sample exception STRINGIFY=0 running on thread 5
|
|
#---------------------------------------------------
|
|
|
|
Exception! Mayhem! and then ...
|
|
|
|
#---------------------------------------------------
|
|
#Sample exception STRINGIFY=1 running on thread 5
|
|
#---------------------------------------------------
|
|
|
|
Exception! Mayhem! and then ...
|
|
at file Exception/Lite.t, line 307 in main::weKnowBetterThanYou, pid=24986, tid=5
|
|
|
|
#---------------------------------------------------
|
|
#Sample exception STRINGIFY=2 running on thread 4
|
|
#---------------------------------------------------
|
|
|
|
Exception! Mayhem! and then ...
|
|
at file Exception/Lite.t, line 307 in main::weKnowBetterThanYou, pid=24986, tid=4
|
|
via file Exception/Lite.t, line 281 in main::notAWhatButAWho
|
|
via file Exception/Lite.t, line 373 in main::__ANON__
|
|
via file Exception/Lite.t, line 374 in <package: main>
|
|
|
|
#---------------------------------------------------
|
|
#Sample exception STRINGIFY=3 running on thread 3
|
|
#---------------------------------------------------
|
|
|
|
Exception! Mayhem! and then ...
|
|
|
|
thrown at file Exception/Lite.t, line 307 in main::weKnowBetterThanYou, pid=24986, tid=3
|
|
reached via file Exception/Lite.t, line 281 in main::notAWhatButAWho
|
|
reached via file Exception/Lite.t, line 362 in main::__ANON__
|
|
reached via file Exception/Lite.t, line 363 in <package: main>
|
|
|
|
Triggered by...
|
|
Exception! Horton hears a hoo!
|
|
rethrown at file Exception/Lite.t, line 315
|
|
|
|
thrown at file Exception/Lite.t, line 316 in main::horton, pid=24986, tid=3
|
|
reached via file Exception/Lite.t, line 310 in main::hoo
|
|
reached via file Exception/Lite.t, line 303 in main::weKnowBetterThanYou
|
|
|
|
#---------------------------------------------------
|
|
#Sample exception STRINGIFY=3 running on thread 2
|
|
#FILTER=OFF (see hidden eval frames)
|
|
#---------------------------------------------------
|
|
|
|
Exception! Mayhem! and then ...
|
|
|
|
thrown at file Exception/Lite.t, line 307 in main::weKnowBetterThanYou, pid=24986, tid=2
|
|
reached via file Exception/Lite.t, line 281 in main::notAWhatButAWho
|
|
reached via file Exception/Lite.t, line 348 in (eval)
|
|
reached via file Exception/Lite.t, line 348 in main::__ANON__
|
|
reached via file Exception/Lite.t, line 350 in (eval)
|
|
reached via file Exception/Lite.t, line 350 in <package: main>
|
|
|
|
Triggered by...
|
|
Exception! Horton hears a hoo!
|
|
rethrown at file Exception/Lite.t, line 315
|
|
|
|
thrown at file Exception/Lite.t, line 316 in main::horton, pid=24986, tid=2
|
|
reached via file Exception/Lite.t, line 310 in (eval)
|
|
reached via file Exception/Lite.t, line 315 in main::hoo
|
|
reached via file Exception/Lite.t, line 303 in (eval)
|
|
reached via file Exception/Lite.t, line 305 in main::weKnowBetterThanYou
|
|
|
|
#---------------------------------------------------
|
|
#Sample exception STRINGIFY=4 running on thread 1
|
|
#FILTER=ON
|
|
#---------------------------------------------------
|
|
|
|
Exception! Mayhem! and then ...
|
|
|
|
thrown at file Exception/Lite.t, line 307 in main::weKnowBetterThanYou, pid=24986, tid=1
|
|
@_=('ARRAY(0x83a8a90)'
|
|
,'rot, rot, rot'
|
|
,'Wikerson brothers'
|
|
,'triculous tripe'
|
|
,'There will be no more talking to hoos who are not!'
|
|
,'black bottom birdie'
|
|
,'from the three billionth flower'
|
|
,'Mrs Tucanella returns with Wikerson uncles and cousins'
|
|
,'sound off! sound off! come make yourself known!'
|
|
,'Apartment 12J'
|
|
,'Jo Jo the young lad'
|
|
,'the whole world was saved by the tiny Yopp! of the smallest of all'
|
|
)
|
|
reached via file Exception/Lite.t, line 281 in main::notAWhatButAWho
|
|
@_=()
|
|
reached via file Exception/Lite.t, line 334 in main::__ANON__
|
|
@_=()
|
|
reached via file Exception/Lite.t, line 335 in <package: main>
|
|
@ARGV=()
|
|
|
|
Triggered by...
|
|
Exception! Horton hears a hoo!
|
|
rethrown at file Exception/Lite.t, line 315
|
|
|
|
thrown at file Exception/Lite.t, line 316 in main::horton, pid=24986, tid=1
|
|
@_=('15th of May'
|
|
,'Jungle of Nool'
|
|
,'a small speck of dust on a small clover'
|
|
,'a person's a person no matter how small'
|
|
)
|
|
reached via file Exception/Lite.t, line 310 in main::hoo
|
|
@_=('Dr Hoovey'
|
|
,'hoo-hoo scope'
|
|
,'Mrs Tucanella'
|
|
,'Uncle Nate'
|
|
)
|
|
reached via file Exception/Lite.t, line 303 in main::weKnowBetterThanYou
|
|
@_=('ARRAY(0x83a8a90)'
|
|
,'rot, rot, rot'
|
|
,'Wikerson brothers'
|
|
,'triculous tripe'
|
|
,'There will be no more talking to hoos who are not!'
|
|
,'black bottom birdie'
|
|
,'from the three billionth flower'
|
|
,'Mrs Tucanella returns with Wikerson uncles and cousins'
|
|
,'sound off! sound off! come make yourself known!'
|
|
,'Apartment 12J'
|
|
,'Jo Jo the young lad'
|
|
,'the whole world was saved by the tiny Yopp! of the smallest of all'
|
|
)
|
|
|
|
|
|
=head3 Custom stringification subroutines
|
|
|
|
The custom stringification subroutine expects one parameter, the
|
|
exception to be stringified. It returns the stringified form of
|
|
the exception. Here is an example of a fairly silly custom
|
|
stringification routine that just prints out the chained messages
|
|
without any stack trace:
|
|
|
|
$Exception::Lite::STRINGIFY = sub {
|
|
my $e=$_[0]; # exception is sole input parameter
|
|
my $sMsg='';
|
|
while ($e) {
|
|
$sMsg .= $e->getMessage() . "\n";
|
|
$e= $e->getChained();
|
|
}
|
|
return $sMsg; # return string repreentation of message
|
|
};
|
|
|
|
=head3 Adding information to the stack trace
|
|
|
|
By default, each frame of the stack trace contains only the file,
|
|
line, containing subroutine, and the state of C<@_> at the time
|
|
C<$sFile>,C<$iLine> was reached.
|
|
|
|
If your custom subroutine needs more information about the stack
|
|
than C<Exception::Lite> normally provides, you can change the
|
|
contents of the stack trace by assigning a custom filter routine
|
|
to C<$Exception::Lite::FILTER>.
|
|
|
|
The arguments to this subroutine are:
|
|
|
|
|
|
($iFrame, $sFile, $iLine $sSub, $aArgs, $iSubFrame, $iLineFrame)
|
|
|
|
where
|
|
|
|
* C<$sFile> is the file of the current line in that frame
|
|
|
|
* C<$iLine> is the line number of current line in that frame
|
|
|
|
* C<$sSub> is the name of the subroutine that contains C<$sFile> and
|
|
C<$iLine>
|
|
|
|
* C<$aArgs> is an array that holds the stringified value of each
|
|
member of @_ at the time the line at C<$sFile>, C<$sLine> was
|
|
called. Usually, this is the parameters passed into C<$sSub>,
|
|
but may not be.
|
|
|
|
* C<$iSubFrame> is the stack frame that provided the name of the sub
|
|
and the contents of $aArgs.
|
|
|
|
* C<$iLineFrame> is the stack frame that provided the file and line
|
|
number for the frame.
|
|
|
|
Please be aware that each line of the stack trace passed into the
|
|
filter subroutine is a composite drawn from two different frames of
|
|
the Perl stack trace, C<$iSubFrame> and C<$iLineFrame>. This
|
|
composition is necessary because the Perl stack trace contains the
|
|
subroutine that was called at C<$sFile>, C<$iLine> rather than the
|
|
subroutine that I<contains> C<$sFile>,C<$iLine>.
|
|
|
|
The subroutine returns 0 or any other false value if the stack frame
|
|
should be omitted. It returns to 1 accept the default stack frame as
|
|
is. If it accepts the stack frame but wants to insert extra data
|
|
in the frame, it returns
|
|
C<[$sFile,$iLine,$sSub,$aArgs, $extra1, $extra2, ...]>
|
|
|
|
The extra data is always placed at the end after the C<$aArgs>
|
|
member.
|
|
|
|
=head3 Stack trace filtering
|
|
|
|
To avoid noise, by default, intermediate frames that are associated
|
|
with a block of code within a subroutine other than an anonymous
|
|
sub (e.g. the frame created by C<eval {...} or do {...} >) are
|
|
omitted from the stack trace.
|
|
|
|
These omissions add to readability for most debugging purposes.
|
|
In most cases one just wants to see which subroutine called which
|
|
other subroutine. Frames created by eval blocks don't provide
|
|
useful information for that purpose and simply clutter up the
|
|
debugging output.
|
|
|
|
However, there are situations where one either wants more or less
|
|
stack trace filtering. Stack filtering can turned on or off or
|
|
customized by setting C<$Exception::Lite::FILTER> to any of the
|
|
following values:
|
|
|
|
Normally the filtering rule is set at the start of the program or
|
|
via the command line. It can also be set anywhere in code, with one
|
|
caveat: an error handling block.
|
|
|
|
=over
|
|
|
|
=item 0
|
|
|
|
Turns all filtering off so that you see each and every frame
|
|
in the stack trace.
|
|
|
|
=item 1
|
|
|
|
Turns on filtering of eval frames only (default)
|
|
|
|
=item C<[ regex1, regex2, ... ]>
|
|
|
|
A list of regexes. If the fully qualified subroutine name matches
|
|
any one of these regular expressions it will be omitted from the
|
|
stack trace.
|
|
|
|
=item C<$regex>
|
|
|
|
A single regular expression. If the fully qualified subroutine name
|
|
matches this regular expression, it will be omitted from the stack
|
|
trace.
|
|
|
|
=item C<$codeReference>
|
|
|
|
The address of a named or anonymous routine that returns a boolean
|
|
value: true if the frame should be includeed, false if it should be
|
|
omitted. For parameters and return value of this subroutine see
|
|
L</Adding information to the stack trace>.
|
|
|
|
|
|
=back
|
|
|
|
If filtering strategies change and an exception is chained, some of
|
|
its stack frames might be lost during the chaining process if the
|
|
filtering strategy that was in effect when the exception was
|
|
generated changes before it is chained to another exception.
|
|
|
|
|
|
=head2 Subclassing
|
|
|
|
To declare a subclass with custom data and methods, use a three step
|
|
process:
|
|
|
|
=over
|
|
|
|
=item *
|
|
|
|
choose an exception superclass. 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.
|
|
|
|
=item *
|
|
|
|
call C<declareExceptionClass> with its C<$bCustom> parameter set
|
|
to 1
|
|
|
|
=item *
|
|
|
|
define a C<_new(...)> method (note the leading underscore _) and
|
|
subclass specific methods in a block that sets the package to
|
|
the subclass package.
|
|
|
|
=back
|
|
|
|
|
|
When the C<$bCustom> flag is set to true, it might be best to think
|
|
of C<declareExceptionClass> as something like C<use base> or
|
|
C<use parent> 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<Exception::Lite> 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<Exception::Lite>
|
|
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:
|
|
|
|
=over
|
|
|
|
=item *
|
|
|
|
a special hash reserved for your subclsses data. You can get
|
|
access to this hash by calling C<_p_getSubclassData()>. You are
|
|
free to add, change, or remove entries in the hash as needed.
|
|
|
|
=item *
|
|
|
|
at the end of its C<new()> method, it calls
|
|
C<< $sClass->_new($self) >>. This is why you must define a C<_new()>
|
|
method in your subclass package block. The C<_new> method is
|
|
responsible for doing additional setup of exception data. Since
|
|
this method is called last it can count on all of the normally
|
|
expected methods and data having been set up, including the
|
|
stack trace and the message generated by the classes format rule
|
|
(if there is one).
|
|
|
|
=back
|
|
|
|
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<Exception::Lite> 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());
|
|
}
|
|
}
|
|
|
|
=head2 Converting other exceptions into Exception::Lite exceptions
|
|
|
|
If you decide that you prefer the stack traces of this package, you
|
|
can temporarily force all exceptions to use the C<Exception::Lite>
|
|
stack trace, even those not generated by your own code.
|
|
|
|
There are two ways to do this:
|
|
|
|
* production code: chaining/wrapping
|
|
|
|
* active debugging: die/warn handlers
|
|
|
|
|
|
=head3 Wrapping and chaining
|
|
|
|
The preferred solution for production code is wrapping and/or
|
|
chaining the exception. Any non-string exception, even one
|
|
of a class not created by C<Exception::Lite> can be chained
|
|
to an C<Exception::Lite> exception.
|
|
|
|
To chain a string exception, you first need to wrap it in
|
|
an exception class. For this purpose you can create a special
|
|
purpose class or use the generic exception class provided by
|
|
the C<Exception::Lite> module: C<Exception::Lite::Any>.
|
|
|
|
If you don't want to chain the exception, you can also just
|
|
rethrow the wrapped exception, as is. Some examples:
|
|
|
|
#-----------------------------------------------------
|
|
# define some classes
|
|
#-----------------------------------------------------
|
|
|
|
# no format rule
|
|
declareExceptionClass('HouseholdRepairNeeded');
|
|
|
|
# format rule
|
|
declareExceptionClass('ProjectDelay'
|
|
, ['The project was delayed % days', qw(days)]);
|
|
|
|
#-----------------------------------------------------
|
|
# chain and/or wrap some exceptins
|
|
#-----------------------------------------------------
|
|
|
|
eval {
|
|
.... some code here ...
|
|
return 1;
|
|
} or do {
|
|
|
|
my $e=$@;
|
|
if (Exception::Lite::isChainable($e)) {
|
|
if ("$e" =~ /project/) {
|
|
|
|
# chain formatted message
|
|
die 'ProjectDelay'->new($e, days => 3);
|
|
|
|
} elsif ("$e" =~ /water pipe exploded/) {
|
|
|
|
# chain unformatted message
|
|
die 'HouseholdRepairNeeded'->new($e, 'Call the plumber');
|
|
|
|
}
|
|
} elsif ($e =~ 'repairman') { #exception is a string
|
|
|
|
# wrapping a scalar exception so it has the stack trace
|
|
# up to this point, but _no_ chaining
|
|
#
|
|
# since the exception is a scalar, the constructor
|
|
# of a no-format exception class will treat the first
|
|
# parameter as a message rather than a chained exception
|
|
|
|
die 'HouseholdRepairNeeded'->new($e);
|
|
|
|
} else {
|
|
|
|
# if we do want to chain a string exception, we need to
|
|
# wrap it first in an exception class:
|
|
|
|
my $eWrapped = Exception::Lite::Any->new($e);
|
|
die 'HouseholdRepairNeeded'
|
|
->new($eWrapped, "Call the repair guy");
|
|
}
|
|
}
|
|
|
|
=head3 Die/Warn Handlers
|
|
|
|
Die/Warn handlers provide a quick and dirty way to at Exception::Lite
|
|
style stack traces to all warnings and exceptions. However,
|
|
it should ONLY BE USED DURING ACTIVE DEBUGGING. They should never
|
|
be used in production code. Setting these handlers
|
|
can interfere with the debugging style and techiniques of other
|
|
programmers and that is not nice.
|
|
|
|
However, so long as you are actiely debugging, setting a die or
|
|
warn handler can be quite useful, especially if a third party module
|
|
is generating an exception or warning and you have no idea where it
|
|
is coming from.
|
|
|
|
To set a die handler, you pass your desired stringify level or
|
|
code reference to C<onDie>:
|
|
|
|
Exception::Lite::onDie(4);
|
|
|
|
This is roughly equivalent to:
|
|
|
|
$SIG{__DIE__} = sub {
|
|
$Exception::Lite::STRINGIFY=4;
|
|
warn 'Exception::Lite::Any'->new('Unexpected death:'.$_[0])
|
|
unless ($^S || Exception::Lite::isException($_[0]));
|
|
};
|
|
|
|
To set a warning handler, you pass your desired stringify level or
|
|
code reference to C<onWarn>:
|
|
|
|
Exception::Lite::onWarn(4);
|
|
|
|
This is roughly equivalent to:
|
|
|
|
$SIG{__WARN__} = sub {
|
|
$Exception::Lite::STRINGIFY=4;
|
|
print STDERR 'Exception::Lite::Any'->new("Warning: $_[0]");
|
|
};
|
|
|
|
Typically these handlers are placed at the top of a test script
|
|
like this:
|
|
|
|
use strict;
|
|
use warnings;
|
|
use Test::More tests => 25;
|
|
|
|
use Exception::Lite;
|
|
Exception::Lite::onDie(4);
|
|
Exception::Lite::onWarn(3);
|
|
|
|
... actual testing code here ...
|
|
|
|
|
|
|
|
=head1 WHY A NEW EXCEPTION CLASS
|
|
|
|
Aren't there enough already? Well, no. This class differs from
|
|
existing classes in several significant ways beyond "lite"-ness.
|
|
|
|
=head2 Simplified integration of properties and messages
|
|
|
|
C<Exception::Lite> simplifies the creation of exceptions by
|
|
minimizing the amount of metadata that needs to be declared for
|
|
each exception and by closely integrating exception properties
|
|
and error messages. Though there are many exception modules
|
|
that let you define message and properties for exceptions, in
|
|
those other modules you have to manually maintain any connection
|
|
between the two either in your code or in a custom subclass.
|
|
|
|
In L<Exception::Class|Exception::Class>, for example, you have to
|
|
do something like this:
|
|
|
|
#... at the start of your code ...
|
|
# notice how exception definition and message format
|
|
# string constant are in two different places and need
|
|
# to be manually coordinated by the programmer.
|
|
|
|
use Exception::Class {
|
|
'Exception::Copy::Mine' {
|
|
fields => [qw(from to)];
|
|
}
|
|
# ... lots of other exceptions here ...
|
|
}
|
|
my $MSG_COPY='Could not copy A.txt to B.txt";
|
|
|
|
... later on when you throw the exception ...
|
|
|
|
# notice the repetition in the use of exception
|
|
# properties; the repetition is error prone and adds
|
|
# unnecessary extra typing
|
|
|
|
my $sMsg = sprintf($MSG_COPY, 'A.txt', 'B.txt');
|
|
Exception::Copy::Mine->throw(error => $sMsg
|
|
, from => 'A.txt'
|
|
, to => 'B.txt');
|
|
|
|
|
|
C<Exception::Lite> provides a succinct and easy to maintain
|
|
method of declaring those same exceptions
|
|
|
|
# the declaration puts the message format string and the
|
|
# class declaration together for the programmer, thus
|
|
# resulting in less maintenence work
|
|
|
|
declareExceptionClass("Exception::Mine::Copy"
|
|
, ["Could not copy %s to %s", qw(from, to) ]);
|
|
|
|
|
|
.... some where else in your code ...
|
|
|
|
|
|
# there is no need to explicitly call sprintf or
|
|
# repetitively type variable names, nor even remember
|
|
# the order of parameters in the format string or check
|
|
# for undefined values. Both of these will produce
|
|
# the same error message:
|
|
# "Could not copy A.txt to B.txt"
|
|
|
|
die "Exception::Mine:Copy"->new(from =>'A.txt', to=>'B.txt');
|
|
die "Exception::Mine:Copy"->new(to =>'B.txt', from=>'A.txt');
|
|
|
|
# and this will politely fill in 'undef' for the
|
|
# property you leave out:
|
|
# "Could not copy A.txt to <undef>"
|
|
|
|
die "Exception::Mine::Copy"->new(from=>'A.txt');
|
|
|
|
|
|
=head2 More intelligent stack trace
|
|
|
|
The vast majority, if not all, of the exception modules on CPAN
|
|
essentially reproduce Carp's presentation of the stack trace. They
|
|
sometimes provide parameters to control the level of detail, but
|
|
make only minimal efforts, if any, to improve on the quality of
|
|
debugging information.
|
|
|
|
C<Exception::Lite> improves on the traditional Perl stack trace
|
|
provided by Carp in a number of ways.
|
|
|
|
=over
|
|
|
|
=item *
|
|
|
|
Error messages are shown in full and never truncated (a problem with
|
|
C<Carp::croak>.
|
|
|
|
=item *
|
|
|
|
The ability to see a list of what called what without the clutter
|
|
of subroutine parameters.
|
|
|
|
=item *
|
|
|
|
The ability to see the context of a line that fails rather than
|
|
a pinhole snapshot of the line itself. Thus one sees
|
|
"at file Foo.pm, line 13 in sub doTheFunkyFunk" rather
|
|
than the contextless stack trace line displayed by nearly every,
|
|
if not all Perl stacktraces, including C<Carp::croak>:
|
|
"called foobar(...) at line 13 in Foo.pm".
|
|
When context rather than line snapshots
|
|
are provided, it is often enough simply to scan the list of what
|
|
called what to see where the error occurred.
|
|
|
|
=item *
|
|
|
|
Automatic filtering of stack frames that do not show the actual
|
|
Flow from call to call. Perl internally creates stack frames for
|
|
each eval block. Seeing these in the stack trace make it harder
|
|
to scan the stack trace and see what called what.
|
|
|
|
=item *
|
|
|
|
The automatic filtering can be turned off or, alternatively
|
|
customized to include/exclude arbitrary stack frames.
|
|
|
|
=item *
|
|
|
|
One can chain together exceptions and then print out what exception
|
|
triggered what other exception. Sometimes what a low level module
|
|
considers important about an exception is not what a higher level
|
|
module considers important. When that happens, the programmer can
|
|
create a new exception with a more relevant error message that
|
|
"remembers" the exception that inspired it. If need be, one can
|
|
see the entire history from origin to destination.
|
|
|
|
=back
|
|
|
|
The "traditional" stack trace smushes together all parameters into
|
|
a single long line that is very hard to read. C<Exception::Lite>
|
|
provides a much more readable parametr listing:
|
|
|
|
=over
|
|
|
|
=item *
|
|
|
|
They are displayed one per line so that they can be easily read
|
|
and distinguished one from another
|
|
|
|
=item *
|
|
|
|
The string value <i>and</i> the normal object representation is
|
|
shown when an object's string conversion is overloaded. That way
|
|
there can be no confusion about whether the actual object or a
|
|
string was passed in as a parameter.
|
|
|
|
=item *
|
|
|
|
It doesn't pretend that these are the parameters passed to the
|
|
subroutine. It is impossible to recreate the actual values in
|
|
the parameter list because the parameter list for any sub is
|
|
just C<@_> and that can be modified when a programmer uses shift
|
|
to process command line arguments. The most Perl can give (through
|
|
its DB module) is the way C<@_> looked at the time the next frame
|
|
in the stack was set up. Instead of positioning the parameters
|
|
as if they were being passed to the subroutine, they are listed
|
|
below the stacktrace line saying "thrown at in line X in
|
|
subroutine Y". In reality, the "parameters" are the value of
|
|
@_ passed to subroutine Y (or @ARGV if this is the entry point),
|
|
or what was left of it when we got to line X.
|
|
|
|
=item
|
|
|
|
A visual hint that leading C<undef>s in C<@_> or C<@ARGV> may be
|
|
the result of shifts rather than a heap of C<undef>s passed into
|
|
the subroutine. This lets the programmer focus on the code, not
|
|
on remembering the quirks of Perl stack tracing.
|
|
|
|
=back
|
|
|
|
=head1 CLASS REFERENCE
|
|
|
|
=head2 Class factory methods
|
|
|
|
=head3 C<declareExceptionClass>
|
|
|
|
declareExceptionClass($sClass);
|
|
declareExceptionClass($sClass, $sSuperclass);
|
|
declareExceptionClass($sClass, $sSuperclass, $bCustom);
|
|
|
|
declareExceptionClass($sClass, $aFormatRule);
|
|
declareExceptionClass($sClass, $sSuperclass, $aFormatRule);
|
|
declareExceptionClass($sClass, $sSuperclass, $aFormatRule
|
|
, $bCustom);
|
|
|
|
Generates a lightweight class definition for an exception class. It
|
|
returns the name of the created class, i.e. $sClass.
|
|
|
|
=over
|
|
|
|
=item C<$sClass>
|
|
|
|
The name of the class (package) to be created. Required.
|
|
|
|
Any legal Perl package name may be used, so long as it hasn't
|
|
already been used to define an exception or any other class.
|
|
|
|
=item C<$sSuperclass>
|
|
|
|
The name of the superclass of C<$sClass>. Optional.
|
|
|
|
If missing or undefed, C<$sClass> will be be a base class
|
|
whose only superclass is C<UNIVERSAL>, the root class of all Perl
|
|
classes. There is no special "Exception::Base" class that all
|
|
exceptions have to descend from, unless you want it that way
|
|
and choose to define your set of exception classes that way.
|
|
|
|
=item C<$aFormatRule>
|
|
|
|
An array reference describing how to use properties to construct
|
|
a message. Optional.
|
|
|
|
If provided, the format rule is essential the same parameters as
|
|
used by sprintf with one major exception: instead of using actual
|
|
values as arguments, you use property names, like this:
|
|
|
|
# insert value of 'from' property in place of first %s
|
|
# insert value of 'to' property in place of first %s
|
|
|
|
[ 'Cannot copy from %s to %s, 'from', 'to' ]
|
|
|
|
When a format rule is provided, C<Exception::Lite> will auto-generate
|
|
the message from the properties whenever the properties are set or
|
|
changed. Regeneration is a lightweight process that selects property
|
|
values from the hash and sends them to C<sprintf> for formatting.
|
|
|
|
Later on, when you are creating exceptions, you simply pass in the
|
|
property values. They can be listed in any order and extra properties
|
|
that do not appear in the message string can also be provided. If
|
|
for some reason the value of a property is unknown, you can assign it
|
|
C<undef> and C<Exception::Lite> will politely insert a placeholder
|
|
for the missing value. All of the following are valid:
|
|
|
|
|
|
# These all generate "Cannot copy A.txt to B.txt"
|
|
|
|
$sClass->new(from => 'A.txt', to => 'B.txt');
|
|
$sClass->new(to => 'B.txt', from => 'A.txt');
|
|
$sClass->new(to => 'B.txt', from => 'A.txt'
|
|
, reason => 'source doesn't exist'
|
|
, seriousness => 4
|
|
);
|
|
$sClass->new(reason => 'source doesn't exist'
|
|
, seriousness => 4
|
|
, to => 'B.txt', from => 'A.txt'
|
|
);
|
|
|
|
# These generate "Cannot copy A.txt to <undef>"
|
|
|
|
$sClass->new(from => 'A.txt');
|
|
$sClass->new(from => 'A.txt', to => 'B.txt');
|
|
|
|
=item C<$bCustom>
|
|
|
|
True if the caller intends to add custom methods and/or a custom
|
|
constructor to the newly declared class. This will force the
|
|
L<Excepton::Lite> to generate some extra methods and data so
|
|
that the subclass can have its own private data area in the class.
|
|
See L</Subclassing> for more information.
|
|
|
|
|
|
=back
|
|
|
|
=head2 Object construction methods
|
|
|
|
=head3 C<new>
|
|
|
|
# class configured for no generation from properties
|
|
|
|
$sClass->new($sMsg);
|
|
$sClass->new($sMsg,$prop1 => $val1, ....);
|
|
$sClass->new($e);
|
|
$sClass->new($e, $sMsg);
|
|
$sClass->new($e, $sMsg,$prop1 => $val1, ....);
|
|
|
|
# class configured to generate messages from properties
|
|
# using a per-class format string
|
|
|
|
$sClass->new($prop1 => $val1, ....);
|
|
$sClass->new($e, $prop1 => $val1, ....);
|
|
|
|
|
|
Creates a new instance of exception class C<$sClass>. The exception
|
|
may be independent or chained to the exception that triggered it.
|
|
|
|
=over
|
|
|
|
=item $e
|
|
|
|
The exception that logically triggered this new exception.
|
|
May be omitted or left undefined. If defined, the new exception is
|
|
considered chained to C<$e>.
|
|
|
|
=item $sMsg
|
|
|
|
The message text, for classes with no autogeneration from properties,
|
|
that is, classes declared like
|
|
|
|
declareExceptionClass($sClass);
|
|
declareExceptionClass($sClass, $sSuperclass);
|
|
|
|
In the constructor, C< $sClass->new($e) >>, the message defaults to
|
|
the message of C<$e>. Otherwise the message is required for any
|
|
class that id declared in the above two ways.
|
|
|
|
=item $prop1 => $val1
|
|
|
|
The first property name and its associated value. There can be
|
|
as many repetitions of this as there are properties. All types
|
|
of exception classes may have property lists.
|
|
|
|
=back
|
|
|
|
If you have chosen to have the message be completely independent
|
|
of properties:
|
|
|
|
declareExceptionClass('A');
|
|
|
|
# unchained exception - print output "Hello"
|
|
|
|
my $e1 = A->new("Hello", importance => 'small', risk => 'large');
|
|
print "$e1\n";
|
|
|
|
# chained exception - print output "Hello"
|
|
|
|
my $e2 = A->new($e1,'Goodbye');
|
|
|
|
$e2->getChained(); # returns $e1
|
|
print $e1->getMessage(); # outputs "Goodbye"
|
|
print $e1; # outputs "Goodbye"
|
|
print $e2->getChained()->getMessage(); # outputs "Hello"
|
|
|
|
|
|
If you have chosen to have the message autogenerated from properties
|
|
your call to C<new> will look like this:
|
|
|
|
$sFormat ='the importance is %s, but the risk is %s';
|
|
declareExceptionClass('B', [ $sFormat, qw(importance risk)]);
|
|
|
|
|
|
# unchained exception
|
|
|
|
my $e1 = B->new(importance=>'small', risk=>'large');
|
|
|
|
$e1->getChained(); # returns undef
|
|
print "$e1\n"; # outputs "The importance is small, but the
|
|
# risk is large"
|
|
|
|
# chained exception
|
|
|
|
$e2 = B->new($e1, importance=>'yink', risk=>'hooboy');
|
|
$e2->getChained(); # returns $e1
|
|
"$e2" # evaluates to "The importance is yink, but
|
|
# the risk is hooboy"
|
|
$e2->getMessage() # same as "$e2"
|
|
$e2->getChained()->getMessage(); # same as "$e1"
|
|
|
|
|
|
|
|
=head2 Object methods
|
|
|
|
=head3 C<getMessage>
|
|
|
|
$e->getMessage();
|
|
|
|
Returns the messsage, i.e. the value displayed when this exception
|
|
is treated as a string. This is the value without line numbers
|
|
stack trace or other information. It includes only the format
|
|
string with the property values inserted.
|
|
|
|
=head3 C<getProperty>
|
|
|
|
$e->getProperty($sName);
|
|
|
|
Returns the property value for the C<$sName> property.
|
|
|
|
=head3 C<isProperty>
|
|
|
|
$e->isProperty($sName)
|
|
|
|
Returns true if the exception has the C<$sName> property, even if
|
|
the value is undefined. (checks existance, not definition).
|
|
|
|
=head3 C<getPid>
|
|
|
|
$e->getPid();
|
|
|
|
Returns the process id of the process where the exception was
|
|
thrown.
|
|
|
|
=head3 C<getPackage>
|
|
|
|
$e->getPackage();
|
|
|
|
Returns the package contining the entry point of the process, i.e.
|
|
the package identified at the top of the stack.
|
|
|
|
|
|
=head3 C<getTid>
|
|
|
|
Returns the thread where the exception was thrown.
|
|
|
|
$e->getTid();
|
|
|
|
=head3 C<getStackTrace>
|
|
|
|
$e->getStackTrace();
|
|
|
|
Returns the stack trace from the point where the exception was
|
|
thrown (frame 0) to the entry point (frame -1). The stack trace
|
|
is structured as an array of arrays (AoA) where each member array
|
|
represents a single lightweight frame with four data per frame:
|
|
|
|
[0] the file
|
|
[1] the line number within the file
|
|
[2] the subroutine where the exception was called. File and
|
|
line number will be within this subroutine.
|
|
[3] a comma delimited string containing string representations
|
|
of the values that were stored in @_ at the time the
|
|
exception was thrown. If shift was used to process the
|
|
incoming subroutine arguments, @_ will usually contain
|
|
several leading undefs.
|
|
|
|
For more information about each component of a stack frame, please
|
|
see the documentation below for the following methods:
|
|
|
|
* C<getFile> - explains what to expect in [0] of stack frame
|
|
|
|
* C<getLine> - explains what to expect in [1] of stack frame
|
|
|
|
* C<getSubroutine> - explains what to expect in [2] of stack frame
|
|
|
|
* C<getArgs> - explains what to expect in [3] of stack frame
|
|
|
|
The frame closest to the thrown exception is numbered 0. In fact
|
|
frame 0, stores information about the actual point where the exception
|
|
was thrown.
|
|
|
|
|
|
=head3 C<getFrameCount>
|
|
|
|
$e->getFrameCount();
|
|
|
|
Returns the number of frames in the stack trace.
|
|
|
|
=head3 C<getFile>
|
|
|
|
$e->getFile(0); # gets frame where exception was thrown
|
|
$e->getFile(-1); # gets entry point frame
|
|
|
|
$e->getFile(); # short hand for $e->getFile(0)
|
|
$e->getFile($i);
|
|
|
|
Without an argument, this method returns the name of the file where
|
|
the exception was thrown. With an argument it returns the name of
|
|
the file in the C<$i>th frame of the stack trace.
|
|
|
|
Negative values of C<$i> will be counted from the entry point with
|
|
C<-1> representing the entry point frame, C<-2> representing the
|
|
first call made within the script and so on.
|
|
|
|
=head3 C<getLine>
|
|
|
|
$e->getLine(0); # gets frame where exception was thrown
|
|
$e->getLine(-1); # gets entry point frame
|
|
|
|
$e->getLine(); # short hand for $e->getLine(0)
|
|
$e->getLine($i);
|
|
|
|
Without an argument, this method returns the line number where the
|
|
exception was thrown. With an argument it returns the line number
|
|
in the C<$i>th frame of the stack trace.
|
|
|
|
Negative values of C<$i> will be counted from the entry point with
|
|
C<-1> representing the entry point frame, C<-2> representing the
|
|
first call made within the script and so on.
|
|
|
|
=head3 C<getSubroutine>
|
|
|
|
$e->getSubroutine(0); # gets frame where exception was thrown
|
|
$e->getSubroutine(-1); # gets entry point frame
|
|
|
|
$e->getSubroutine(); # short hand for $e->getSubroutine(0)
|
|
$e->getSubroutine($i);
|
|
|
|
Without an argument, this method returns the name of the subroutine
|
|
where this exception was created via C<new(...)>. With an argument
|
|
it returns the value of the subroutine (or package entry point) in
|
|
the C<$i>th frame of the stack trace.
|
|
|
|
Negative values of C<$i> will be counted from the entry point with
|
|
C<-1> representing the entry point frame, C<-2> representing the
|
|
first call made within the script and so on.
|
|
|
|
Note: This is not the same value as returned by C<caller($i)>. C<caller> returns the name of the subroutine that was being called
|
|
at the time of death rather than the containing subroutine.
|
|
|
|
The subroutine name in array element [2] includes the package name
|
|
so it will be 'MyPackage::Utils::doit' and not just 'doit'. In the
|
|
entry point frame there is, of course, no containing subroutine so
|
|
the value in this string is instead the package name embedded in
|
|
the string "<package: packageName>".
|
|
|
|
|
|
=head3 C<getArgs>
|
|
|
|
$e->getArgs(0); # gets frame where exception was thrown
|
|
$e->getArgs(-1); # gets entry point frame
|
|
|
|
$e->getArgs(); # short hand for $e->getArgs(0)
|
|
$e->getArgs($i);
|
|
|
|
Without an argument, this method returns the value of C<@_> (or
|
|
C<@ARGV> for an entry point frame) at the time the exception was
|
|
thrown. With an argument it returns the name of
|
|
the file in the C<$i>th frame of the stack trace.
|
|
|
|
Negative values of C<$i> will be counted from the entry point with
|
|
C<-1> representing the entry point frame, C<-2> representing the
|
|
first call made within the script and so on.
|
|
|
|
@_, is the best approximation Perl provides for the arguments
|
|
used to call the subroutine. At the start of the subroutine it does
|
|
in fact reflect the parameters passed in, but frequently programmers
|
|
will process this array with the C<shift> operator which will set
|
|
leading arguments to C<undef>. The debugger does not cache the
|
|
oiginal value of @_, so all you can get from its stack trace is the
|
|
value at the time the exception was thrown, not the value when the
|
|
subroutine was entered.
|
|
|
|
=head3 C<getPropagation>
|
|
|
|
$e->getPropagation();
|
|
|
|
Returns an array reference with one element for each time this
|
|
exception was caught and rethrown using either Perl's own rethrow
|
|
syntax C<$@=$e; die;> or this packages: C<< die->rethrow() >>.
|
|
|
|
Each element of the array contains a file and line number where
|
|
the exception was rethrown:
|
|
|
|
[0] file where exception was caught and rethrown
|
|
[1] line number where the exception was caught and rethrown
|
|
|
|
Note: do not confuse the stack trace with propagation. The stack
|
|
trace is the sequence of calls that were made I<before> the
|
|
exception was thrown. The propagation file and line numbers
|
|
refer to where the exception was caught in an exception handling
|
|
block I<after> the exception was thrown.
|
|
|
|
Generally, bad data is the reason behind an exception. To see
|
|
where the bad data came from, it is generally more useful to
|
|
look at the stack and see what data was passed down to the point
|
|
where the exception was generated than it is to look at where
|
|
the exception was caught after the fact.
|
|
|
|
=head3 C<getChained>
|
|
|
|
my $eChained = $e->getChained();
|
|
|
|
Returns the chained exception, or undef if the exception is not
|
|
chained. Chained exceptions are created by inserting the triggering
|
|
exception as the first parameter to C<new(...)>.
|
|
|
|
# class level format
|
|
MyException1->new(reason=>'blahblahblah'); #unchained
|
|
MyException1->new($e, reason=>'blahblahblah'); #chained
|
|
|
|
# no format string
|
|
MyException1->new('blahblahblah'); #unchained
|
|
MyException1->new($e, reason=>'blahblahblah'); #chained
|
|
|
|
|
|
The chained exception can be a reference to any sort of data. It
|
|
does not need to belong to the same class as the new exception,
|
|
nor does it even have to belong to a class generated by
|
|
C<Exception::Lite>. Its only restriction is that it may not be
|
|
a scalar(string, number, ec). To see if an exception
|
|
may be chained you can call C<Exception::Lite::isChainable()>:
|
|
|
|
if (Exception::Lite::isChainable($e)) {
|
|
die MyException1->new($e, reason=>'blahblahblah');
|
|
} else {
|
|
|
|
# another alternative for string exceptions
|
|
my $eWrapper=MyWrapperForStringExceptions->new($e);
|
|
die MyException1->new($eWrapper, reason=>'blahblahblah');
|
|
|
|
# another alternative for string exceptions
|
|
die MyException1->new($eWrapper, reason=>"blahblahblah: $e");
|
|
}
|
|
|
|
|
|
=head3 C<rethrow>
|
|
|
|
$e->rethrow();
|
|
$e->rethrow($prop => $newValue); # format rule
|
|
|
|
$e->rethrow($newMsg, $p1 => $newValue); # no format rule
|
|
$e->rethrow(undef, $pl => $newValue); # no format rule
|
|
$e->rethrow($sNewMsg); # no format rule
|
|
|
|
|
|
Propagates the exception using the method (C<PROPAGATE>) as would
|
|
be called were one to use Perl's native 'rethrow' syntax,
|
|
C<$@=$e; die>.
|
|
|
|
The first form with no arguments simply rethrows the exception.
|
|
The remain formats let one override property values and/or update
|
|
the message. The argument list is the same as for C<new> except
|
|
that exceptions with no or object level format strings may have
|
|
an undefined message.
|
|
|
|
For class format exceptions, the message will automatically be
|
|
updated if any of the properties used to construct it have changed.
|
|
|
|
For exception classes with no formatting, property and message
|
|
changes are independent of each other. If C<$sMsg> is set to C<undef>
|
|
the properties will be changed and the message will be left alone.
|
|
If C<$sMsg> is provided, but no override properties are provided,
|
|
the message will change but the properties will be left untouched.
|
|
|
|
=head3 C<_p_getSubclassData>
|
|
|
|
Method for internal use by custom subclasses. This method retrieves
|
|
the data hash reserved for use by custom methods.
|
|
|
|
|
|
=head1 SEE ALSO
|
|
|
|
=head2 Canned test modules
|
|
|
|
Test modules for making sure your code generates the right
|
|
exceptions. They work with any OOP solution, even C<Exception::Lite>
|
|
|
|
* L<Test::Exception|Test::Exception> - works with any OOP solution
|
|
|
|
* L<Test::Exception::LessClever|Test::Exception::LessClever> - works
|
|
with any OOP solution
|
|
|
|
=head2 Alternate OOP solutions
|
|
|
|
=head3 L<Exception::Class|Exception::Class>
|
|
|
|
This module has a fair number of non-core modules. There are several
|
|
extension modules. Most are adapter classes that convert exceptions
|
|
produced by popular CPAN modules into Exception::Class modules:
|
|
|
|
* L<Exception::Class::Nested|Exception::Class::Nested> - changes
|
|
the syntax for declaring exceptions.
|
|
|
|
* L<MooseX::Error::Exception::Class|MooseX::Error::Exception::Class>
|
|
- converts Moose exceptions to
|
|
C<Exception::Class> instances.
|
|
|
|
* L<HTTP::Exception|HTTP::Exception> - wrapper around HTTP exceptions
|
|
|
|
* L<Mail::Log::Exceptions|Mail::Log::Exceptions> - wrapper around
|
|
Mail::Log exceptions
|
|
|
|
* L<Exception::Class::DBI|Exception::Class::DBI> - wrapper around
|
|
DBI exceptions
|
|
|
|
* L<Error::Exception|Error::Exception> - prints out exception
|
|
properties as part of exception stringification.
|
|
|
|
It takes a heavy approach to OOP, requiring all properties to be
|
|
predeclared. It also stores a lot of information about an exception,
|
|
not all of which is likely to be important to the average user, e.g.
|
|
pid, uid, guid and even the entire stack trace.
|
|
|
|
There is no support for auto-generating messages based on
|
|
properties.
|
|
|
|
For an extended discussion of C<Exception::Class>, see
|
|
L<http://www.drdobbs.com/web-development/184416129>.
|
|
|
|
=head3 L<Exception::Base|Exception::Base>
|
|
|
|
A light weight version of L<Exception::Class|Exception::Class>.
|
|
Uses only core modules but is fairly new and has no significant
|
|
eco-system of extensions (yet).
|
|
Like C<Exception::Class> properties must be explicitly declared and
|
|
there is no support for autogenerating messages based on properties.
|
|
|
|
|
|
=head3 L<Class::Throwable|Class::Throwable>
|
|
|
|
Another light weight version of L<Exception::Class|Exception::Class>.
|
|
Unlike C<Exception::Class> you can control the amount of system
|
|
state and stack trace information stored at the time an exception
|
|
is generated.
|
|
|
|
=head2 Syntactic sugar solutions
|
|
|
|
Syntactical sugar solutions allow java like try/catch blocks to
|
|
replace the more awkward C<die>, C<eval/do>, and C<$@=$e; die>
|
|
pattern. Take care in chosing these methods as they sometimes
|
|
use coding strategies known to cause problems:
|
|
|
|
=over
|
|
|
|
=item *
|
|
|
|
overriding signal handlers - possible interference with your own
|
|
code or third party module use of those handlers.
|
|
|
|
=item *
|
|
|
|
source code filtering - can shift line numbers so that the reported
|
|
line number and the actual line number may not be the same.
|
|
|
|
=item *
|
|
|
|
closures - there is a apparently a problem with nested closures
|
|
causing memory leaks in some versions of Perl (pre 5.8.4). This
|
|
has been since fixed since 5.8.4.
|
|
|
|
=back
|
|
|
|
Modules providing syntactic sugar include:
|
|
|
|
* L<Try::Catch|Try::Catch>
|
|
|
|
* L<Try::Tiny|Try::Tiny>
|
|
|
|
* C<Error|Error>
|
|
|
|
* L<Exception::Caught|Exception::Caught>
|
|
|
|
* L<Exception::SEH|Exception::SEH>
|
|
|
|
* C<Exception|Exception>
|
|
|
|
* L<Exception::Class::TryCatch|Exception::Class::TryCatch> - extension of L<Exception::Class|Exception::Class>
|
|
|
|
* L<Exception::Class::TCF|Exception::Class::TCF> - extension of L<Exception::Class|Exception::Class>
|
|
|
|
|
|
=head1 EXPORTS
|
|
|
|
No subroutines are exported by default. See the start of the synopsis
|
|
for optional exports.
|
|
|
|
|
|
=head1 AUTHOR
|
|
|
|
Elizabeth Grace Frank-Backman
|
|
|
|
=head1 COPYRIGHT
|
|
|
|
Copyright (c) 2011 Elizabeth Grace Frank-Backman.
|
|
All rights reserved.
|
|
|
|
=head1 LICENSE
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the same terms as Perl itself.
|