Exception extensions

These are some extensions to Exception that I've found useful recently. Maybe someone else will too.

The #details method outputs something like what ruby itself does when you see an exception in stderr. However, it returns a string, and you can control the threshold for removing informative lines with '...'. Also, the output is formatted more nicely, IMO.

The #reraise_as method is useful when you want to catch something that's fairly opaque, like NameError, or NoMethodErr, and, using a more descriptive and app-specific exception class, raise it again, but with the same backtrace.

Some example code is included.

class Exception
   # More or less reproduces ruby's default exception reporting in
   # the returned string, except truncation can be controlled with
   # +maxlines+: +nil+ means no limit, an integer value means at
   # most <tt>maxlines/2</tt> frames from each of the top and bottom
   # of the stack are reported.
   def details(maxlines=nil)
     bt = backtrace.dup
     bt0 = bt.shift
     if maxlines and maxlines < bt.size
       ml, r = maxlines.divmod(2)
       bt = bt[0...ml+r] + ["..."] + bt[-(ml==0 ? 1 : ml)..-1]
     end
     [
       "#{bt0}: #{self.class}:",
       message,
       " from " + bt.join("\n from ")
     ].join("\n")
   end

   # Raise the exception, preserving the backtrace, but with as an
   # instance of class +cl+. The message is preserved by default,
   # but may be set to +msg+.
   def reraise_as(cl, msg = message)
     e = cl.new(msg)
     e.set_backtrace backtrace
     raise e
   end
end

class FooError < StandardError; end

def foo
   bar(10)
rescue StandardError => e
   puts e.details(5)
   puts "*"*40
   e.reraise_as(FooError)
end

def bar(try)
   if try <= 0
     raise " bar problem:\n can't find a bar\n anywhere around here"
   else
     bar(try-1)
   end
end

foo

EXAMPLE OUTPUT:

reraise.rb:43:in `bar': RuntimeError:
   bar problem:
     can't find a bar
     anywhere around here
         from reraise.rb:45:in `bar'
         from ...
         from reraise.rb:34:in `foo'
         from reraise.rb:49

···

****************************************
reraise.rb:43:in `bar': bar problem: (FooError)
     can't find a bar
     anywhere around here from reraise.rb:45:in `bar'
         from reraise.rb:45:in `bar'
         from reraise.rb:34:in `foo'
         from reraise.rb:49

"Joel VanderWerf" <vjoel@PATH.Berkeley.EDU> schrieb im Newsbeitrag news:418D989C.1010303@path.berkeley.edu...

These are some extensions to Exception that I've found useful recently. Maybe someone else will too.

The #details method outputs something like what ruby itself does when you see an exception in stderr. However, it returns a string, and you can control the threshold for removing informative lines with '...'. Also, the output is formatted more nicely, IMO.

The #reraise_as method is useful when you want to catch something that's fairly opaque, like NameError, or NoMethodErr, and, using a more descriptive and app-specific exception class, raise it again, but with the same backtrace.

This both is quite nice. But does the reraise_as really work as expected? As far as I can see the set_backtrace is rendered useless because when the secondary exception is raise the stack trace is overwritten (which is quite logical btw). With your code in x.rb I get

$ ruby x.rb
x.rb:44:in `bar': RuntimeError:
  bar problem:
    can't find a bar
    anywhere around here
        from x.rb:46:in `bar'
        from ...
        from x.rb:35:in `foo'
        from x.rb:50

···

****************************************
x.rb:27:in `reraise_as': bar problem: (FooError)
    can't find a bar
    anywhere around here from x.rb:39:in `foo'
        from x.rb:50

Line 27 is the one with "raise e". The output is the same if I comment line 26 ("e.set_backtrace backtrace").

Probably an approach similar to Java 1.4 exception chaining is better: there each exception has an optional "cause" (another exception) and printing of the stack trace is modified that it prints this stack trace and then the cause's trace. The nice thing about this feature is that no stack frame is printed twice, i.e. the exceptions trace is shortened because the cause will contain similar frames. Also this works recursive, i.e., if the cause has a cause itself etc.

Kind regards

    robert

Robert Klemme schrieb:

(...) But does the reraise_as really work as expected? As far as I can see the set_backtrace is rendered useless because when the secondary exception is raise the stack trace is overwritten (which is quite logical btw).

> (...)

Kernel#raise can take a backtrace as the third parameter. So reraise_as could be implemented as:

   class Exception
      # Raise the exception, preserving the backtrace, but as an
      # instance of class +cl+. The message is preserved by default,
      # but may be set to +msg+.
      def reraise_as(cl, msg = message)
        raise cl, msg, backtrace
      end
   end

Regards,
Pit