Как добавить информацию в сообщение об исключении в Ruby?
Как добавить информацию в сообщение об исключении без изменения его класса в ruby?
в настоящее время я использую подход
strings.each_with_index do |string, i|
begin
do_risky_operation(string)
rescue
raise $!.class, "Problem with string number #{i}: #{$!}"
end
end
В идеале, я также хотел бы сохранить backtrace.
есть ли лучший способ?
6 ответов:
чтобы повторно вызвать исключение и изменить сообщение, сохраняя класс исключения и его обратную трассировку, просто выполните:
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...
это не намного лучше, но вы можете просто повторить исключение с новым сообщением:
raise $!, "Problem with string number #{i}: #{$!}"
вы также можете получить измененный объект исключения с
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
therescue
D ошибка с анонимным модулем, который расширяет ошибку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 это ответ должен быть принят.
это распространенная проблема в сложных приложениях и сохранение исходного backtrace часто имеет решающее значение настолько, что у нас есть метод полезности в нашем
ErrorHandling
вспомогательный модуль для этого.одна из проблем, которую мы обнаружили, заключалась в том, что иногда попытка генерировать более значимые сообщения, когда система находится в перепутанном состоянии, приведет к созданию исключений внутри самого обработчика исключений, который привел нас к упрочнению нашей функции полезности следующим образом:
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[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
и:
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!>"
это похоже на Марк Rushakoff'ы ответ, но:
- переопределяет
to_s
вместоmessage
так как по умолчаниюmessage
определяется как простоto_s
(по крайней мере в Ruby 2.0 и 2.2, где я проверял)- звонки
extend
для вас вместо того, чтобы заставить абонента сделать этот дополнительный шаг.- использует
define_method
и закрытие так, что локальная переменнаяmessage
можно ссылаться. Когда я попытался использовать классvariable @@message
, он предупредил: "предупреждение: доступ к переменной класса с верхнего уровня" (см. Это вопрос: "поскольку вы не создаете класс с ключевым словом class, ваша переменная class устанавливается наObject
, а не [ваш анонимный модуль]")характеристики:
- прост в использовании
- использует один и тот же объект (вместо создания нового экземпляр класса), поэтому такие вещи, как идентификатор объекта, класс и backtrace сохраняются
to_s
,message
иinspect
все отвечают соответствующим образом- может использоваться с исключением, которое уже хранится в переменной; не требует, чтобы вы повторно поднимали что-либо (например, решение, которое включало передачу backtrace для повышения:
raise $!, …, $!.backtrace
). Это было важно для меня, так как исключение было передано в мой метод ведения журнала, а не то, что я спас себя.