【问题标题】:How to determine where an exception is being rescued in Ruby?如何确定异常在 Ruby 中被救出的位置?
【发布时间】:2017-06-29 09:16:18
【问题描述】:

一些背景知识:我在大型代码库 (Rails) 中有一些 Ruby 代码,在某些情况下会引发异常。然而,异常并没有像预期的那样“发生”,它被默默地丢弃。我假设其他一些代码(gem)挽救了异常,可能是偶然的。

我怎样才能确定在哪里该异常被挽救了?

我确实可以完全控制异常。那么也许有办法让异常知道何时或何时获救?

人为的例子:

# code outside my control
def foo
  yield
rescue
end

def black_box(&block)
  foo(&block)
end

# my code
black_box do
  puts 'about to raise'
  raise
  puts 'never gets here'
end

输出:

about to raise

所以异常被抢救了。我如何识别(从“我的代码”中)它是在 foo 中获救的?

【问题讨论】:

  • 您可以假设外国 gem 做了 something 例外,而不仅仅是默默地跳过它。尝试在#message#cause 等处设置断点并检查是否有意外中断。如果幸运的话,您会在堆栈顶部获得调用方。
  • @mudasobwa 确实有效!由于在验收测试中config.action_dispatch.show_exceptions = false,Rails 自己挽救了异常。
  • 酷。 Rails 太神奇了,这就是为什么我更喜欢 COBOL :)

标签: ruby-on-rails ruby exception


【解决方案1】:

我(现在)能想到的唯一方法是手动调试/检查。

当您要提出要跟踪的异常时,请检查当前的caller。这为您提供了一个调用堆栈。现在访问编辑器中的每一行/方法并寻找过于贪婪的救援。

至于更“自动”的方式,我没有看到。 Ruby 异常没有on_rescue 回调或类似的东西,所以它们不知道自己被救了。

【讨论】:

  • 一个115行的调用栈:-(
  • @Stefan:你很幸运,它是一个小的 :)
  • 无赖,TracePoint 也无济于事,它有一个 :raise 事件,但没有 :rescue
  • 这种手动调试方式其实还是挺快的。我使用 iTerm2,它允许我通过命令单击 filename:lineno 模式以在我的编辑器中打开文件,因此我可以轻松单击 caller 数组以找到罪魁祸首。
  • 它隐藏在 Preferences > Profiles > Advanced > Semantic History 下。
【解决方案2】:

[3 年后,我在这里回答自己的问题]

至少在 MRI 中,rescue 的处理方式与 case 语句中的 when 一样!

例如,

begin
  # ...
rescue A, B
  # ...
rescue C
  # ...
rescue
  # ...
end

评估如下:

case exception
when A, B
  # ...
when C
  # ...
when StandardError
  # ...
end

在后台,以上两个都执行:(直到找到匹配项)

A === exception
B === exception
C === exception
StandardError === Exception

===Module#===,因为 ABCStandardError 是类。

=== 调用可用于追踪潜在的rescue,例如通过在Exception 中覆盖Module#===:(它成为类方法)

# foo.rb

def foo
  yield
rescue
end

def black_box(&block)
  foo(&block)
end
class Exception
  def self.===(other)
    cl = caller_locations[0]
    puts "#{cl.path}:#{cl.lineno} - #{$!.inspect}"
    super
  end
end

require_relative 'foo.rb'

black_box do
  puts 'about to raise'
  raise 'error message'
  puts 'never gets here'
end

输出:

about to raise
foo.rb:5 - #<RuntimeError: error message>

TracePoint也可以用来拦截方法调用:

TracePoint.trace(:c_call) do |tp|
  if tp.defined_class == Module && tp.method_id == :=== && tp.self <= Exception
    puts "#{tp.path}:#{tp.lineno} - #{$!.inspect}"
  end
end

【讨论】:

  • 不错!解决这个问题需要3年吗? :)
猜你喜欢
  • 1970-01-01
  • 2019-01-11
  • 2013-06-28
  • 1970-01-01
  • 2020-08-26
  • 2012-04-15
  • 1970-01-01
  • 2012-07-09
  • 2021-10-24
相关资源
最近更新 更多