Как добавить информацию в сообщение об исключении в Ruby?
Как добавить информацию к сообщению об исключении без изменения его класса в ruby?
Подход, который я сейчас использую,
strings.each_with_index do |string, i|
begin
do_risky_operation(string)
rescue
raise $!.class, "Problem with string number #{i}: #{$!}"
end
end
В идеале я также хотел бы сохранить обратную трассировку.
Есть ли лучший способ?
Ответы
Ответ 1
Чтобы сделать резус исключения и изменить сообщение, сохраняя класс исключения и его обратную трассировку, просто выполните:
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...
Ответ 2
Это не намного лучше, но вы можете просто переделать исключение с новым сообщением:
raise $!, "Problem with string number #{i}: #{$!}"
Вы также можете получить модифицированный объект исключения самостоятельно с помощью метода exception
:
new_exception = $!.exception "Problem with string number #{i}: #{$!}"
raise new_exception
Ответ 3
Здесь другой способ:
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)
Ответ 4
Мой подход заключался бы в extend
ошибке rescue
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
).
Ответ 5
Я проголосовал за то, что ответ Райана Хенезиса должен быть принят.
Это обычная проблема в сложных приложениях, и сохранение исходной обратной трассы часто настолько критично, что для этого мы имеем метод полезности в нашем вспомогательном модуле ErrorHandling
.
Одна из проблем, которые мы обнаружили, заключалась в том, что иногда попытка генерировать более значимые сообщения, когда система находится в перепутанном состоянии, приводит к тому, что в обработчике исключений генерируются исключения, которые заставляют нас упрочить нашу функцию полезности следующим образом:/p >
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>'
Ответ 6
Вот что я в итоге сделал:
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!>"
Это похоже на Mark Rushakoff, но:
- Переопределяет
to_s
вместо message
, поскольку по умолчанию message
определяется как просто to_s
(по крайней мере, в Ruby 2.0 и 2.2, где я его тестировал)
- Вызов
extend
для вас вместо того, чтобы сделать вызывающий абонент дополнительным шагом.
- Использует
define_method
и замыкание, чтобы можно было ссылаться на локальную переменную message
. Когда я попытался использовать класс variable @@message
, он предупредил: "предупреждение: доступ к переменной переменной класса из toplevel" (см. Этот question: "Поскольку вы не создаете класс с ключевым словом класса, ваша переменная класса устанавливается на Object
, а не [ваш анонимный модуль]" )
Особенности:
- Простота использования
- Повторное использование одного и того же объекта (вместо создания нового экземпляра класса), поэтому сохраняются такие вещи, как идентификатор объекта, класс и обратная трасса.
-
to_s
, message
и inspect
все отвечают соответствующим образом
- Может использоваться с исключением, которое уже хранится в переменной; не требует, чтобы вы повторно поднимали что-либо (например, решение, которое включало передачу обратного хода для повышения:
raise $!, …, $!.backtrace
). Это было важно для меня, поскольку исключение было передано моему методу ведения журнала, а не то, что я спасал сам.