Module DevUtils::Debug
In: lib/dev-utils/debug/irb.rb
lib/dev-utils/debug/log.rb
lib/dev-utils/debug.rb

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.

Methods

break_point   breakpoint   debug   goirb   logfile=   logger=   trace  

Constants

DEBUGLOG = Logger.new(File.new('debug.log', 'w'))   The target of logging messages from debug and trace.

External Aliases

local_variables -> lv
instance_variables -> iv

Public Instance methods

break_point(name = nil, context = nil, &block)

Alias for breakpoint

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 the debug_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.

[Source]

# 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
    message = "Executing breakpoint #{name.inspect if name} at #{file}:#{line}"
    message << " in '#{method}'" if method

    body = lambda do |_context|
      puts message
      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

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.

[Source]

# File lib/dev-utils/debug/log.rb, line 44
  def debug(message)
    DEBUGLOG.debug message
  end
goirb(name = nil, context = nil, &block)

Alias for breakpoint

Planned feature; not yet implemented.

[Source]

# File lib/dev-utils/debug/log.rb, line 103
  def logfile=(x)
    warn 'logfile= is not implemented; ignoring'
  end

Planned feature; not yet implemented.

[Source]

# File lib/dev-utils/debug/log.rb, line 98
  def logger=(x)
    warn 'logger= is not implemented; ignoring'
  end

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.

[Source]

# File lib/dev-utils/debug/log.rb, line 70
  def trace(expr, style=:p)
    unless expr.respond_to? :to_str
      message = "trace: Can't evaluate the given value: #{caller.first}"
      DEBUGLOG.warn message
    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)
        message = "#{expr} = #{value_s}"
        lines = message.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

[Validate]