It's often useful to be able to get the most recent exception, even if your code doesn't control the lifecycle of that exception. Imagine that you want to add basic crash detection to your application. You'd like to log extra info about any crash that happens as a result of an uncaught exception.
The first step is to add a handler that's run whenever your application exits. It's super easy to do this via the Ruby kernel's at_exit method.
at_exit
puts "the app exited"
end
But how can we know if the exit callback was invoked as a result of an exception? Well, Ruby provides the cryptically named $!
global variable. It contains the most recently raised exception that has occurred somewhere in the current call stack.
It's trivial to use $!
to detect if the program is being exited due to an exception. It looks something like this:
at_exit do
save_error_to_log($!) if $!
end
The limitations of $!
Unfortunately, the $!
method only works if the exception occurred somewhere in the current call stack. If you rescue an exception, then try to access $!
outside of the rescue clause, you'll get nil.
begin
raise "x"
rescue
puts $! # => RuntimeError
end
puts $! # => nil
This means that $!
is pretty useless inside of a shell like IRB. Often in IRB, I'll run a method and get an exception. Sometimes I'd like to get ahold of that exception object. But $!
doesn't work for this.
irb(main):001:0> 1/0
ZeroDivisionError: divided by 0
from (irb):1:in `/'
irb(main):002:0> $!
=> nil
Working around $! with PRY
PRY gets around the limitations of $!
by adding its own local variable, _ex_
. This variable contains the most recent uncaught exception.
[1] pry(main)> raise "hi"
RuntimeError: hi
from (pry):1:in `__pry__'
[2] pry(main)> _ex_
=> #<RuntimeError: hi>
The reason that PRY is able to do this is because there are not really any uncaught exceptions inside of PRY or IRB. The shell itself catches the exceptions and displays them as nicely-formatted error messages.
I've copied the relevant bits of the PRY source below. You can see that the code that evaluates your commands is wrapped inside of a begin/rescue/end block. When a rescuable exception occurs, PRY saves the exception to self.last_exception and it later gets assigned to _ex_
.
# Excerpted from the PRY source at https://github.com/pry/pry/blob/623306966bfa86890ac182bc8375ec9699abe90d/lib/pry/pry_instance.rb#L273
begin
if !process_command_safely(line)
@eval_string << "#{line.chomp}\n" if !line.empty? || !@eval_string.empty?
end
rescue RescuableException => e
self.last_exception = e
result = e
Pry.critical_section do
show_result(result)
end
return
end
Require English
Perhaps you find variable names like $!
a little hard on the eyes? Fortunately, Ruby includes a module called "English" which provides english-language versions of many global variables which otherwise look like robot cusswords.
The synonym for $!
is $ERROR_INFO
. You can use it wherever you'd normally use $!
.
require "English"
begin
raise "x"
rescue
puts $ERROR_INFO # => RuntimeError
end
And although most of the other english equivalents have nothing whatsoever to do with the topic of this blog post, I'm including them for kicks. English variables are on the left. The originals are on the right.
$ERROR_INFO | $! |
$ERROR_POSITION | $@ |
$FS | $; |
$FIELD_SEPARATOR | $; |
$OFS | $, |
$OUTPUT_FIELD_SEPARATOR | $, |
$RS | $/ |
$INPUT_RECORD_SEPARATOR | $/ |
$ORS | $\ |
$OUTPUT_RECORD_SEPARATOR | $\ |
$INPUT_LINE_NUMBER | $. |
$NR | $. |
$LAST_READ_LINE | $_ |
$DEFAULT_OUTPUT | $> |
$DEFAULT_INPUT | $< |
$PID | $$ |
$PROCESS_ID | $$ |
$CHILD_STATUS | $? |
$LAST_MATCH_INFO | $~ |
$IGNORECASE | $= |
$ARGV | $* |
$MATCH | $& |
$PREMATCH | $` |
$POSTMATCH | $‘ |
$LAST_PAREN_MATCH | $+ |