Class: ActiveSupport::ErrorReporter

Inherits:
Object
  • Object
show all
Defined in:
activesupport/lib/active_support/error_reporter.rb

Overview

ActiveSupport::ErrorReporter is a common interface for error reporting services.

To rescue and report any unhandled error, you can use the handle method:

Rails.error.handle do
  do_something!
end

If an error is raised, it will be reported and swallowed.

Alternatively if you want to report the error but not swallow it, you can use record

Rails.error.record do
  do_something!
end

Both methods can be restricted to only handle a specific exception class

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

You can also pass some extra context information that may be used by the error subscribers:

Rails.error.handle(context: { section: "admin" }) do
  # ...
end

Additionally a severity can be passed along to communicate how important the error report is. severity can be one of :error, :warning, or :info. Handled errors default to the :warning severity, and unhandled ones to :error.

Both handle and record pass through the return value from the block. In the case of handle rescuing an error, a fallback can be provided. The fallback must be a callable whose result will be returned when the block raises and is handled:

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

Constant Summary collapse

SEVERITIES =
%i(error warning info)
DEFAULT_SOURCE =
"application"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*subscribers, logger: nil) ⇒ ErrorReporter

Returns a new instance of ErrorReporter.


47
48
49
50
# File 'activesupport/lib/active_support/error_reporter.rb', line 47

def initialize(*subscribers, logger: nil)
  @subscribers = subscribers.flatten
  @logger = logger
end

Instance Attribute Details

#loggerObject

Returns the value of attribute logger.


45
46
47
# File 'activesupport/lib/active_support/error_reporter.rb', line 45

def logger
  @logger
end

Instance Method Details

#disable(subscriber) ⇒ Object

Prevent a subscriber from being notified of errors for the duration of the block.

It can be used by error reporting service integration when they wish to handle the error higher in the stack.


89
90
91
92
93
94
95
96
97
# File 'activesupport/lib/active_support/error_reporter.rb', line 89

def disable(subscriber)
  disabled_subscribers = (ActiveSupport::IsolatedExecutionState[self] ||= [])
  disabled_subscribers << subscriber
  begin
    yield
  ensure
    disabled_subscribers.delete(subscriber)
  end
end

#handle(error_class = StandardError, severity: :warning, context: {}, fallback: nil, source: DEFAULT_SOURCE) ⇒ Object

Report any unhandled exception, and swallow it.

Rails.error.handle do
  1 + '1'
end

58
59
60
61
62
63
# File 'activesupport/lib/active_support/error_reporter.rb', line 58

def handle(error_class = StandardError, severity: :warning, context: {}, fallback: nil, source: DEFAULT_SOURCE)
  yield
rescue error_class => error
  report(error, handled: true, severity: severity, context: context, source: source)
  fallback.call if fallback
end

#record(error_class = StandardError, severity: :error, context: {}, source: DEFAULT_SOURCE) ⇒ Object


65
66
67
68
69
70
# File 'activesupport/lib/active_support/error_reporter.rb', line 65

def record(error_class = StandardError, severity: :error, context: {}, source: DEFAULT_SOURCE)
  yield
rescue error_class => 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) ⇒ Object

When the block based handle and record methods are not suitable, you can directly use report

Rails.error.report(error)

111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'activesupport/lib/active_support/error_reporter.rb', line 111

def report(error, handled: true, severity: handled ? :warning : :error, context: {}, source: DEFAULT_SOURCE)
  unless SEVERITIES.include?(severity)
    raise ArgumentError, "severity must be one of #{SEVERITIES.map(&:inspect).join(", ")}, got: #{severity.inspect}"
  end

  full_context = ActiveSupport::ExecutionContext.to_h.merge(context)
  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

  nil
end

#set_contextObject

Update the execution context that is accessible to error subscribers

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

See ActiveSupport::ExecutionContext.set


104
105
106
# File 'activesupport/lib/active_support/error_reporter.rb', line 104

def set_context(...)
  ActiveSupport::ExecutionContext.set(...)
end

#subscribe(subscriber) ⇒ Object

Register a new error subscriber. The subscriber must respond to

report(Exception, handled: Boolean, context: Hash)

The report method should never raise an error.


77
78
79
80
81
82
# File 'activesupport/lib/active_support/error_reporter.rb', line 77

def subscribe(subscriber)
  unless subscriber.respond_to?(:report)
    raise ArgumentError, "Error subscribers must respond to #report"
  end
  @subscribers << subscriber
end