rubyonrails.org에서 더 보기:

GitHub에서 이 파일을 읽지 마세요. 가이드는 https://guides.rubyonrails.org 에서 제공됩니다.

Rails 애플리케이션의 에러 보고

이 가이드는 Rails 애플리케이션에서 에러를 관리하는 방법을 소개합니다.

이 가이드를 읽은 후에는 다음을 알 수 있습니다:

  • Rails의 error reporter를 사용하여 에러를 캡처하고 보고하는 방법
  • 에러 보고 서비스를 위한 custom subscriber를 만드는 방법

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를 교체하거나 제거하고 싶을 때 유용할 수 있습니다. subscribeunsubscribe 모두 다음과 같이 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.handleRails.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가 에러 알림을 받지 않도록 할 수 있습니다. subscribeunsubscribe와 유사하게, 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와 같은 다른 에러 처리 메커니즘이 있는 경우, 에러가 여러 번 보고될 수 있습니다. 다른 메커니즘을 제거하거나 이미 보고된 에러는 다시 보고하지 않도록 보고 기능을 조정해야 합니다.



맨 위로