跳至内容 跳至搜索

Active Support 错误报告器

ActiveSupport::ErrorReporter 是一个通用的错误报告服务接口。

要捕获并报告任何未处理的错误,您可以使用 handle 方法

Rails.error.handle do
  do_something!
end

如果引发错误,它将被报告并吞没。

或者,如果您想报告错误但不想吞没它,可以使用 record

Rails.error.record do
  do_something!
end

这两个方法都可以限制为只处理特定的错误类

maybe_tags = Rails.error.handle(Redis::BaseError) { redis.get("tags") }
命名空间
方法
A
D
H
N
R
S
U

常量

DEFAULT_RESCUE = [StandardError].freeze
 
DEFAULT_SOURCE = "application"
 
SEVERITIES = %i(error warning info)
 
UnexpectedError = Class.new(Exception)
 

Attributes

[RW] debug_mode
[RW] logger

类公共方法

new(*subscribers, logger: nil)

# File activesupport/lib/active_support/error_reporter.rb, line 35
def initialize(*subscribers, logger: nil)
  @subscribers = subscribers.flatten
  @logger = logger
  @debug_mode = false
  @context_middlewares = ErrorContextMiddlewareStack.new
end

实例公共方法

add_middleware(middleware)

添加一个中间件来修改错误上下文,然后再发送给订阅者。

中间件被添加到可调用栈中,在将错误传递给订阅者之前,在错误执行上下文上运行。允许在所有订阅者共享的错误上下文中创建条目。

上下文中间件接收与 report 相同的参数。它必须返回一个哈希——中间件栈在所有中间件运行后返回该哈希。中间件可以修改或替换哈希。

Rails.error.add_middleware(-> (error, context) { context.merge({ foo: :bar }) })
# File activesupport/lib/active_support/error_reporter.rb, line 218
def add_middleware(middleware)
  @context_middlewares.use(middleware)
end

disable(subscriber)

阻止订阅者在块的持续时间内收到错误通知。您可以传入订阅者本身,或其类。

这对于错误报告服务集成很有帮助,当它们希望处理堆栈中更高的任何错误时。

# File activesupport/lib/active_support/error_reporter.rb, line 186
def disable(subscriber)
  disabled_subscribers = (ActiveSupport::IsolatedExecutionState[self] ||= [])
  disabled_subscribers << subscriber
  begin
    yield
  ensure
    disabled_subscribers.delete(subscriber)
  end
end

handle(*error_classes, severity: :warning, context: {}, fallback: nil, source: DEFAULT_SOURCE)

评估给定的块,报告并吞没任何未处理的错误。如果没有引发错误,则返回块的返回值。否则,返回 fallback.call 的结果,如果 fallback 未指定,则返回 nil

# Will report a TypeError to all subscribers and return nil.
Rails.error.handle do
  1 + '1'
end

可以限制为只处理特定的错误类

maybe_tags = Rails.error.handle(Redis::BaseError) { redis.get("tags") }

选项

  • :severity - 此值会传递给订阅者,以指示错误报告的重要性。可以是 :error:warning:info。默认为 :warning

  • :context - 传递给订阅者的额外信息。例如

    Rails.error.handle(context: { section: "admin" }) do
      # ...
    end
    
  • :fallback - 一个可调用对象,当引发未处理的错误时,它提供 handle 的返回值。例如

    user = Rails.error.handle(fallback: -> { User.anonymous }) do
      User.find_by(params)
    end
    
  • :source - 此值会传递给订阅者,以指示错误的来源。订阅者可以使用此值忽略某些错误。默认为 "application"

# File activesupport/lib/active_support/error_reporter.rb, line 79
def handle(*error_classes, severity: :warning, context: {}, fallback: nil, source: DEFAULT_SOURCE)
  error_classes = DEFAULT_RESCUE if error_classes.empty?
  yield
rescue *error_classes => error
  report(error, handled: true, severity: severity, context: context, source: source)
  fallback.call if fallback
end

record(*error_classes, severity: :error, context: {}, source: DEFAULT_SOURCE)

评估给定的块,报告并重新抛出任何未处理的错误。如果没有引发错误,则返回块的返回值。

# Will report a TypeError to all subscribers and re-raise it.
Rails.error.record do
  1 + '1'
end

可以限制为只处理特定的错误类

tags = Rails.error.record(Redis::BaseError) { redis.get("tags") }

选项

  • :severity - 此值会传递给订阅者,以指示错误报告的重要性。可以是 :error:warning:info。默认为 :error

  • :context - 传递给订阅者的额外信息。例如

    Rails.error.record(context: { section: "admin" }) do
      # ...
    end
    
  • :source - 此值会传递给订阅者,以指示错误的来源。订阅者可以使用此值忽略某些错误。默认为 "application"

