Ruby에서 예외 메시지에 정보를 어떻게 추가합니까?
Ruby에서 클래스를 변경하지 않고 예외 메시지에 정보를 추가하려면 어떻게해야합니까?
현재 사용하고있는 접근 방식은
strings.each_with_index do |string, i|
begin
do_risky_operation(string)
rescue
raise $!.class, "Problem with string number #{i}: #{$!}"
end
end
이상적으로는 역 추적도 보존하고 싶습니다.
더 좋은 방법이 있습니까?
예외 클래스와 역 추적을 유지하면서 예외를 다시 발생시키고 메시지를 수정하려면 다음을 수행하십시오.
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에게 감사드립니다)
내 접근하는 것 오류의 확장 익명 모듈과 D 오류 방법 :extendrescuemessage
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.
나는 Ryan Heneise의 대답이 받아 들여 져야한다고 투표했습니다 .
이것은 복잡한 응용 프로그램에서 일반적인 문제이며 원래 역 추적을 보존하는 것이 종종 매우 중요하므로 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>'
나는이 파티에 6 년 늦었다는 것을 알고 있지만 ... 나는 이번 주까지 Ruby 오류 처리를 이해하고이 질문을 만났다. 답변은 유용하지만이 스레드의 미래 독자에게 유용 할 수있는 명확하지 않은 (그리고 문서화되지 않은) 동작이 있습니다. 모든 코드는 루비 v2.3.1에서 실행되었습니다.
@Andrew Grimm이 묻습니다.
Ruby에서 클래스를 변경하지 않고 예외 메시지에 정보를 추가하려면 어떻게해야합니까?
그런 다음 샘플 코드를 제공합니다.
raise $!.class, "Problem with string number #{i}: #{$!}"
나는 이것이 원래 오류 인스턴스 객체 에 정보 를 추가하지 않고 대신 동일한 클래스를 가진 NEW 오류 객체를 발생 시킨다는 점을 지적 하는 것이 중요 하다고 생각합니다 .
@BoosterStage라고
예외를 다시 발생시키고 메시지를 수정하려면 ...
그러나 다시 제공된 코드
raise $!, "Problem with string number #{i}: #{$!}", $!.backtrace
$!가 참조하는 오류 클래스의 새 인스턴스를 발생 시키지만 $! 와 똑같은 인스턴스 는 아닙니다 .
@Andrew Grimm의 코드와 @BoosterStage의 예제의 차이점 #raise은 첫 번째 경우에 대한 첫 번째 인수가 Class이고 두 번째 경우에는 일부 (아마도) 인스턴스라는 사실입니다 StandardError. Kernel # raise에 대한 문서에 다음과 같이 나와 있기 때문에 차이가 중요합니다 .
단일 String 인수를 사용하여 문자열을 메시지로 사용하여 RuntimeError를 발생시킵니다. 그렇지 않으면 첫 번째 매개 변수는 Exception 클래스 (또는 예외 메시지를 보낼 때 Exception 개체를 반환하는 개체)의 이름이어야합니다.
하나의 인수가 주어지고이 오류 객체 인스턴스 인 경우, 그 객체가 될 raise거라고 경우 해당 개체의 #exception메소드 상속 또는 구현 기본 동작에 정의 된 예외 번호 예외 (문자열) :
인수가 없거나 인수가 수신자와 동일한 경우 수신자를 반환합니다. 그렇지 않으면 수신자와 동일한 클래스의 새 예외 객체를 생성하지만 메시지는 string.to_str과 같습니다.
많은 사람들이 추측 하듯이 :
...
catch StandardError => e
raise $!
...
$!가 참조하는 동일한 오류를 발생시킵니다.
...
catch StandardError => e
raise
...
그러나 아마도 생각할 수있는 이유 때문일 것입니다. 이 경우, 호출 할 수 raise있다 NOT 단지 객체를 제기 $!은 결과 제기 ... $!.exception(nil)이 경우에 될 일이, $!.
이 동작을 명확히하기 위해 다음 장난감 코드를 고려하십시오.
class TestError < StandardError
def initialize(message=nil)
puts 'initialize'
super
end
def exception(message=nil)
puts 'exception'
return self if message.nil? || message == self
super
end
end
실행 (위에서 인용 한 @Andrew Grimm의 샘플과 동일) :
2.3.1 :071 > begin ; raise TestError, 'message' ; rescue => e ; puts e ; end
결과 :
initialize
message
따라서 TestError는 initialized, rescued이고 메시지가 인쇄되었습니다. 여태까지는 그런대로 잘됐다. 두 번째 테스트 (위에 인용 된 @BoosterStage의 샘플과 유사) :
2.3.1 :073 > begin ; raise TestError.new('foo'), 'bar' ; rescue => e ; puts e ; end
다소 놀라운 결과 :
initialize
exception
bar
A는 그래서 TestError한 initialize후 '갑'과 거라고하지만, #raise호출 한 #exception첫 번째 인수 (의 인스턴스 TestError)와 '바'의 메시지 전달 의 두 번째 인스턴스를 생성하는 TestError궁극적 제기됩니다 것입니다 .
TIL.
또한, @Sim처럼, 내가하고 매우 어떤 원래 역 추적 컨텍스트를 유지하지만, 대신 자신과 같은 사용자 지정 오류 처리기를 구현하는 방법에 대한 우려 raise_with_new_message, 루비는 Exception#cause내 뒤를 가지고 : 나는 오류를 잡을 할 때마다, 도메인 특정 오류에 싸서 그런 다음 오류 를 발생 #cause시키면 도메인 별 오류가 발생 하면 원래 역 추적을 사용할 수 있습니다 .
이 모든 것의 요점은 @Andrew Grimm처럼 더 많은 맥락에서 오류를 제기하고 싶다는 것입니다. 특히 네트워크 관련 오류 모드가 많을 수있는 내 앱의 특정 지점에서만 도메인 관련 오류를 발생시키고 싶습니다. 그런 다음 내 앱의 최상위 수준에서 도메인 오류를 처리하기 위해 내 오류보고를 수행 할 수 있으며 #cause"근본 원인"에 도달 할 때까지 재귀 적 으로 호출하여 로깅 /보고에 필요한 모든 컨텍스트를 얻을 수 있습니다.
다음과 같이 사용합니다.
class BaseDomainError < StandardError
attr_reader :extra
def initialize(message = nil, extra = nil)
super(message)
@extra = extra
end
end
class ServerDomainError < BaseDomainError; end
그런 다음 Faraday와 같은 것을 사용하여 원격 REST 서비스를 호출하는 경우 가능한 모든 오류를 도메인 별 오류로 래핑하고 추가 정보를 전달할 수 있습니다 (이 스레드의 원래 질문이라고 생각합니다).
class ServiceX
def initialize(foo)
@foo = foo
end
def get_data(args)
begin
# This method is not defined and calling it will raise an error
make_network_call_to_service_x(args)
rescue StandardError => e
raise ServerDomainError.new('error calling service x', binding)
end
end
end
Yeah, that's right: I literally just realized I can set the extra info to the current binding to grab all local vars defined at the time the ServerDomainError is instantiated/raised. This test code:
begin
ServiceX.new(:bar).get_data(a: 1, b: 2)
rescue
puts $!.extra.receiver
puts $!.extra.local_variables.join(', ')
puts $!.extra.local_variable_get(:args)
puts $!.extra.local_variable_get(:e)
puts eval('self.instance_variables', $!.extra)
puts eval('self.instance_variable_get(:@foo)', $!.extra)
end
will output:
exception
#<ServiceX:0x00007f9b10c9ef48>
args, e
{:a=>1, :b=>2}
undefined method `make_network_call_to_service_x' for #<ServiceX:0x00007f9b10c9ef48 @foo=:bar>
@foo
bar
Now a Rails controller calling ServiceX doesn't particularly need to know that ServiceX is using Faraday (or gRPC, or anything else), it just makes the call and handles BaseDomainError. Again: for logging purposes, a single handler at the top level can recursively log all the #causes of any caught errors, and for any BaseDomainError instances in the error chain it can also log the extra values, potentially including the local variables pulled from the encapsulated binding(s).
I hope this tour has been as useful for others as it was for me. I learned a lot.
UPDATE: Skiptrace looks like it adds the bindings to Ruby errors.
Also, see this other post for info about how the implementation of Exception#exception will clone the object (copying instance variables).
Here's what I ended up doing:
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
Examples:
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
and:
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!>"
This is similar to Mark Rushakoff's answer but:
- Overrides
to_sinstead ofmessagesince by defaultmessageis defined as simplyto_s(at least in Ruby 2.0 and 2.2 where I tested it) - Calls
extendfor you instead of making the caller do that extra step. - Uses
define_methodand a closure so that the local variablemessagecan be referenced. When I tried using a classvariable @@message, it warned, "warning: class variable access from toplevel" (See this question: "Since you're not creating a class with the class keyword, your class variable is being set onObject, not [your anonymous module]")
Features:
- Easy to use
- Reuses the same object (instead of creating a new instance of the class), so things like object identity, class, and backtrace are preserved
to_s,message, andinspectall respond appropriately- Can be used with an exception that is already stored in a variable; doesn't require you to re-raise anything (like the solution that involved passing the backtrace to raise:
raise $!, …, $!.backtrace). This was important to me since the exception was passed in to my logging method, not something I had rescued myself.
ReferenceURL : https://stackoverflow.com/questions/2823748/how-do-i-add-information-to-an-exception-message-in-ruby
'program story' 카테고리의 다른 글
| Unit과 Nothing의 차이점은 무엇입니까? (0) | 2021.01.07 |
|---|---|
| Eclipse : 스위치 케이스의 Java Enum 자동 완성 (0) | 2021.01.06 |
| 자바 스크립트 변수 이름에 $ (달러 기호)를 사용하는 이유는 무엇입니까? (0) | 2021.01.06 |
| SVM-하드 마진 또는 소프트 마진? (0) | 2021.01.06 |
| 오류 메시지 : (제공자 : 공유 메모리 공급자, 오류 : 0-파이프의 다른 쪽 끝에 프로세스가 없습니다.) (0) | 2021.01.06 |