新手问题 rescue 里可否获得各层栈里局部变量?

est · September 22, 2014 · Last by ery replied at January 21, 2016 · 3776 hits
Topic has been selected as the excellent topic by the admin.

在做一个异常捕获工具,rescue 里面可否获得异常 call stack 每一层栈的局部变量和全局变量信息?

都带上,内容得很多啊 其实按照实际情况来看,真的用得上么?或者需要的时候有多少。

Exception#backtrace 里面已经包含很详细的调用情况,出异常只要对应到正确的行,问题应该都能猜测出来的。

#1 楼 @huacnlee 恩。目前就是打印 #!.backtrace 到 log 里。有的时候还是希望有变量的。主要是有没有办法获得 call stack 变量呢?

用 TracePoint 可以监听:return, :raise 事件,当发生异常时,会监听到:raise 事件,接着是:return 事件,利用这一特性,可以基本实现获取每一层栈帧的局部变量,代码如下:

class LocalVariablesTracer

  class << self
    attr_accessor :raised_exception

    def start
      @raised_exception = nil
      _define_dump_func

      TracePoint.trace(:return, :raise) do |tp|
        case tp.event
        when :raise
          @raised_exception = tp.raised_exception
        when :return
          if @raised_exception
            @raised_exception.instance_eval {
              @func_bindings ||= {}
              @func_bindings[tp.method_id] = tp.binding
            }
          end
        end
      end
    end

    private
    def _define_dump_func
      Exception.class_eval do
        def dump_local_variables(out = $stderr)
          if @func_bindings
            backtrace.grep(/\`(\w+)\'/) do
              func = $1.to_sym
              b = @func_bindings[func]
              local_variables = b.eval("local_variables").map { |var|
                %/#{var}: #{b.eval("#{var}")}/
              }
              out.puts "#{func}: #{local_variables}"
              LocalVariablesTracer.raised_exception = nil
            end
          end
        end
      end
    end

  end

end

使用例子:

LocalVariablesTracer.start

def func
  a = 1
  b = 1
  raise "hello"
end

def func1
  name = "xzgyb"
  msgs = ["hello", "world"]
  func
end

begin
  func1
rescue Exception => ex
  ex.dump_local_variables
end

#4 楼 @xzgyb 多谢。我先研究下。

这种事情我第一反应是有没有人做过

我记着 better error 中可以回到某个堆栈,然后查看 binding。 也许你应该去翻翻 better_error

7 Floor has deleted

嗯,better errors 通过 binding_of_callee 支持这个功能的

[25] pry(main)> binding.class.ancestors
=> [Binding, BindingOfCaller::BindingExtensions, Object, PP::ObjectMixin, Kernel, BasicObject]
[26] pry(main)>

Gem binding_of_callere 通过 BindingOfCaller::BindingExtensions 给 Binding 类增加了一个函数 callers(返回一个 Binding 数组,每个元素即是每层函数栈的 binding) 该函数不同的 ruby 版本有不同的实现方式

MRI 2.0 以下版本实现方式 https://github.com/banister/binding_of_caller/blob/master/ext/binding_of_caller/binding_of_caller.c#L222

MRI 2.0 以上版本实现方式 https://github.com/banister/binding_of_caller/blob/master/lib/binding_of_caller/mri2.rb#L18

以上两版都是通过 ruby 的 C 接口实现的。

You need to Sign in before reply, if you don't have an account, please Sign up first.