Module: DevUtils::Debug
- Defined in:
- lib/dev-utils/debug.rb,
lib/dev-utils/debug/irb.rb,
lib/dev-utils/debug/log.rb
Overview
DevUtils::Debug
contains methods to aid debugging Ruby programs, although when using these methods, you don’t care about the module; it is included into the top-level when you require 'dev-utils/debug'
.
The methods are:
-
#breakpoint, for escaping to IRB from a running program, with local environment intact;
-
#debug, for logging debugging messages to a zero-conf logfile; and
-
#trace, for tracing expressions to that same file.
Planned features include a method for determining the difference between two complex objects.
Constant Summary collapse
- DEBUGLOG =
The target of logging messages from debug and trace.
Logger.new(File.new('debug.log', 'w'))
- TRACE_STYLES =
:nodoc:
{}
Instance Method Summary collapse
-
#breakpoint(name = nil, context = nil, &block) ⇒ Object
(also: #break_point, #goirb)
- Method
- breakpoint Aliases
-
break_point, goirb.
-
#debug(message) ⇒ Object
Write message to the debugging log.
-
#logfile=(x) ⇒ Object
Planned feature; not yet implemented.
-
#logger=(x) ⇒ Object
Planned feature; not yet implemented.
-
#trace(expr, style = :p) ⇒ Object
Prints a trace message to DEBUGLOG (at debug level).
Instance Method Details
#breakpoint(name = nil, context = nil, &block) ⇒ Object Also known as: break_point, goirb
- Method
-
breakpoint
- Aliases
-
break_point, goirb
Description
This will pop up an interactive ruby session from whereever it is called in a Ruby application. In IRB you can examine the environment of the break point, peeking and poking local variables, calling methods, viewing the stack (with caller
), etc.
This is like setting breakpoints in a debugger, except that you are running the program normally (debuggers tend to run programs more slowly). Debuggers are generally more flexible, but this is a good lightweight solution for many cases. You can not step through the code with this technique. But you can, of course, set multiple breakpoints. And you can make breakpoints conditional.
You can force a breakpoint to return a certain value. This is typically only useful if the breakpoint is the last value in a method, as this will cause the method to return a different value than normal. This is demonstrated in the example below.
You can also give names to break points which will be used in the message that is displayed upon execution of them. This helps to differentiate them at runtime if you have set several breakpoints in the code.
Parameters
- name
-
A String to identify the breakpoint, giving a more informative message.
- context
-
Any object; IRB will use this as its context. The default is the current scope’s binding, which is nearly always what you will want.
- block
-
Will be executed when the breakpoint returns normally. Bypassed if you
throw :debug_return,
value from IRB. Unless you are planning to use thedebug_return
feature for a given breakpoint, you don’t need to worry about the block.
Typical Invocation
breakpoint # Plain message.
breakpoint "Person#name" # More informative message.
breakpoint { normal_return_value }
Example
Here’s a sample of how breakpoints should be placed:
require 'dev-utils/debug'
class Person
def initialize(name, age)
@name, @age = name, age
breakpoint "Person#initialize"
end
attr_reader :age
def name
breakpoint "Person#name" { @name }
end
end
person = Person.new("Random Person", 23)
puts "Name: #{person.name}"
And here is a sample debug session:
Executing break point "Person#initialize" at file.rb:4 in `initialize'
irb(#<Person:0x292fbe8>):001:0> <b>local_variables</b>
=> ["name", "age", "_", "__"]
irb(#<Person:0x292fbe8>):002:0> [name, age]
=> ["Random Person", 23]
irb(#<Person:0x292fbe8>):003:0> [@name, @age]
=> ["Random Person", 23]
irb(#<Person:0x292fbe8>):004:0> self
=> #<Person:0x292fbe8 @age=23, @name="Random Person">
irb(#<Person:0x292fbe8>):005:0> @age += 1; self
=> #<Person:0x292fbe8 @age=24, @name="Random Person">
irb(#<Person:0x292fbe8>):006:0> exit
Executing break point "Person#name" at file.rb:9 in `name'
irb(#<Person:0x292fbe8>):001:0> throw(:debug_return, "Overriden name")
Name: Overriden name
This example is explored more thoroughly at dev-utils.rubyforge.org/DebuggingAids.html.
Credits
Joel VanderWerf and Florian Gross have contributed the code and documentation for this method.
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/dev-utils/debug/irb.rb', line 99 def breakpoint(name = nil, context = nil, &block) file, line, method = *caller.first.match(/^(.+?):(\d+)(?::in `(.*?)')?/).captures = "Executing breakpoint #{name.inspect if name} at #{file}:#{line}" << " in '#{method}'" if method body = lambda do |_context| puts catch(:debug_return) do |value| IRB.start_session(IRB::WorkSpace.new(_context)) block.call if block end end # Run IRB with the given context, if it _is_ given. return body.call(context) if context # Otherwise, run IRB with the parent scope's binding, giving access to # the local variables of the method that called method. Binding.of_caller do |binding_context| body.call(binding_context) end end |
#debug(message) ⇒ Object
Write message to the debugging log.
The debugging log is a zero-conf logfile. Here is an example usage:
$ cat example.rb
require 'dev-utils/debug'
debug "Setting variables x and y"
x = 5; y = 17
trace 'x + y'
puts "Finished"
$ ruby example.rb
Finished
$ cat debug.log
D, [#244] DEBUG : Setting variables x and y
D, [#244] DEBUG : x + y = 22
Simply with require 'dev-utils/debug'
, you have availed yourself of a handy debugging log file which you don’t have to create.
See also the trace method.
44 45 46 |
# File 'lib/dev-utils/debug/log.rb', line 44 def debug() DEBUGLOG.debug end |
#logfile=(x) ⇒ Object
Planned feature; not yet implemented.
103 104 105 |
# File 'lib/dev-utils/debug/log.rb', line 103 def logfile=(x) warn 'logfile= is not implemented; ignoring' end |
#logger=(x) ⇒ Object
Planned feature; not yet implemented.
98 99 100 |
# File 'lib/dev-utils/debug/log.rb', line 98 def logger=(x) warn 'logger= is not implemented; ignoring' end |
#trace(expr, style = :p) ⇒ Object
Prints a trace message to DEBUGLOG (at debug level). Useful for emitting the value of variables, etc. Use like this:
x = y = 5
trace 'x' # -> 'x = 5'
trace 'x ** y' # -> 'x ** y = 3125'
If you have a more complicated value, like an array of hashes, then you’ll probably want to use an alternative output format. For instance:
trace 'value', :yaml
Valid output format values (the style parameter) are:
:p :inspect
:pp (pretty-print, using 'pp' library)
:s :to_s
:y :yaml :to_yaml (using the 'yaml' library')
The default is :p
.
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/dev-utils/debug/log.rb', line 70 def trace(expr, style=:p) unless expr.respond_to? :to_str = "trace: Can't evaluate the given value: #{caller.first}" DEBUGLOG.warn else Binding.of_caller do |b| value = b.eval(expr.to_str) formatter = TRACE_STYLES[style] || :inspect case formatter when :pp then require 'pp' when :y, :yaml, :to_yaml then require 'yaml' end value_s = value.send(formatter) = "#{expr} = #{value_s}" lines = .split(/\n/) indent = " " DEBUGLOG.debug lines.shift # First line unindented. lines.each do |line| DEBUGLOG.debug(indent + line) # Other lines indented. end end end end |