Rails 애플리케이션의 오류 보고

이 가이드는 Ruby on Rails 애플리케이션에서 발생하는 예외를 관리하는 방법을 소개합니다.

이 가이드를 읽고 나면 다음을 알 수 있습니다:

  • Rails의 오류 리포터를 사용하여 오류를 캡처하고 보고하는 방법.
  • 오류 보고 서비스를 위한 사용자 정의 구독자를 만드는 방법.

오류 보고

Rails 오류 리포터는 애플리케이션에서 발생하는 예외를 수집하고 선호하는 서비스나 위치에 보고하는 표준화된 방법을 제공합니다.

오류 리포터는 다음과 같은 보일러플레이트 오류 처리 코드를 대체하는 것을 목표로 합니다:

begin
  do_something
rescue SomethingIsBroken => error
  MyErrorReportingService.notify(error)
end

일관된 인터페이스로:

Rails.error.handle(SomethingIsBroken) do
  do_something
end

Rails는 모든 실행(HTTP 요청, 작업, rails runner 호출 등)을 오류 리포터로 감싸므로, 애플리케이션에서 발생하는 모든 처리되지 않은 오류가 자동으로 구독자를 통해 오류 보고 서비스에 보고됩니다.

이는 타사 오류 보고 라이브러리가 더 이상 Rack 미들웨어를 삽입하거나 몽키 패칭을 수행할 필요가 없음을 의미합니다. ActiveSupport를 사용하는 라이브러리도 이를 통해 이전에는 로그에 손실되었던 경고를 비침입적으로 보고할 수 있습니다.

Rails의 오류 리포터를 사용하는 것은 필수는 아닙니다. 오류를 캡처하는 다른 모든 방법이 여전히 작동합니다.

리포터 구독하기

오류 리포터를 사용하려면 구독자가 필요합니다. 구독자는 report 메서드가 있는 모든 객체입니다. 애플리케이션에서 오류가 발생하거나 수동으로 보고되면 Rails 오류 리포터가 이 메서드를 호출하여 오류 객체와 일부 옵션을 전달합니다.

SentryHoneybadger와 같은 일부 오류 보고 라이브러리는 자동으로 구독자를 등록합니다. 자세한 내용은 공급업체의 문서를 참조하십시오.

또한 사용자 정의 구독자를 만들 수 있습니다. 예를 들면 다음과 같습니다:

# 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)

원하는 만큼 많은 구독자를 등록할 수 있습니다. Rails는 등록된 순서대로 이들을 차례로 호출합니다.

참고: Rails 오류 리포터는 환경에 관계없이 등록된 모든 구독자를 항상 호출합니다. 그러나 많은 오류 보고 서비스는 기본적으로 프로덕션 환경에서만 오류를 보고합니다. 필요에 따라 모든 환경에서 설정과 테스트를 수행해야 합니다.

오류 리포터 사용하기

오류 리포터를 사용하는 세 가지 방법이 있습니다:

오류 보고 및 삼킴

Rails.error.handle은 블록 내에서 발생한 모든 오류를 보고합니다. 그런 다음 오류를 삼켜 블록 외부의 나머지 코드가 정상적으로 계속 실행됩니다.

result = Rails.error.handle do
  1 + '1' # TypeError 발생
end
result # => nil
1 + 1 # 이 코드가 실행됩니다

블록에서 오류가 발생하지 않으면 Rails.error.handle이 블록의 결과를 반환하고, 그렇지 않으면 nil을 반환합니다. fallback을 제공하여 이를 재정의할 수 있습니다:

user = Rails.error.handle(fallback: -> { User.anonymous }) do
  User.find_by(params[:id])
end

오류 보고 및 다시 발생

Rails.error.record는 등록된 모든 구독자에게 오류를 보고한 다음 오류를 다시 발생시킵니다. 즉, 코드의 나머지 부분이 실행되지 않습니다.

Rails.error.record do
  1 + '1' # TypeError 발생
end
1 + 1 # 이 코드가 실행되지 않습니다

블록에서 오류가 발생하지 않으면 Rails.error.record가 블록의 결과를 반환합니다.

수동으로 오류 보고하기

Rails.error.report를 호출하여 수동으로 오류를 보고할 수도 있습니다:

begin
  # 코드
rescue StandardError => e
  Rails.error.report(e)
end

전달한 옵션은 오류 구독자에게 전달됩니다.

오류 보고 옵션

3가지 보고 API(#handle, #record, #report)는 모두 다음과 같은 옵션을 지원하며, 이 옵션은 등록된 모든 구독자에게 전달됩니다:

  • handled: 오류가 처리되었음을 나타내는 Boolean. 기본값은 true입니다. #record는 이 값을 false로 설정합니다.
  • severity: 오류의 심각도를 나타내는 Symbol. 예상되는 값은 :error, :warning, :info입니다. #handle은 이 값을 :warning으로, #record:error로 설정합니다.
  • context: 오류에 대한 추가 정보를 제공하는 Hash, 예를 들어 요청 또는 사용자 세부 정보
  • source: 오류의 출처를 나타내는 String. 기본 소스는 "application"입니다. 내부 라이브러리에서 보고된 오류는 다른 소스를 설정할 수 있습니다. 예를 들어 Redis 캐시 라이브러리는 "redis_cache_store.active_support"를 사용할 수 있습니다. 구독자는 소스를 사용하여 관심이 없는 오류를 무시할 수 있습니다.
Rails.error.handle(context: { user_id: user.id }, severity: :info) do
  # ...
end

오류 클래스별 필터링

Rails.error.handleRails.error.record에서는 특정 클래스의 오류만 보고하도록 선택할 수 있습니다. 예를 들면 다음과 같습니다:

Rails.error.handle(IOError) do
  1 + '1' # TypeError 발생
end
1 + 1 # TypeError는 IOError가 아니므로 이 코드가 실행됩니다

이 경우 TypeError는 Rails 오류 리포터에 의해 캡처되지 않습니다. IOError와 그 하위 클래스의 인스턴스만 보고됩니다. 다른 모든 오류는 정상적으로 발생합니다.

전역적으로 컨텍스트 설정

context 옵션을 통해 컨텍스트를 설정하는 것 외에도 #set_context API를 사용할 수 있습니다. 예를 들면 다음과 같습니다:

Rails.error.set_context(section: "checkout", user_id: @user.id)

이렇게 설정된 컨텍스트는 context 옵션과 병합됩니다.

Rails.error.set_context(a: 1)
Rails.error.handle(context: { b: 2 }) { raise }
# 보고된 컨텍스트는 {:a=>1, :b=>2}입니다.
Rails.error.handle(context: { b: 3 }) { raise }
# 보고된 컨텍스트는 {:a=>1, :b=>3}입니다.

라이브러리의 경우

오류 보고 라이브러리는 Railtie에서 구독자를 등록할 수 있습니다:

module MySdk
  class Railtie < ::Rails::Railtie
    initializer "my_sdk.error_subscribe" do
      Rails.error.subscribe(MyErrorSubscriber.new)
    end
  end
end

오류 구독자를 등록했지만 여전히 Rack 미들웨어와 같은 다른 오류 메커니즘이 있는 경우, 오류가 여러 번 보고될 수 있습니다. 다른 메커니즘을 제거하거나 이전에 본 예외는 보고하지 않도록 보고 기능을 조정해야 합니다.