The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Exception::Lite - light weight exception handling class with smart stack tracing, chaining, and localization support.

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 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);

DESCRIPTION

The 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.

Features

This module provides a light weight but powerful exception class that

  • 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 carp and other exception modules on CPAN (as of 12/2010). For further discussion and a sample, see "More intelligent stack trace".

  • gives the user full control over the amount of debugging information displayed when exceptions are thrown.

  • permits global changes to the amount of debugging information displayed via the command line.

  • 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.

  • 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.

  • 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 Scalar::Util::refaddr().

  • does not interfere with signal handlers or the normal Perl syntax and the assumptions of Perl operators.

  • can be easily extended and subclassed

Lightweight how?

Despite these features Exception::Lite maintains its "lite" status by

  • using only core modules

  • generating tiny exception classes (30-45LOC per class).

  • 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.

  • 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).

  • 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). Exception::Lite contains about 400 lines when developer comments are excluded). The next smallest all core module is Exception::Base which clocks in at just over 1000 lines after pod and developer comments are excluded).

  • avoiding a heavy-weight base class. Code shared by 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 Exception::Base class contains 700+ lines of code. The base class of Exception::Class has 200 lines of its own but drags in two rather large non-core modules as dependencies: Devel::StackTrace Class::Data::Inheritable.

Exception::Lite has more features (chaining, message/property integration) but less code due to the following factors:

  • working with Perl syntax rather than trying to replace it.

  • 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.

  • respecting separation of concerns. 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 Try::Tiny. Other modules try to double as comprehensive providers of exception related syntactic sugar.

  • not trying to be the only kind of exception that an application uses.

USAGE

Defining Exception Classes

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.

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);

$sClass is the name of the exception class.

$sSuperClass is the name of the superclass, if there is one. The superclass can be any class created by 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.

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

Creating and throwing exceptions

When it comes times to create an exception, you create and throw it like this ($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. 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.

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)

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 Exception::Lite users is 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.

Catching exceptions the Perl way

The most reliable and fastest way to catch an exception is to use 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 eval block ends with return 1; to insure that successful completion of the eval block never results in an undefined value. In certain cases undef is a valid return value for a statement, We don't want to enter the do block for any reason other than a thrown exception.

eval/do is both faster and more reliable than the eval/if which is commonly promoted in Perl programming tutorials:

  # eval ... if

  eval {...};
  if ($@) {....}

It is faster because the do block is executed if and only if the eval fails. By contrast the if must be evaluated both in cases of succes and failure.

eval/do is more reliable because the 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 eval block, it will always evaluate to undef therefore triggering the do block.

On the other hand we can't guarentee that $@ will be defined even if an exception is thrown. If $@ is 0, undef, or an empty string, the if block will never be entered. This happens more often then many programmers realize. When eval exits the eval block, it calls destructors of any my variables. If any of those has an eval statement, then the value of $@ is wiped clean or reset to the exception generated by the destructor.

Within the do block, it is a good idea to save $@ 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 $@ can because Perl lets a programmer override built-ins with user defined routines and those user define routines might set $@ even if the built-in does not.

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. Exception::Lite provides the 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;
     }
   }

Rethrowing exceptions

Perl doesn't have a rethrow statement. To reliably rethrow an exception, you must set $@ to the original exception (in case it has been clobbered during the error handling process) and then call 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 PROPAGATE method to record the file and line number where the exception is rethrown. See getLine, getFile, and getPropagation in the class reference below for more information.

As this Perl syntax is not exactly screaming "I'm a rethrow", 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 PROPAGATE method.

   eval {
     ...
     return 1;
   } or do {
     my $e=$@;

     # rethrow $e
     die $e->rethrow();
   }

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 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 new).

Chaining is efficient, especially if the chained exception is another 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 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 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 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;
    }
  }

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'
             )
  • lines begining with "thrown" indicate a line where a new exception was thrown. If an exception was chained, there might be multiple such lines.

  • lines beginning with "reached via" indicate the path travelled down to the point where the exception was thrown. This is the code that was excuted before the exception was triggered.

  • lines beginning with "rethrown at" indicate the path travelled up the stack by the exception after it was geenerated. Each line indicates a place where the exception was caught and rethrown.

  • 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.

  • @_ 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 @_. 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 Getopt::Long to process @ARGV, @ARGV will be empty reduced to an empty array. If this bothers you, you can localize <@ARGV> before calling GetOptions, like this:

      my %hARGV;
      {
        local @ARGV = @ARGV;
        GetOptions(\%hARGV,...);
      }
  • pid is the process id where the code was running

  • tid is the thread id where the code was running

