And*_*imm 55 ruby exception-handling exception
如何在不更改ruby中的类的情况下向异常消息添加信息?
我目前使用的方法是
strings.each_with_index do |string, i|
begin
do_risky_operation(string)
rescue
raise $!.class, "Problem with string number #{i}: #{$!}"
end
end
Run Code Online (Sandbox Code Playgroud)
理想情况下,我还想保留回溯.
有没有更好的办法?
Boo*_*age 95
要在保留异常类及其回溯的同时重新加载异常并修改消息,只需执行以下操作:
strings.each_with_index do |string, i|
begin
do_risky_operation(string)
rescue Exception => e
raise $!, "Problem with string number #{i}: #{$!}", $!.backtrace
end
end
Run Code Online (Sandbox Code Playgroud)
哪个会产生:
# RuntimeError: Problem with string number 0: Original error message here
# backtrace...
Run Code Online (Sandbox Code Playgroud)
Chu*_*uck 17
它不是更好,但你可以用一条新消息重新加载异常:
raise $!, "Problem with string number #{i}: #{$!}"
Run Code Online (Sandbox Code Playgroud)
您还可以使用以下exception方法自行获取已修改的异常对象:
new_exception = $!.exception "Problem with string number #{i}: #{$!}"
raise new_exception
Run Code Online (Sandbox Code Playgroud)
我意识到我参加这个聚会晚了 6 年,但是......我以为我直到本周才了解 Ruby 错误处理并遇到了这个问题。虽然答案很有用,但有一些不明显(和未记录)的行为可能对这个线程的未来读者有用。所有代码都在 ruby v2.3.1 下运行。
@Andrew Grimm 问
如何在不更改 ruby 中的类的情况下向异常消息添加信息?
然后提供示例代码:
raise $!.class, "Problem with string number #{i}: #{$!}"
Run Code Online (Sandbox Code Playgroud)
我认为重要的是指出这不会向原始错误实例对象添加信息,而是引发具有相同类的新错误对象。
@BoosterStage 说
要重新引发异常并修改消息...
但同样,提供的代码
raise $!, "Problem with string number #{i}: #{$!}", $!.backtrace
Run Code Online (Sandbox Code Playgroud)
将引发 $! 引用的任何错误类的新实例,但它不会与 $! 完全相同。
@Andrew Grimm 的代码和@BoosterStage 的示例之间的区别在于,#raise在第一种情况下Class,to的第一个参数是 a ,而在第二种情况下,它是 some (大概)的实例StandardError。差异很重要,因为Kernel#raise的文档说:
使用单个 String 参数,以字符串作为消息引发 RuntimeError。否则,第一个参数应该是 Exception 类的名称(或在发送异常消息时返回 Exception 对象的对象)。
如果只给出一个参数并且它是一个错误对象实例,那么raise 如果该对象的#exception方法继承或实现了Exception#exception(string) 中定义的默认行为,则该对象将是d:
没有参数,或者如果参数与接收者相同,则返回接收者。否则,创建一个与接收者类相同的新异常对象,但消息等于 string.to_str。
正如许多人猜测的那样:
...
catch StandardError => e
raise $!
...
Run Code Online (Sandbox Code Playgroud)
引发 $! 引用的相同错误,与简单调用相同:
...
catch StandardError => e
raise
...
Run Code Online (Sandbox Code Playgroud)
但可能不是因为人们可能认为的原因。在这种情况下,调用raise的不只是提高了对象$!......它提出的结果$!.exception(nil),在这种情况下,恰好是$!。
为了阐明这种行为,请考虑以下玩具代码:
class TestError < StandardError
def initialize(message=nil)
puts 'initialize'
super
end
def exception(message=nil)
puts 'exception'
return self if message.nil? || message == self
super
end
end
Run Code Online (Sandbox Code Playgroud)
运行它(这与我上面引用的@Andrew Grimm 的示例相同):
2.3.1 :071 > begin ; raise TestError, 'message' ; rescue => e ; puts e ; end
Run Code Online (Sandbox Code Playgroud)
结果是:
initialize
message
Run Code Online (Sandbox Code Playgroud)
所以一个 TestError 是initialized, rescued,并打印了它的消息。到现在为止还挺好。第二个测试(类似于上面引用的@BoosterStage 的示例):
initialize
message
Run Code Online (Sandbox Code Playgroud)
结果有些出人意料:
initialize
exception
bar
Run Code Online (Sandbox Code Playgroud)
所以 aTestError是initialize带有 'foo' 的 d,但随后#raise调用#exception了第一个参数( 的实例TestError)并传入了 'bar' 的消息以创建 的第二个实例TestError,这就是最终引发的。
直到。
此外,像@Sim 一样,我非常关心保留任何原始的回溯上下文,但不是像他那样实现自定义错误处理程序,而是raise_with_new_messageRuby 支持Exception#cause我:每当我想捕获错误时,将其包装在特定于域的错误中,然后然后引发该错误,我仍然可以通过#cause引发的特定于域的错误获得原始回溯。
这一切的重点是——就像@Andrew Grimm——我想用更多的上下文来提出错误;具体来说,我只想从我的应用程序中的某些点引发特定于域的错误,这些错误可能具有许多与网络相关的故障模式。然后我的错误报告可以用来处理我的应用程序顶层的域错误,并且我通过#cause递归调用获得日志/报告所需的所有上下文,直到我找到“根本原因”。
我使用这样的东西:
2.3.1 :073 > begin ; raise TestError.new('foo'), 'bar' ; rescue => e ; puts e ; end
Run Code Online (Sandbox Code Playgroud)
然后,如果我使用 Faraday 之类的东西来调用远程 REST 服务,我可以将所有可能的错误包装到特定于域的错误中并传入额外的信息(我认为这是该线程的原始问题):
initialize
exception
bar
Run Code Online (Sandbox Code Playgroud)
是的,没错:我刚刚意识到我可以将extra信息设置为当前,binding以获取在ServerDomainError实例化/引发时定义的所有本地变量。此测试代码:
class BaseDomainError < StandardError
attr_reader :extra
def initialize(message = nil, extra = nil)
super(message)
@extra = extra
end
end
class ServerDomainError < BaseDomainError; end
Run Code Online (Sandbox Code Playgroud)
将输出:
exception
#<ServiceX:0x00007f9b10c9ef48>
args, e
{:a=>1, :b=>2}
undefined method `make_network_call_to_service_x' for #<ServiceX:0x00007f9b10c9ef48 @foo=:bar>
@foo
bar
Run Code Online (Sandbox Code Playgroud)
现在,调用 ServiceX 的 Rails 控制器并不特别需要知道 ServiceX 正在使用Faraday(或 gRPC,或其他任何东西),它只需要调用并处理BaseDomainError. 再次:出于日志记录的目的,顶层的单个处理程序可以递归地记录#cause任何捕获错误的所有s,并且对于BaseDomainError错误链中的任何实例,它还可以记录extra值,可能包括从封装的binding(s )。
我希望这次旅行对其他人和我一样有用。我学到了很多。
更新:Skiptrace看起来像是将绑定添加到 Ruby 错误。
此外,请参阅本等岗位的信息有关的实施如何 Exception#exception将克隆的对象(复制实例变量)。
这是另一种方式:
class Exception
def with_extra_message extra
exception "#{message} - #{extra}"
end
end
begin
1/0
rescue => e
raise e.with_extra_message "you fool"
end
# raises an exception "ZeroDivisionError: divided by 0 - you fool" with original backtrace
Run Code Online (Sandbox Code Playgroud)
(修改为在exception内部使用该方法,感谢@Chuck)