如何将信息添加到Ruby中的exception消息?
如何将信息添加到exception消息,而不用在ruby中更改它的类?
我目前使用的方法是
strings.each_with_index do |string, i| begin do_risky_operation(string) rescue raise $!.class, "Problem with string number #{i}: #{$!}" end end
理想情况下,我也想保留回溯。
有没有更好的办法?
要重新生成exception并修改消息,同时保留exception类及其回溯,只需执行以下操作:
strings.each_with_index do |string, i| begin do_risky_operation(string) rescue Exception => e raise $!, "Problem with string number #{i}: #{$!}", $!.backtrace end end
这将产生:
# RuntimeError: Problem with string number 0: Original error message here # backtrace...
这并不好,但你可以用一个新的信息来重新评估exception:
raise $!, "Problem with string number #{i}: #{$!}"
您也可以使用exception
方法自己获取修改的exception对象:
new_exception = $!.exception "Problem with string number #{i}: #{$!}" raise new_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
(修改为在内部使用exception
方法,谢谢@Chuck)
我的方法是extend
rescue
错误与扩展错误的message
方法的匿名模块:
def make_extended_message(msg) Module.new do @@msg = msg def message super + @@msg end end end begin begin raise "this is a test" rescue raise($!.extend(make_extended_message(" that has been extended"))) end rescue puts $! # just says "this is a test" puts $!.message # says extended message end
这样,你就不会在例外情况下(即backtrace
)破坏任何其他信息。
我把我的投票, 瑞恩Heneise的答案应该是接受的。
这是复杂应用程序中的一个常见问题,保留原始回溯常常非常重要,所以我们在ErrorHandling
助手模块中有一个实用方法。
我们发现的一个问题是,当系统处于混乱状态时,有时试图产生更有意义的消息会导致在exception处理器本身内部产生exception,这导致我们硬化我们的效用函数,如下所示:
def raise_with_new_message(*args) ex = args.first.kind_of?(Exception) ? args.shift : $! msg = begin sprintf args.shift, *args rescue Exception => e "internal error modifying exception message for #{ex}: #{e}" end raise ex, msg, ex.backtrace end
事情顺利的时候
begin 1/0 rescue => e raise_with_new_message "error dividing %d by %d: %s", 1, 0, e end
你会得到一个很好的修改信息
ZeroDivisionError: error dividing 1 by 0: divided by 0 from (irb):19:in `/' from (irb):19 from /Users/sim/.rvm/rubies/ruby-2.0.0-p247/bin/irb:16:in `<main>'
当事情变得糟糕
begin 1/0 rescue => e # Oops, not passing enough arguments here... raise_with_new_message "error dividing %d by %d: %s", e end
你仍然不会忘记大局
ZeroDivisionError: internal error modifying exception message for divided by 0: can't convert ZeroDivisionError into Integer from (irb):25:in `/' from (irb):25 from /Users/sim/.rvm/rubies/ruby-2.0.0-p247/bin/irb:16:in `<main>'
这就是我最终做的事情:
Exception.class_eval do def prepend_message(message) mod = Module.new do define_method :to_s do message + super() end end self.extend mod end def append_message(message) mod = Module.new do define_method :to_s do super() + message end end self.extend mod end end
例子:
strings = %w[abc] strings.each_with_index do |string, i| begin do_risky_operation(string) rescue raise $!.prepend_message "Problem with string number #{i}:" end end => NoMethodError: Problem with string number 0:undefined method `do_risky_operation' for main:Object
和:
pry(main)> exception = 0/0 rescue $! => #<ZeroDivisionError: divided by 0> pry(main)> exception = exception.append_message('. With additional info!') => #<ZeroDivisionError: divided by 0. With additional info!> pry(main)> exception.message => "divided by 0. With additional info!" pry(main)> exception.to_s => "divided by 0. With additional info!" pry(main)> exception.inspect => "#<ZeroDivisionError: divided by 0. With additional info!>"
这与Mark Rushakoff的答案类似,但是:
- 覆盖
to_s
而不是message
因为默认情况下,message
被定义为简单的to_s
(至less在我testing过的Ruby 2.0和2.2中) - 呼叫
extend
给你,而不是让呼叫者做那个额外的步骤。 - 使用
define_method
和一个闭包,以便可以引用局部variablesmessage
。 当我尝试使用类variable @@message
,它警告:“警告:从顶级类访问类variables”(请参阅此问题 :“由于您没有使用class关键字创build类,因此您的类variables正在设置为Object
,而不是[你的匿名模块]“)
特征:
- 使用方便
- 重用相同的对象(而不是创build类的新实例),因此保留对象标识,类和回溯等内容
-
to_s
,message
,并inspect
所有适当的反应 - 可以与已经存储在variables中的exception一起使用; 并不要求你重新提高任何东西(比如涉及传递回溯的方法:
raise $!, …, $!.backtrace
)。 这对我来说很重要,因为这个例外被传递给了我的日志logging方法,而不是我自己救出的东西。