SPECIAL TOPICS

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 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, 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);
   }

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 @_ and @ARGV.

If you want more or less verbosity or even an entirely different trace, 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 $Exception::Lite::STRINGIFY in the program or -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 $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 $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.

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 @_ or @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 undef if 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'
                )

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
  };

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 @_ at the time $sFile,$iLine was reached.

If your custom subroutine needs more information about the stack than Exception::Lite normally provides, you can change the contents of the stack trace by assigning a custom filter routine to $Exception::Lite::FILTER.

The arguments to this subroutine are:

   ($iFrame, $sFile, $iLine $sSub, $aArgs, $iSubFrame, $iLineFrame)

where

* $sFile is the file of the current line in that frame

* $iLine is the line number of current line in that frame

* $sSub is the name of the subroutine that contains $sFile and $iLine

* $aArgs is an array that holds the stringified value of each member of @_ at the time the line at $sFile, $sLine was called. Usually, this is the parameters passed into $sSub, but may not be.

* $iSubFrame is the stack frame that provided the name of the sub and the contents of $aArgs.

* $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, $iSubFrame and $iLineFrame. This composition is necessary because the Perl stack trace contains the subroutine that was called at $sFile, $iLine rather than the subroutine that contains $sFile,$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 [$sFile,$iLine,$sSub,$aArgs, $extra1, $extra2, ...]

The extra data is always placed at the end after the $aArgs member.

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 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 $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.

0

Turns all filtering off so that you see each and every frame in the stack trace.

1

Turns on filtering of eval frames only (default)

[ 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.

$regex

A single regular expression. If the fully qualified subroutine name matches this regular expression, it will be omitted from the stack trace.

$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 "Adding information to the stack trace".

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.

Subclassing

To declare a subclass with custom data and methods, use a three step process:

  • 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.

  • call declareExceptionClass with its $bCustom parameter set to 1

  • define a _new(...) method (note the leading underscore _) and subclass specific methods in a block that sets the package to the subclass package.

When the $bCustom flag is set to true, it might be best to think of declareExceptionClass as something like use base or 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 Exception::Lite sees the $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 Exception::Lite class. However, on account of $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 _p_getSubclassData(). You are free to add, change, or remove entries in the hash as needed.

  • at the end of its new() method, it calls $sClass->_new($self). This is why you must define a _new() method in your subclass package block. The _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).

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 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());
    }
  }

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 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

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 Exception::Lite can be chained to an 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 Exception::Lite module: 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");
    }
  }

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 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 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 ...

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.

Simplified integration of properties and messages

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 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');

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');

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.