# File activesupport/lib/active_support/error_reporter.rb, line 115
def record(*error_classes, severity: :error, context: {}, source: DEFAULT_SOURCE)
  error_classes = DEFAULT_RESCUE if error_classes.empty?
  yield
rescue *error_classes => error
  report(error, handled: false, severity: severity, context: context, source: source)
  raise
end

report(error, handled: true, severity: handled ? :warning : :error, context: {}, source: DEFAULT_SOURCE)

直接向订阅者报告错误。当块式的 handlerecord 方法不适用时,您可以使用此方法。

Rails.error.report(error)

error 参数必须是 Exception 的实例。

Rails.error.report(Exception.new("Something went wrong"))

否则,您可以使用 unexpected 来报告接受字符串参数的错误。

# File activesupport/lib/active_support/error_reporter.rb, line 233
def report(error, handled: true, severity: handled ? :warning : :error, context: {}, source: DEFAULT_SOURCE)
  return if error.instance_variable_defined?(:@__rails_error_reported)
  raise ArgumentError, "Reported error must be an Exception, got: #{error.inspect}" unless error.is_a?(Exception)

  ensure_backtrace(error)

  unless SEVERITIES.include?(severity)
    raise ArgumentError, "severity must be one of #{SEVERITIES.map(&:inspect).join(", ")}, got: #{severity.inspect}"
  end

  full_context = @context_middlewares.execute(
    error,
    context: ActiveSupport::ExecutionContext.to_h.merge(context || {}),
    handled:,
    severity:,
    source:
  )

  disabled_subscribers = ActiveSupport::IsolatedExecutionState[self]
  @subscribers.each do |subscriber|
    unless disabled_subscribers&.any? { |s| s === subscriber }
      subscriber.report(error, handled: handled, severity: severity, context: full_context, source: source)
    end
  rescue => subscriber_error
    if logger
      logger.fatal(
        "Error subscriber raised an error: #{subscriber_error.message} (#{subscriber_error.class})\n" +
        subscriber_error.backtrace.join("\n")
      )
    else
      raise
    end
  end

  while error
    unless error.frozen?
      error.instance_variable_set(:@__rails_error_reported, true)
    end
    error = error.cause
  end

  nil
end

set_context(...)

更新可供错误订阅者访问的执行上下文。传递给 handlerecordreport 的任何上下文都将与此处设置的上下文合并。

Rails.error.set_context(section: "checkout", user_id: @user.id)
# File activesupport/lib/active_support/error_reporter.rb, line 202
def set_context(...)
  ActiveSupport::ExecutionContext.set(...)
end

subscribe(subscriber)

注册一个新的错误订阅者。订阅者必须响应

report(Exception, handled: Boolean, severity: (:error OR :warning OR :info), context: Hash, source: String)

report 方法**绝不能**引发错误。

# File activesupport/lib/active_support/error_reporter.rb, line 162
def subscribe(subscriber)
  unless subscriber.respond_to?(:report)
    raise ArgumentError, "Error subscribers must respond to #report"
  end
  @subscribers << subscriber
end

unexpected(error, severity: :warning, context: {}, source: DEFAULT_SOURCE)

在生产环境中报告给定错误,或在开发或测试环境中引发它。

在生产环境中调用时,错误报告后,此方法将返回 nil,执行将继续。

在开发环境中调用时,原始错误将被包装在一个不同的错误类中,以确保它不会被堆栈上层捕获,并会显示给开发人员。

此方法用于报告违反前提条件的断言,或类似情况,这些情况在生产环境中可以并且应该得到妥善处理,但不应该发生。

错误可以是异常实例或 String

example:

  def edit
    if published?
      Rails.error.unexpected("[BUG] Attempting to edit a published article, that shouldn't be possible")
      return false
    end
    # ...
  end
# File activesupport/lib/active_support/error_reporter.rb, line 146
def unexpected(error, severity: :warning, context: {}, source: DEFAULT_SOURCE)
  error = RuntimeError.new(error) if error.is_a?(String)

  if @debug_mode
    ensure_backtrace(error)
    raise UnexpectedError, "#{error.class.name}: #{error.message}", error.backtrace, cause: error
  else
    report(error, handled: true, severity: severity, context: context, source: source)
  end
end

unsubscribe(subscriber)

注销错误订阅者。可以接受订阅者或类。

subscriber = MyErrorSubscriber.new
Rails.error.subscribe(subscriber)

Rails.error.unsubscribe(subscriber)
# or
Rails.error.unsubscribe(MyErrorSubscriber)
# File activesupport/lib/active_support/error_reporter.rb, line 177
def unsubscribe(subscriber)
  @subscribers.delete_if { |s| subscriber === s }
end