-
Notifications
You must be signed in to change notification settings - Fork 0
Exceptions
- The
_ex_
local variable. - Using the
wtf?
command. - Using the
cat --ex
command. - Configuring
cat --ex
. - Using the
edit --ex
command. - Customising the exception handler.
- Configuring which exceptions are caught.
- The pry-rescue plugin.
Pry catches most of the exceptions that your code raises so that you have time to work out what went wrong and fix the problem.
Pry gives you access to the most recently caught exception in a local variable called _ex_
. Unlike Ruby's builtin $!
, this persists until another exception is raised, so can be used for detailed digging.
The _ex_
variable is a 'special local', read more about those here.
For example:
pry(main)> 4/0
ZeroDivisionError: divided by 0
from (pry):1:in `/'
pry(main)> _ex_.message
=> "divided by 0"
pry(main)> puts _ex_.backtrace
(pry):1:in `/'
(pry):1
As an alternative to using _ex_.backtrace
you can use the wtf?
command to display a few lines of the backtrace for the most recent exception. If you want to see more lines, add more question marks or exclamation marks.
Example: Using wtf
to investigate the very top of the backtrace
pry(main)> sdf
NameError: undefined local variable or method `sdf' for main:Object
from (pry):17:in `__pry__'
pry(main)> wtf
Exception: NameError: undefined local variable or method `sdf' for main:Object
--
0: (pry):17:in `<main>'
1: /Users/john/.rvm/gems/ruby-1.9.3-p125/gems/pry-0.9.8.3/lib/pry/pry_instance.rb:251:in `eval'
2: /Users/john/.rvm/gems/ruby-1.9.3-p125/gems/pry-0.9.8.3/lib/pry/pry_instance.rb:251:in `re'
3: /Users/john/.rvm/gems/ruby-1.9.3-p125/gems/pry-0.9.8.3/lib/pry/pry_instance.rb:218:in `rep'
4: /Users/john/.rvm/gems/ruby-1.9.3-p125/gems/pry-0.9.8.3/lib/pry/pry_instance.rb:202:in `block (2 levels) in repl'
Example: Append ?
to see more of the backtrace
pry(main)> sdf
NameError: undefined local variable or method `sdf' for main:Object
from (pry):17:in `__pry__'
pry(main)> wtf?
0: (pry):17:in `<main>'
1: /Users/john/.rvm/gems/ruby-1.9.3-p125/gems/pry-0.9.8.3/lib/pry/pry_instance.rb:251:in `eval'
2: /Users/john/.rvm/gems/ruby-1.9.3-p125/gems/pry-0.9.8.3/lib/pry/pry_instance.rb:251:in `re'
3: /Users/john/.rvm/gems/ruby-1.9.3-p125/gems/pry-0.9.8.3/lib/pry/pry_instance.rb:218:in `rep'
4: /Users/john/.rvm/gems/ruby-1.9.3-p125/gems/pry-0.9.8.3/lib/pry/pry_instance.rb:202:in `block (2 levels) in repl'
5: /Users/john/.rvm/gems/ruby-1.9.3-p125/gems/pry-0.9.8.3/lib/pry/pry_instance.rb:201:in `loop'
6: /Users/john/.rvm/gems/ruby-1.9.3-p125/gems/pry-0.9.8.3/lib/pry/pry_instance.rb:201:in `block in repl'
7: /Users/john/.rvm/gems/ruby-1.9.3-p125/gems/pry-0.9.8.3/lib/pry/pry_instance.rb:200:in `catch'
8: /Users/john/.rvm/gems/ruby-1.9.3-p125/gems/pry-0.9.8.3/lib/pry/pry_instance.rb:200:in `repl'
9: /Users/john/.rvm/gems/ruby-1.9.3-p125/gems/pry-0.9.8.3/lib/pry/pry_class.rb:136:in `start'
Example: Append even more ?
and !
marks to see the full backtrace
pry(main)> sdf
NameError: undefined local variable or method `sdf' for main:Object
from (pry):17:in `__pry__'
pry(main)> wtf?!?!!!
NameError: undefined local variable or method `sdf' for main:Object
from (pry):17:in `__pry__'
0: (pry):17:in `<main>'
1: /Users/john/.rvm/gems/ruby-1.9.3-p125/gems/pry-0.9.8.3/lib/pry/pry_instance.rb:251:in `eval'
2: /Users/john/.rvm/gems/ruby-1.9.3-p125/gems/pry-0.9.8.3/lib/pry/pry_instance.rb:251:in `re'
3: /Users/john/.rvm/gems/ruby-1.9.3-p125/gems/pry-0.9.8.3/lib/pry/pry_instance.rb:218:in `rep'
4: /Users/john/.rvm/gems/ruby-1.9.3-p125/gems/pry-0.9.8.3/lib/pry/pry_instance.rb:202:in `block (2 levels) in repl'
5: /Users/john/.rvm/gems/ruby-1.9.3-p125/gems/pry-0.9.8.3/lib/pry/pry_instance.rb:201:in `loop'
6: /Users/john/.rvm/gems/ruby-1.9.3-p125/gems/pry-0.9.8.3/lib/pry/pry_instance.rb:201:in `block in repl'
7: /Users/john/.rvm/gems/ruby-1.9.3-p125/gems/pry-0.9.8.3/lib/pry/pry_instance.rb:200:in `catch'
8: /Users/john/.rvm/gems/ruby-1.9.3-p125/gems/pry-0.9.8.3/lib/pry/pry_instance.rb:200:in `repl'
9: /Users/john/.rvm/gems/ruby-1.9.3-p125/gems/pry-0.9.8.3/lib/pry/pry_class.rb:136:in `start'
10: /Users/john/.rvm/gems/ruby-1.9.3-p125/gems/pry-0.9.8.3/lib/pry/cli.rb:150:in `block in <top (required)>'
11: /Users/john/.rvm/gems/ruby-1.9.3-p125/gems/pry-0.9.8.3/lib/pry/cli.rb:59:in `call'
12: /Users/john/.rvm/gems/ruby-1.9.3-p125/gems/pry-0.9.8.3/lib/pry/cli.rb:59:in `block in parse_options'
13: /Users/john/.rvm/gems/ruby-1.9.3-p125/gems/pry-0.9.8.3/lib/pry/cli.rb:59:in `each'
14: /Users/john/.rvm/gems/ruby-1.9.3-p125/gems/pry-0.9.8.3/lib/pry/cli.rb:59:in `parse_options'
15: /Users/john/.rvm/gems/ruby-1.9.3-p125/gems/pry-0.9.8.3/bin/pry:16:in `<top (required)>'
16: /Users/john/.rvm/gems/ruby-1.9.3-p125/bin/pry:19:in `load'
17: /Users/john/.rvm/gems/ruby-1.9.3-p125/bin/pry:19:in `<main>'
To see the entire backtrace, pass the -v/--verbose flag, e.g: wtf -v
In addition to giving you easy access to the backtrace, Pry can also show you the actual code that caused problems. This is done with the --ex
argument to the cat command.
For example:
[1] pry(main)> require 'set'
=> true
[2] pry(main)> Set.new(1)
ArgumentError: value must be enumerable
from /home/pryer/ruby/lib/ruby/1.8/set.rb:263:in `merge'
[3] pry(main)> cat --ex
Exception: ArgumentError: value must be enumerable
--
From: /home/pryer/ruby/lib/ruby/1.8/set.rb @ line 263 @ level: 0 of backtrace (of 3).
258: # returns self.
259: def merge(enum)
260: if enum.is_a?(Set)
261: @hash.update(enum.instance_eval { @hash })
262: else
=>263: enum.is_a?(Enumerable) or raise ArgumentError, "value must be enumerable"
264: enum.each { |o| add(o) }
265: end
266:
267: self
268: end
This is partly useful, I know now that the code I'm calling is explicitly expecting an Enumerable, but I didn't give it one. What's not so clear is where it's getting that Enumerable from — after all, I didn't call Set#merge
! Luckily cat --ex
also supports an optional argument, cat --ex N
jumps N lines up the backtrace. To find out how my code ended up in Set#merge
I can use cat --ex 1
:
[4] pry(main)> cat --ex 1
Exception: ArgumentError: value must be enumerable
--
From: /home/pryer/ruby/lib/ruby/1.8/set.rb @ line 76 @ level: 1 of backtrace (of 3).
68: def initialize(enum = nil, &block) # :yields: o
69: @hash ||= Hash.new
70:
71: enum.nil? and return
72:
73: if block
74: enum.each { |o| add(block[o]) }
75: else
=>76: merge(enum)
77: end
78: end
It's now clear why the exception happened, and exactly what I need to do to fix it.
HINT: If you're having problems with the standard library, you should probably use the show-doc command command to read up on what you're doing wrong! In this case show-doc Set#initialize
tells me it should take an Enumerable
, not a number.
It can be effective to use this command in concert with the wtf?
command. Use wtf?
to show the backtrace and cat --ex N
to show the context for a frame in that backtrace.
cat --ex
usually shows five lines of context before, and five lines of context after the exception. You can override this by setting Pry.config.default_window_size
in your ~/.pryrc.
For example:
Pry.config.default_window_size = 10
This will show you a total of 21 lines of context: 10 before, 10 after and the actual line contained within the exception's backtrace.
In the previous section, we saw how cat --ex
can be used to show you how the error came about. In that case it was because we used the library wrong. Sometimes however, the problem is caused by the code being called. In that case we want to be able to fix the code as quickly as possible.
To help you do this, Pry's edit command also supports --ex
in the same manner as cat
. It also takes an optional number to jump up the stacktrace:
[1] pry(main)> require 'set'
=> true
[2] pry(main)> Set.new(1)
ArgumentError: value must be enumerable
from /home/pryer/ruby/lib/ruby/1.8/set.rb:263:in `merge'
[3] pry(main)> edit --ex 1 # N.B. do not try editing the standard library at home!
[4] pry(main)> Set.new(1)
=> #<Set: {1}>
Just like with cat --ex
, if you provide a number to edit --ex N
it will jump N levels up the stacktrace, and if you don't specify N (or set it to 0) it will edit the actual line of code that raised the exception.
By default when Pry catches an exception, it doesn't show you very much information — it only includes the message, and the first line of the backtrace. If you'd prefer to see more information without having to use additional commands, you can override Pry.config.exception_handler
in your ~/.pryrc.
For example, to show a few more lines of backtrace, you might want to do:
Pry.config.exception_handler = proc do |output, exception, _pry_|
output.puts "#{exception}"
output.puts "#{exception.backtrace.first(10)}"
end
Or, if you find that you always use cat --ex
after an exception is raised, you can short-cut by getting Pry to run that for you:
Pry.config.exception_handler = proc do |output, exception, _pry_|
_pry_.run_command 'cat --ex'
end
Because the exception_handler is designed to deal with exceptions thrown by the code that you are currently prying, it should be reasonably robust. For example, it would be silly to have an exception_handler that itself threw an exception. If you do end up throwing an exception by accident, Pry will still catch it and tell you, for example:
(pry) output error: #<ArgumentError, too few arguments>
The other thing to remember is that you need to call output.puts
, the return value of your Proc is ignored, and just using puts
will send output to $stdout
instead of output
.
Pry tries to be clever about which exceptions are caught. While it tries to catch everything that your program might throw by accident, it tries not to catch exceptions that are thrown deliberately. The most obvious example of a deliberate exception is SystemExit
, which is thrown when you call Kernel#exit
, though Pry also allows all SignalException
s to be passed through so that you can run the UNIX kill
command on instances of Pry.
If your program has its own exceptions that should never be caught by Pry, you can add them to Pry.config.exception_whitelist
in your ~/.pryrc. For example to tell Pry not to catch out-of-memory errors:
Pry.config.exception_whitelist = [SignalError, SystemExit, NoMemoryError]
If you find a class of exception that you think Pry should not catch by default, please open a bug report and let us know.
### The pry-rescue pluginFor much more powerful exception handling, look into the pry-rescue plugin.