Jak dodać informacje do wiadomości wyjątku w Ruby?

Jak dodać informacje do wiadomości wyjątku bez zmiany jego klasy w ruby?

Obecnie używam podejścia

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

Idealnie, chciałbym również zachować backtrace.

Czy jest lepszy sposób?
Author: Sim, 2010-05-13

6 answers

Aby odtworzyć wyjątek i zmodyfikować wiadomość, zachowując klasę wyjątku i jej backtrace, po prostu wykonaj:

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

Co daje:

# RuntimeError: Problem with string number 0: Original error message here
#     backtrace...
 87
Author: BoosterStage,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-31 17:24:47

Nie jest dużo lepiej, ale możesz po prostu przekierować wyjątek z nową wiadomością:

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

Możesz również samodzielnie uzyskać zmodyfikowany obiekt wyjątku za pomocą metody exception:

new_exception = $!.exception "Problem with string number #{i}: #{$!}"
raise new_exception
 17
Author: Chuck,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-05-13 00:48:31

Oto inny sposób:

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

(poprawione, aby użyć metody exception wewnętrznie, dzięki @ Chuck)

 6
Author: AlexChaffee,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-28 15:00:18

Moim podejściem byłoby extend błąd rescued z anonimowym modułem rozszerzającym metodę message błędu:

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

W ten sposób nie zatrzaskujesz żadnych innych informacji w wyjątku(tj. jego backtrace).

 4
Author: Mark Rushakoff,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-05-13 00:51:26

Głosuję, że odpowiedź Ryana Heneise ' a powinna być zaakceptowana.

Jest to częsty problem w złożonych aplikacjach, a zachowanie oryginalnego backtrace jest często krytyczne tak bardzo, że w naszym module pomocniczym ErrorHandling mamy do tego metodę użytkową.

Jednym z problemów, który odkryliśmy, było to, że czasami próba wygenerowania bardziej znaczących wiadomości, gdy system jest w poplątanym stanie, spowodowałaby wygenerowanie WYJĄTKÓW wewnątrz wyjątku sam handler, który doprowadził nas do utwardzenia naszej funkcji użytkowej w następujący sposób:

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

When things go well

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

Otrzymujesz ładnie zmodyfikowaną wiadomość

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

When things go bad

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

Nadal nie tracisz pojęcia o wielkim obrazie

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>'
 2
Author: Sim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:03:02

Oto co zrobiłem:

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

Przykłady:

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

I:

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 jest podobne do Mark Rushakoff's odpowiedź, ale:

  1. nadpisuje to_s zamiast message ponieważ domyślnie message jest zdefiniowane po prostu to_s (przynajmniej w Rubim 2.0 i 2.2 gdzie go testowałem)
  2. Połączenia extend dla ciebie, zamiast zmuszać rozmówcę do zrobienia tego dodatkowego kroku.
  3. używa define_method i zamknięcia, aby można było odwoływać się do zmiennej lokalnej message. Kiedy próbowałem użyć klasy variable @@message, ostrzegł, " warning: class variable access from toplevel "(Zobacz to pytanie: "ponieważ nie tworzysz klasy ze słowem kluczowym class, twoja zmienna klasy jest ustawiona na Object, a nie na [Twój anonimowy Moduł]")

Cechy:

  • łatwy w użyciu
  • używa ponownie tego samego obiektu (zamiast tworzyć nową instancję klasy), więc rzeczy takie jak tożsamość obiektu, klasa i backtrace są zachowywane
  • to_s, message, i inspect wszyscy odpowiadają odpowiednio
  • Może być używany z wyjątkiem, który jest już przechowywany w zmiennej; nie wymaga ponownego podnoszenia czegokolwiek (jak rozwiązanie, które wymagało przekazania backtrace do raise: raise $!, …, $!.backtrace). Było to dla mnie ważne, ponieważ wyjątek został przekazany do mojej metody logowania, a nie coś, co sam uratowałem.
 0
Author: Tyler Rick,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:14