Как получить исходные значения и значения переменных в трассировках ruby? - PullRequest
7 голосов
/ 20 сентября 2008

Вот несколько последних кадров типичной трассировки Ruby on Rails: application trace

А вот последние несколько кадров типичного отслеживания Nevow в Python: alt text

Это не только веб-среда, вы можете сделать аналогичные сравнения между ipython и irb. Как я могу получить больше таких деталей в Ruby?

1 Ответ

7 голосов
/ 20 сентября 2008

AFAIK, как только исключение было обнаружено, слишком поздно, чтобы получить контекст, в котором оно было вызвано. Если вы перехватываете новый вызов исключения, вы можете использовать Binding.of_caller evil.rb, чтобы захватить область вызова, и выполнить

eval("local_variables.collect { |l| [l, eval(l)] }", Binding.of_caller)

Но это довольно большой взлом. Правильный ответ, вероятно, состоит в том, чтобы расширить Ruby, чтобы позволить некоторую проверку стека вызовов. Я не уверен, позволят ли это некоторые из новых реализаций Ruby, но я помню обратную реакцию на Binding.of_caller, потому что это сделает оптимизацию намного сложнее.

(Если честно, я не понимаю этой обратной реакции: пока интерпретатор записывает достаточно информации о выполненных оптимизациях, Binding.of_caller должен работать, хотя, возможно, и медленно.)

Обновление

Хорошо, я понял это. Далее следует длинный код:

class Foo < Exception
  attr_reader :call_binding

  def initialize
    # Find the calling location
    expected_file, expected_line = caller(1).first.split(':')[0,2]
    expected_line = expected_line.to_i
    return_count = 5  # If we see more than 5 returns, stop tracing

    # Start tracing until we see our caller.
    set_trace_func(proc do |event, file, line, id, binding, kls|
      if file == expected_file && line == expected_line
        # Found it: Save the binding and stop tracing
        @call_binding = binding
        set_trace_func(nil)
      end

      if event == :return
        # Seen too many returns, give up. :-(
        set_trace_func(nil) if (return_count -= 1) <= 0
      end
    end)
  end
end

class Hello
  def a
    x = 10
    y = 20
    raise Foo
  end
end
class World
  def b
    Hello.new.a
  end
end

begin World.new.b
rescue Foo => e
  b = e.call_binding
  puts eval("local_variables.collect {|l| [l, eval(l)]}", b).inspect
end
...