¿Cómo agrego información a un mensaje de excepción en Ruby?

¿Cómo agrego información a un mensaje de excepción sin cambiar su clase en ruby?

El enfoque que estoy usando actualmente es

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

Idealmente, también me gustaría preservar el retroceso.

¿Hay alguna manera mejor?

Para volver a subir la excepción y modificar el mensaje, conservando la clase de excepción y su retroceso, simplemente haga lo siguiente:

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

Que dará lugar a:

 # RuntimeError: Problem with string number 0: Original error message here # backtrace... 

No es mucho mejor, pero puedes volver a subir la excepción con un nuevo mensaje:

 raise $!, "Problem with string number #{i}: #{$!}" 

También puede obtener un objeto de excepción modificado usted mismo con el método de exception :

 new_exception = $!.exception "Problem with string number #{i}: #{$!}" raise new_exception 

Aquí hay otra manera:

 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 

(revisado para usar el método de exception internamente, gracias @Chuck)

Mi enfoque sería extend el error de rescue d con un módulo anónimo que extienda el método de message de error:

 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 

De esa manera, no se anota ninguna otra información en la excepción (es decir, su backtrace ).

Puse a mi voto que la respuesta de Ryan Heneise debería ser la aceptada.

Este es un problema común en aplicaciones complejas y preservar el retroceso original a menudo es crítico, por lo que tenemos un método de utilidad en nuestro módulo de ayuda ErrorHandling para esto.

Uno de los problemas que descubrimos fue que, a veces, intentar generar mensajes más significativos cuando un sistema está en un estado desordenado daría lugar a que se generaran excepciones dentro del controlador de excepciones, lo que nos llevó a fortalecer nuestra función de utilidad de la siguiente manera:

 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 

Cuando las cosas van bien

 begin 1/0 rescue => e raise_with_new_message "error dividing %d by %d: %s", 1, 0, e end 

recibes un mensaje bien modificado

 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 `
'

Cuando las cosas van mal

 begin 1/0 rescue => e # Oops, not passing enough arguments here... raise_with_new_message "error dividing %d by %d: %s", e end 

Todavía no pierdes la pista del outlook general.

 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 `
'

Esto es lo que terminé haciendo:

 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 

Ejemplos:

 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 

y:

 pry(main)> exception = 0/0 rescue $! => # pry(main)> exception = exception.append_message('. 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 => "#" 

Esto es similar a la respuesta de Mark Rushakoff pero:

  1. to_s lugar de message ya que el message predeterminado se define simplemente como to_s (al menos en Ruby 2.0 y 2.2 donde lo probé)
  2. Las llamadas se extend por usted en lugar de hacer que la persona que llama realice ese paso adicional.
  3. Utiliza define_method y un cierre para que se pueda hacer referencia al message variable local. Cuando intenté usar una variable @@message clase variable @@message , advirtió, “advertencia: acceso de variable de clase desde el nivel superior” (vea esta pregunta : “Ya que no está creando una clase con la palabra clave de clase, su variable de clase se está configurando en Object , no [su módulo anónimo] “)

caracteristicas:

  • Fácil de usar
  • Reutiliza el mismo objeto (en lugar de crear una nueva instancia de la clase), por lo que se conservan cosas como la identidad del objeto, la clase y el retroceso.
  • to_s , message e inspect todos responden apropiadamente
  • Se puede usar con una excepción que ya está almacenada en una variable; no requiere que vuelvas a subir nada (como la solución que involucra pasar la marcha atrás para recaudar: raise $!, …, $!.backtrace ). Esto fue importante para mí ya que la excepción se transfirió a mi método de registro, no es algo que yo mismo haya rescatado.