exception extensions

  • Thread starter Joel VanderWerf
  • Start date
J

Joel VanderWerf

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 reraise.rb:45:in `bar'
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:45:in `bar'
from reraise.rb:45:in `bar'
from reraise.rb:45:in `bar'
from reraise.rb:45:in `bar'
from reraise.rb:45:in `bar'
from reraise.rb:45:in `bar'
from reraise.rb:45:in `bar'
from reraise.rb:45:in `bar'
from reraise.rb:34:in `foo'
from reraise.rb:49
 
R

Robert Klemme

Joel VanderWerf said:
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 x.rb:46:in `bar'
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
 
P

Pit Capitain

Robert said:
(...) 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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,777
Messages
2,569,604
Members
45,225
Latest member
Top Crypto Podcasts

Latest Threads

Top