1 에러 보고
Rails error reporter는 애플리케이션에서 발생하는 에러를 수집하고 선호하는 서비스나 위치(예: Sentry와 같은 모니터링 서비스)에 보고하는 표준 방법을 제공합니다.
이는 다음과 같은 상용구 에러 처리 코드를 대체하는 것을 목표로 합니다:
begin
do_something
rescue SomethingIsBroken => error
MyErrorReportingService.notify(error)
end
일관된 인터페이스를 통해:
Rails.error.handle(SomethingIsBroken) do
do_something
end
Rails는 모든 실행(HTTP 요청, jobs, rails runner 호출 등)을 error reporter로 감싸므로, 앱에서 발생한 모든 처리되지 않은 에러는 자동으로 구독자를 통해 에러 보고 서비스에 보고됩니다.
이는 서드파티 에러 보고 라이브러리가 처리되지 않은 에러를 캡처하기 위해 Rack 미들웨어를 삽입하거나 몽키 패칭을 할 필요가 없다는 것을 의미합니다. Active Support를 사용하는 라이브러리들도 이를 활용하여 이전에는 로그에서 누락되었던 경고들을 비침투적으로 보고할 수 있습니다.
다른 에러 캡처 방법도 여전히 작동하므로 Rails error reporter 사용은 선택사항입니다.
1.1 Reporter 구독하기
외부 서비스와 함께 error reporter를 사용하려면 subscriber가 필요합니다. subscriber는 report
메서드가 있는 모든 Ruby 객체가 될 수 있습니다. 애플리케이션에서 오류가 발생하거나 수동으로 보고될 때, Rails error reporter는 error 객체와 일부 옵션을 사용하여 이 메서드를 호출합니다.
Sentry와 Honeybadger와 같은 일부 error-reporting 라이브러리들은 자동으로 subscriber를 등록합니다.
사용자 정의 subscriber를 생성할 수도 있습니다. 예를 들어:
# config/initializers/error_subscriber.rb
class ErrorSubscriber
def report(error, handled:, severity:, context:, source: nil)
MyErrorReportingService.report_error(error, context: context, handled: handled, level: severity)
end
end
구독자 클래스를 정의한 후, Rails.error.subscribe
메서드를 호출하여 등록할 수 있습니다:
Rails.error.subscribe(ErrorSubscriber.new)
원하는 만큼 많은 subscriber를 등록할 수 있습니다. Rails는 등록된 순서대로 이들을 호출합니다.
Rails.error.unsubscribe
를 호출하여 subscriber를 등록 해제하는 것도 가능합니다. 이는 의존성에 의해 추가된 subscriber를 교체하거나 제거하고 싶을 때 유용할 수 있습니다. subscribe
와 unsubscribe
모두 다음과 같이 subscriber나 클래스를 인자로 받을 수 있습니다:
subscriber = ErrorSubscriber.new
Rails.error.unsubscribe(subscriber)
# 또는
Rails.error.unsubscribe(ErrorSubscriber)
Rails error reporter는 환경에 관계없이 등록된 subscriber들을 항상 호출합니다. 하지만 많은 error-reporting 서비스들은 기본적으로 production 환경에서만 에러를 보고합니다. 필요에 따라 각 환경별로 설정하고 테스트해야 합니다.
1.2 Error Reporter 사용하기
Rails error reporter는 다음과 같이 다양한 방법으로 에러를 보고할 수 있는 네 가지 메소드를 제공합니다:
Rails.error.handle
Rails.error.record
Rails.error.report
Rails.error.unexpected
1.2.1 에러 보고와 무시하기
Rails.error.handle
메소드는 블록 내에서 발생하는 모든 에러를 보고합니다. 그런 다음 에러를 무시하고, 블록 외부의 나머지 코드는 정상적으로 계속 실행됩니다.
result = Rails.error.handle do
1 + "1" # TypeError를 발생시킴
end
result # => nil
1 + 1 # 이것은 실행될 것입니다
블록에서 error가 발생하지 않으면 Rails.error.handle
은 블록의 결과를 반환하고, 그렇지 않으면 nil
을 반환합니다. fallback
을 제공하여 이를 오버라이드할 수 있습니다.
user = Rails.error.handle(fallback: -> { User.anonymous }) do
User.find(params[:id])
end
위 코드는 User.anonymous를 fallback으로 사용하여 실행 오류를 처리합니다.
1.2.2 에러 보고 및 재발생
Rails.error.record
메서드는 등록된 모든 구독자에게 에러를 보고한 다음 에러를 재발생시킵니다. 이는 나머지 코드가 실행되지 않는다는 것을 의미합니다.
Rails.error.record do
1 + "1" # TypeError 발생
end
1 + 1 # 이것은 실행되지 않음
블록에서 에러가 발생하지 않으면, Rails.error.record
는 블록의 결과값을 반환합니다.
1.2.3 수동으로 에러 보고하기
Rails.error.report
를 호출하여 수동으로 에러를 보고할 수도 있습니다:
begin
# code
rescue StandardError => e
Rails.error.report(e)
end
위 코드는 error를 report하는 방법을 보여줍니다. error reporter는 exception이 발생했을 때 추가적인 context를 수집하고 error tracking service에 정보를 보냅니다.
전달하는 모든 옵션은 error subscriber에게 전달됩니다.
1.2.4 예상치 못한 오류 보고하기
Rails.error.unexpected
를 호출하여 예상치 못한 오류를 보고할 수 있습니다.
production 환경에서 호출될 때, 이 메서드는 오류가 보고된 후 nil을 반환하고 코드 실행이 계속됩니다.
development 환경에서 호출될 때, 오류는 새로운 error 클래스로 감싸져서(스택의 상위에서 rescue되지 않도록 하기 위해) 디버깅을 위해 개발자에게 표시됩니다.
예시:
def edit
if published?
Rails.error.unexpected("[BUG] 공개된 게시글을 편집하려고 시도했습니다. 이는 불가능해야 합니다")
false
end
# ...
end
이 메소드는 일반적인 사용으로 인한 결과가 아닌, production 환경에서 발생할 수 있는 모든 에러를 우아하게 처리하기 위한 것입니다.
1.3 Error-reporting Options
reporting API인 #handle
, #record
, 그리고 #report
는 다음과 같은 옵션을 지원하며, 이 옵션들은 등록된 모든 subscriber에게 전달됩니다:
handled
: error가 처리되었는지를 나타내는Boolean
값입니다. 기본값은true
입니다.#record
는 이 값을false
로 설정합니다.severity
: error의 심각도를 나타내는Symbol
입니다. 예상되는 값은:error
,:warning
,:info
입니다.#handle
은 이를:warning
으로 설정하고,#record
는:error
로 설정합니다.context
: error에 대한 추가 맥락(요청이나 사용자 세부정보 등)을 제공하는Hash
입니다source
: error의 출처를 나타내는String
입니다. 기본 source는"application"
입니다. 내부 라이브러리에서 보고된 error는 다른 source를 설정할 수 있습니다. 예를 들어 Redis cache 라이브러리는"redis_cache_store.active_support"
를 사용할 수 있습니다. subscriber는 source를 사용하여 관심 없는 error를 무시할 수 있습니다.
Rails.error.handle(context: { user_id: user.id }, severity: :info) do
# ...
end
1.4 전역적으로 Context 설정하기
context
옵션을 통해 context를 설정하는 것 외에도, Rails.error.set_context
를 사용할 수 있습니다. 예를 들면:
Rails.error.set_context(section: "checkout", user_id: @user.id)
이런 방식으로 설정된 모든 context는 context
옵션과 병합됩니다
Rails.error.set_context(a: 1)
Rails.error.handle(context: { b: 2 }) { raise }
# 보고될 context는: {:a=>1, :b=>2}
Rails.error.handle(context: { b: 3 }) { raise }
# 보고될 context는: {:a=>1, :b=>3}
1.5 Error 클래스로 필터링하기
Rails.error.handle
과 Rails.error.record
를 사용하면 특정 클래스의 에러만 보고하도록 선택할 수 있습니다. 예시:
Rails.error.handle(IOError) do
1 + "1" # TypeError를 발생시킴
end
1 + 1 # TypeError는 IOError가 아니므로 이 코드는 *실행되지 않을* 것입니다
여기서 TypeError
는 Rails 에러 리포터에 의해 캡처되지 않습니다. IOError
와 그 하위 클래스의 인스턴스만이 보고됩니다. 다른 모든 에러들은 정상적으로 발생됩니다.
1.6 Notification 비활성화하기
Rails.error.disable
을 호출하여 블록의 실행 동안 subscriber가 에러 알림을 받지 않도록 할 수 있습니다. subscribe
와 unsubscribe
와 유사하게, subscriber 자체 또는 그 클래스를 전달할 수 있습니다.
Rails.error.disable(ErrorSubscriber) do
1 + "1" # TypeError는 ErrorSubscriber를 통해 보고되지 않습니다
end
이는 에러 처리를 다르게 하거나 스택의 상위에서 처리하고자 하는 서드파티 에러 보고 서비스에도 도움이 될 수 있습니다.
2 Error-reporting 라이브러리
Error-reporting 라이브러리는 Railtie에서 subscriber를 등록할 수 있습니다:
module MySdk
class Railtie < ::Rails::Railtie
initializer "my_sdk.error_subscribe" do
Rails.error.subscribe(MyErrorSubscriber.new)
end
end
end
위 코드는 MySdk 모듈의 Railtie에서 MyErrorSubscriber를 Rails error system에 등록하는 예시입니다.
error subscriber를 등록했지만 Rack middleware와 같은 다른 에러 처리 메커니즘이 있는 경우, 에러가 여러 번 보고될 수 있습니다. 다른 메커니즘을 제거하거나 이미 보고된 에러는 다시 보고하지 않도록 보고 기능을 조정해야 합니다.