在做一个异常捕获工具,rescue 里面可否获得异常 call stack 每一层栈的局部变量和全局变量信息?
都带上,内容得很多啊 其实按照实际情况来看,真的用得上么?或者需要的时候有多少。
Exception#backtrace 里面已经包含很详细的调用情况,出异常只要对应到正确的行,问题应该都能猜测出来的。
用 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
这种事情我第一反应是有没有人做过
我记着 better error 中可以回到某个堆栈,然后查看 binding。 也许你应该去翻翻 better_error
[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 接口实现的。