Skip to content

Commit fa6573d

Browse files
committed
Improve error-reporting guide
1 parent 93b2a2c commit fa6573d

File tree

1 file changed

+82
-63
lines changed

1 file changed

+82
-63
lines changed

guides/source/error_reporting.md

Lines changed: 82 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -8,53 +8,48 @@ This guide introduces ways to manage exceptions that occur in Ruby on Rails appl
88
After reading this guide, you will know:
99

1010
* How to use Rails' error reporter to capture and report errors.
11-
12-
(Because error reporting libraries are mainly for production use, this guide is mostly for production environment too.
13-
However, you may want to test related setup in development by temporarily activating the libraries locally.)
11+
* How to create custom subscribers for your error-reporting service.
1412

1513
--------------------------------------------------------------------------------
1614

17-
Error Reporter
15+
Error Reporting
1816
------------------------
1917

20-
The [error reporter](https://api.rubyonrails.org/classes/ActiveSupport/ErrorReporter.html) collects exceptions occur in Ruby on Rails applications and reports them to registered subscribers.
21-
22-
The goals are to
18+
The Rails [error reporter](https://api.rubyonrails.org/classes/ActiveSupport/ErrorReporter.html) provides a standard way to collect exceptions that occur in your application and report them to your preferred service or location.
2319

24-
1. Automatically capture and report any unhandled exception from a controller or a job.
20+
The error reporter aims to replace boilerplate error-handling code like this:
2521

26-
2. Replace such manual rescue in applications and libraries
27-
28-
```rb
22+
```ruby
2923
begin
3024
do_something
3125
rescue SomethingIsBroken => error
3226
MyErrorReportingService.notify(error)
3327
end
3428
```
3529

36-
with
30+
with a consistent interface:
3731

38-
```rb
32+
```ruby
3933
Rails.error.handle(SomethingIsBroken) do
4034
do_something
4135
end
4236
```
4337

44-
This approach provides several benefits:
38+
Rails wraps all executions (such as HTTP requests, jobs, and `rails runner` invocations) in the error reporter, so any unhandled errors raised in your app will automatically be reported to your error-reporting service via their subscribers.
39+
40+
This means that third-party error-reporting libraries no longer need to insert a Rack middleware or do any monkey-patching to capture unhandled exceptions. Libraries that use ActiveSupport can also use this to non-intrusively report warnings that would previously have been lost in logs.
4541

46-
* Application or error reporting libraries don't need to insert a Rack middleware to capture unhandled exceptions from requests anymore.
47-
* To ActiveSupport-aware libraries, this can be used to report errors to the host application.
48-
* It decouples application code from error reporting libraries.
49-
* It reduces boilerplate code for handling exceptions.
50-
* Error reporting libraries will need less monkey-patches and be less intrusive to applications.
42+
Using the Rails' error reporter is not required. All other means of capturing errors still work.
5143

52-
### Subscribe To The Reporter
44+
### Subscribing to the Reporter
5345

54-
An error subscriber is expected to have a `report` method that takes an exception object and a few options.
55-
For example:
46+
To use the error reporter, you need a _subscriber_. A subscriber is any object with a `report` method. When an error occurs in your application or is manually reported, the Rails error reporter will call this method with the error object and some options.
5647

57-
```rb
48+
Some error-reporting libraries, such as [Sentry's](https://github.com/getsentry/sentry-ruby/blob/e18ce4b6dcce2ebd37778c1e96164684a1e9ebfc/sentry-rails/lib/sentry/rails/error_subscriber.rb) and [Honeybadger's](https://docs.honeybadger.io/lib/ruby/integration-guides/rails-exception-tracking/), automatically register a subscriber for you. Consult your provider's documentation for more details.
49+
50+
You may also create a custom subscriber. For example:
51+
52+
```ruby
5853
# config/initializers/error_subscriber.rb
5954
class ErrorSubscriber
6055
def report(error, handled:, severity:, context:, source: nil)
@@ -63,93 +58,115 @@ class ErrorSubscriber
6358
end
6459
```
6560

66-
After defining the subscriber class, you can register its instance with the [`#subscribe`](https://api.rubyonrails.org/classes/ActiveSupport/ErrorReporter.html#method-i-subscribe) method:
61+
After defining the subscriber class, register it by calling [`Rails.error.subscribe`](https://api.rubyonrails.org/classes/ActiveSupport/ErrorReporter.html#method-i-subscribe) method:
6762

68-
```rb
63+
```ruby
6964
Rails.error.subscribe(ErrorSubscriber.new)
7065
```
7166

72-
To test the error subscriber, try this in Rails console:
73-
74-
```
75-
irb(main):001:0> Rails.error.handle { raise }
76-
```
77-
78-
And see if the error is reported to the service you use.
79-
80-
#### Libraries Subscribe To Rails Reporter
67+
You can register as many subscribers as you wish. Rails will call them in turn, in the order in which they were registered.
8168

82-
Some libraries may provide their own subscriber classes. Please check their documentation for more information.
69+
Note: The Rails error-reporter will always call registered subscribers, regardless of your environment. However, many error-reporting services only report errors in production by default. You should configure and test your setup across environments as needed.
8370

84-
- [Sentry](https://sentry.io/) ([document](https://github.com/getsentry/sentry-ruby/blob/master/sentry-rails/lib/sentry/rails/error_subscriber.rb))
71+
### Using the Error Reporter
8572

86-
### Capture and Report Errors
73+
There are three ways you can use the error reporter:
8774

88-
You can wrap your code inside a block with the reporting APIs, which will report the exceptions surface from the block.
75+
#### Reporting and swallowing errors
76+
[`Rails.error.handle`](https://api.rubyonrails.org/classes/ActiveSupport/ErrorReporter.html#method-i-handle) will report any error raised within the block. It will then **swallow** the error, and the rest of your code outside the block will continue as normal.
8977

90-
To report and **swallow** the error, use [`Rails.error.handle`](https://api.rubyonrails.org/classes/ActiveSupport/ErrorReporter.html#method-i-handle):
91-
92-
```rb
93-
Rails.error.handle do
78+
```ruby
79+
result = Rails.error.handle do
9480
1 + '1' # raises TypeError
9581
end
82+
result # => nil
9683
1 + 1 # This will be executed
9784
```
9885

99-
The error will be reported with `handled: true`
86+
If no error is raised in the block, `Rails.error.handle` will return the result of the block, otherwise it will return `nil`. You can override this by providing a `fallback`:
10087

101-
To report but **not swallow** the error, use [`Rails.error.record`](https://api.rubyonrails.org/classes/ActiveSupport/ErrorReporter.html#method-i-record):
88+
```ruby
89+
user = Rails.error.handle(fallback: -> { User.anonymous }) do
90+
User.find_by(params[:id])
91+
end
92+
```
10293

103-
```rb
94+
#### Reporting and re-raising errors
95+
[`Rails.error.record`](https://api.rubyonrails.org/classes/ActiveSupport/ErrorReporter.html#method-i-record) will report errors to all registered subscribers and then re-raise the error, meaning that the rest of your code won't execute.
96+
97+
```ruby
10498
Rails.error.record do
10599
1 + '1' # raises TypeError
106100
end
107101
1 + 1 # This won't be executed
108102
```
109103

110-
The error will be reported with `handled: false`
104+
If no error is raised in the block, `Rails.error.record` will return the result of the block.
111105

112-
If you decide to rescue the exception manually, you can also report it with [`Rails.error.report`](https://api.rubyonrails.org/classes/ActiveSupport/ErrorReporter.html#method-i-report):
106+
#### Manually reporting errors
107+
You can also manually report errors by calling [`Rails.error.report`](https://api.rubyonrails.org/classes/ActiveSupport/ErrorReporter.html#method-i-report):
113108

114-
```rb
109+
```ruby
115110
begin
116111
# code
117112
rescue StandardError => e
118113
Rails.error.report(e)
119114
end
120115
```
121116

122-
#### Options
117+
Any options you pass will be passed on the error subscribers.
118+
119+
### Error-reporting options
123120

124-
All 3 reporting APIs (`#handle`, `#record`, and `#report`) support the same options:
121+
All 3 reporting APIs (`#handle`, `#record`, and `#report`) support the following options, which are then passed along to all registered subscribers:
125122

126-
- `handled`: a `Boolean` to tell if the error was handled
127-
- `severity`: a `Symbol` about the severity of the exception. Expected values are: `:error`, `:warning`, and `:info`
128-
- `context`: a `Hash` to provide more context about the error, like request headers or record attributes
129-
- `source`: a `String` about the source of the exception. Default is `"application"`
130-
- You can use it to skip exceptions from certain sources
123+
- `handled`: a `Boolean` to indicate if the error was handled. This is set to `true` by default. `#record` sets this to `false`.
124+
- `severity`: a `Symbol` describing the severity of the error. Expected values are: `:error`, `:warning`, and `:info`. `#handle` sets this to `:warning`, while `#record` sets it to `:error`.
125+
- `context`: a `Hash` to provide more context about the error, like request or user details
126+
- `source`: a `String` about the source of the error. The default source is `"application"`. Errors reported by internal libraries may set other sources; the Redis cache library may use `"redis_cache_store.active_support"`, for instance. Your subscriber can use the source to ignore errors you aren't interested in.
131127

132-
### Setting Context
128+
```ruby
129+
Rails.error.handle(context: {user_id: user.id}, severity: :info) do
130+
# ...
131+
end
132+
```
133133

134-
In addition to setting context through the `context` option, you can also use the [`#set_context`](https://api.rubyonrails.org/classes/ActiveSupport/ErrorReporter.html#method-i-set_context) API. For example:
134+
### Filtering by Error Class
135135

136-
```rb
136+
With `Rails.error.handle` and `Rails.error.record`, you can also choose to only report errors of a certain class. For example:
137+
138+
```ruby
139+
Rails.error.handle(IOError) do
140+
1 + '1' # raises TypeError
141+
end
142+
1 + 1 # TypeErrors are not IOErrors, so this will *not* be executed
143+
```
144+
145+
Here, the `TypeError` will not be captured by the Rails error reporter. Only instances of `IOError` and its descendants will be reported. Any other errors will be raised as normal.
146+
147+
### Setting Context Globally
148+
149+
In addition to setting context through the `context` option, you can use the [`#set_context`](https://api.rubyonrails.org/classes/ActiveSupport/ErrorReporter.html#method-i-set_context) API. For example:
150+
151+
```ruby
137152
Rails.error.set_context(section: "checkout", user_id: @user.id)
138153
```
139154

140-
The context set this way will be merged with the `context` option
155+
Any context set this way will be merged with the `context` option
141156

142-
```rb
157+
```ruby
143158
Rails.error.set_context(a: 1)
144159
Rails.error.handle(context: { b: 2 }) { raise }
145-
# the reported context will be: {:a=>1, :b=>2}
160+
# The reported context will be: {:a=>1, :b=>2}
161+
Rails.error.handle(context: { b: 3 }) { raise }
162+
# The reported context will be: {:a=>1, :b=>3}
146163
```
147164

148165
### For Libraries
149166

150-
Libraries can easily register their subscribers in `Railtie`:
167+
Error-reporting libraries can register their subscribers in a `Railtie`:
151168

152-
```rb
169+
```ruby
153170
module MySdk
154171
class Railtie < ::Rails::Railtie
155172
initializer "error_subscribe.my_sdk" do
@@ -158,3 +175,5 @@ module MySdk
158175
end
159176
end
160177
```
178+
179+
If you register an error subscriber, but still have other error mechanisms like a Rack middleware, you may end up with errors reported multiple times. You should either remove your other mechanisms or adjust your report functionality so it skips reporting an exception it has seen before.

0 commit comments

Comments
 (0)