Как добавить информацию в сообщение об исключении в Ruby? - программирование
Подтвердить что ты не робот

Как добавить информацию в сообщение об исключении в Ruby?

Как добавить информацию к сообщению об исключении без изменения его класса в ruby?

Подход, который я сейчас использую,

strings.each_with_index do |string, i|
  begin
    do_risky_operation(string)
  rescue
    raise $!.class, "Problem with string number #{i}: #{$!}"
  end
end

В идеале я также хотел бы сохранить обратную трассировку.

Есть ли лучший способ?

4b9b3361

Ответ 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). Это было важно для меня, поскольку исключение было передано моему методу ведения журнала, а не то, что я спасал сам.