Exception::Lite improves on the traditional Perl stack trace provided by Carp in a number of ways.

  • Error messages are shown in full and never truncated (a problem with Carp::croak.

  • The ability to see a list of what called what without the clutter of subroutine parameters.

  • 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 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.

  • 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.

  • The automatic filtering can be turned off or, alternatively customized to include/exclude arbitrary stack frames.

  • 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.

The "traditional" stack trace smushes together all parameters into a single long line that is very hard to read. Exception::Lite provides a much more readable parametr listing:

  • They are displayed one per line so that they can be easily read and distinguished one from another

  • 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.

  • 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 @_ 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 @_ 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.

  • A visual hint that leading undefs in @_ or @ARGV may be the result of shifts rather than a heap of undefs passed into the subroutine. This lets the programmer focus on the code, not on remembering the quirks of Perl stack tracing.

CLASS REFERENCE

Class factory methods

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.

$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.

$sSuperclass

The name of the superclass of $sClass. Optional.

If missing or undefed, $sClass will be be a base class whose only superclass is 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.

$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, 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 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 undef and 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');
$bCustom

True if the caller intends to add custom methods and/or a custom constructor to the newly declared class. This will force the Excepton::Lite to generate some extra methods and data so that the subclass can have its own private data area in the class. See "Subclassing" for more information.

Object construction methods

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 $sClass. The exception may be independent or chained to the exception that triggered it.

$e

The exception that logically triggered this new exception. May be omitted or left undefined. If defined, the new exception is considered chained to $e.

$sMsg

The message text, for classes with no autogeneration from properties, that is, classes declared like

   declareExceptionClass($sClass);
   declareExceptionClass($sClass, $sSuperclass);

In the constructor, $sClass-new($e) >>, the message defaults to the message of $e. Otherwise the message is required for any class that id declared in the above two ways.

$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.

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 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"

Object methods

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.

getProperty

   $e->getProperty($sName);

Returns the property value for the $sName property.

isProperty

   $e->isProperty($sName)

Returns true if the exception has the $sName property, even if the value is undefined. (checks existance, not definition).

getPid

   $e->getPid();

Returns the process id of the process where the exception was thrown.

getPackage

   $e->getPackage();

Returns the package contining the entry point of the process, i.e. the package identified at the top of the stack.

getTid

Returns the thread where the exception was thrown.

   $e->getTid();

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:

* getFile - explains what to expect in [0] of stack frame

* getLine - explains what to expect in [1] of stack frame

* getSubroutine - explains what to expect in [2] of stack frame

* 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.

getFrameCount

   $e->getFrameCount();

Returns the number of frames in the stack trace.

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 $ith frame of the stack trace.

Negative values of $i will be counted from the entry point with -1 representing the entry point frame, -2 representing the first call made within the script and so on.

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 $ith frame of the stack trace.

Negative values of $i will be counted from the entry point with -1 representing the entry point frame, -2 representing the first call made within the script and so on.

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 new(...). With an argument it returns the value of the subroutine (or package entry point) in the $ith frame of the stack trace.

Negative values of $i will be counted from the entry point with -1 representing the entry point frame, -2 representing the first call made within the script and so on.

Note: This is not the same value as returned by caller($i). 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>".

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 @_ (or @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 $ith frame of the stack trace.

Negative values of $i will be counted from the entry point with -1 representing the entry point frame, -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.

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 $@=$e; die; or this packages: 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 before the exception was thrown. The propagation file and line numbers refer to where the exception was caught in an exception handling block 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.

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 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 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 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");
   }

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 (PROPAGATE) as would be called were one to use Perl's native 'rethrow' syntax, $@=$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 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 $sMsg is set to undef the properties will be changed and the message will be left alone. If $sMsg is provided, but no override properties are provided, the message will change but the properties will be left untouched.

_p_getSubclassData

Method for internal use by custom subclasses. This method retrieves the data hash reserved for use by custom methods.

SEE ALSO

Canned test modules

Test modules for making sure your code generates the right exceptions. They work with any OOP solution, even Exception::Lite

* Test::Exception - works with any OOP solution

* Test::Exception::LessClever - works with any OOP solution

Alternate OOP solutions

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:

* Exception::Class::Nested - changes the syntax for declaring exceptions.

* MooseX::Error::Exception::Class - converts Moose exceptions to Exception::Class instances.

* HTTP::Exception - wrapper around HTTP exceptions

* Mail::Log::Exceptions - wrapper around Mail::Log exceptions

* Exception::Class::DBI - wrapper around DBI exceptions

* 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 Exception::Class, see http://www.drdobbs.com/web-development/184416129.

Exception::Base

A light weight version of Exception::Class. Uses only core modules but is fairly new and has no significant eco-system of extensions (yet). Like Exception::Class properties must be explicitly declared and there is no support for autogenerating messages based on properties.

Class::Throwable

Another light weight version of Exception::Class. Unlike Exception::Class you can control the amount of system state and stack trace information stored at the time an exception is generated.

Syntactic sugar solutions

Syntactical sugar solutions allow java like try/catch blocks to replace the more awkward die, eval/do, and $@=$e; die pattern. Take care in chosing these methods as they sometimes use coding strategies known to cause problems:

  • overriding signal handlers - possible interference with your own code or third party module use of those handlers.

  • source code filtering - can shift line numbers so that the reported line number and the actual line number may not be the same.

  • 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.

Modules providing syntactic sugar include:

* Try::Catch

* Try::Tiny

* Error|Error

* Exception::Caught

* Exception::SEH

* Exception|Exception

* Exception::Class::TryCatch - extension of Exception::Class

* Exception::Class::TCF - extension of Exception::Class

EXPORTS

No subroutines are exported by default. See the start of the synopsis for optional exports.

AUTHOR

Elizabeth Grace Frank-Backman

COPYRIGHT

Copyright (c) 2011 Elizabeth Grace Frank-Backman. All rights reserved.

LICENSE

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

1 POD Error

The following errors were encountered while parsing the POD:

Around line 1249:

Expected text after =item, not a number