How do I add information to an exception message in Ruby?
如何在不更改Ruby中的类的情况下向异常消息添加信息?
我目前使用的方法是
1 2 3 4 5 6 7 | strings.each_with_index do |string, i| begin do_risky_operation(string) rescue raise $!.class,"Problem with string number #{i}: #{$!}" end end |
理想情况下,我还希望保留回溯。
有更好的方法吗?
要重新发出异常并修改消息,同时保留异常类及其回溯,只需执行以下操作:
1 2 3 4 5 6 7 | strings.each_with_index do |string, i| begin do_risky_operation(string) rescue Exception => e raise $!,"Problem with string number #{i}: #{$!}", $!.backtrace end end |
这将产生:
1 2 | # RuntimeError: Problem with string number 0: Original error message here # backtrace... |
这并不是更好,但是你可以用一条新的信息来重新判断例外:
1 | raise $!,"Problem with string number #{i}: #{$!}" |
您还可以使用
1 2 | new_exception = $!.exception"Problem with string number #{i}: #{$!}" raise new_exception |
另一种方法是:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 |
(修改为内部使用
我的方法是使用匿名模块来扩展错误的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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 |
这样,您就不会删除任何其他例外信息(即它的
我投的票是瑞安·海涅斯的回答应该是被接受的。
这是复杂应用程序中的一个常见问题,保留原始回溯通常非常关键,因此我们的
我们发现的一个问题是,当系统处于混乱状态时,有时试图生成更有意义的消息会导致异常处理程序内部生成异常,这导致我们强化了实用程序功能,如下所示:
1 2 3 4 5 6 7 8 9 | 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 |
当一切顺利的时候
1 2 3 4 5 | begin 1/0 rescue => e raise_with_new_message"error dividing %d by %d: %s", 1, 0, e end |
你得到一条修改得很好的信息
1 2 3 4 | 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>' |
当情况恶化时
1 2 3 4 5 6 | begin 1/0 rescue => e # Oops, not passing enough arguments here... raise_with_new_message"error dividing %d by %d: %s", e end |
你仍然不会忘记大局
1 2 3 4 | 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>' |
我最后做的是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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 |
实例:
1 2 3 4 5 6 7 8 9 | strings = %w[a b c] 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 |
还有:
1 2 3 4 5 6 7 8 9 10 | 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!>" |
这与马克·拉沙科夫的回答类似,但:
特征:
- 易于使用
- 重用同一个对象(而不是创建类的新实例),因此保留对象标识、类和回溯等内容
to_s 、message 和inspect 都做出了适当的反应。- 可以与已存储在变量中的异常一起使用;不需要重新引发任何内容(例如,涉及传递backtrace来引发的解决方案:
raise $!, …, $!.backtrace )。这对我很重要,因为异常被传递到我的日志记录方法中,而不是我自己挽救的东西。