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 오류 방법 :extend
rescue
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
.
나는 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는 initialize
d, rescue
d이고 메시지가 인쇄되었습니다. 여태까지는 그런대로 잘됐다. 두 번째 테스트 (위에 인용 된 @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 #cause
s 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_s
instead ofmessage
since by defaultmessage
is defined as simplyto_s
(at least in Ruby 2.0 and 2.2 where I tested it) - Calls
extend
for you instead of making the caller do that extra step. - Uses
define_method
and a closure so that the local variablemessage
can 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
, andinspect
all 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 |