如何将信息添加到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的答案类似,但是:

  1. 覆盖to_s而不是message因为默认情况下, message被定义为简单的to_s (至less在我testing过的Ruby 2.0和2.2中)
  2. 呼叫extend给你,而不是让呼叫者做那个额外的步骤。
  3. 使用define_method和一个闭包,以便可以引用局部variablesmessage 。 当我尝试使用类variable @@message ,它警告:“警告:从顶级类访问类variables”(请参阅​​此问题 :“由于您没有使用class关键字创build类,因此您的类variables正在设置为Object ,而不是[你的匿名模块]“)

特征:

  • 使用方便
  • 重用相同的对象(而不是创build类的新实例),因此保留对象标识,类和回溯等内容
  • to_smessage ,并inspect所有适当的反应
  • 可以与已经存储在variables中的exception一起使用; 并不要求你重新提高任何东西(比如涉及传递回溯的方法: raise $!, …, $!.backtrace )。 这对我来说很重要,因为这个例外被传递给了我的日志logging方法,而不是我自己救出的东西。