rails: Rails standardized error reporting interface

Context

A very common pattern when dealing with exceptions is to report them asynchronously to the developer and continue with some fallback. E.g.

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

An example in Rails is in ActiveSupport::Cache::RedisCacheStore we simply log handled errors.

That is the kind of errors you’d want reported to your error monitoring service so that even though the error is not directly breaking the user experience, you are alerted early that some part of your infrastructure is no longer working, and might take action, or that some edge case is causing a degraded experience.

Another example is the recent ActiveSupport::Notifications handling of exceptions raised by finish. In such case the most appropriate behaviour could be to swallow the exception, as to not interrupt the notification chain, but to report it to the application developer.

In application code it’s easy because you know where to send the exception report, but in libraries and frameworks, usually the best you can do is log the error. But people usually don’t look at logs unless they know something is happening.

Introducing a standard interface for error reporting services

Since the vast majority of Rails application out there do setup an error reporting service (Sentry, Airbrake, Errbit, HoneyBadger, and plenty others), I believe there’s an opportunity for Rails to provide a generic interface to report errors, with an adapter API to allow using the service of your choice. In other term, introducing an Active Job or Active Support Cache for error reporting.

There would be several upsides to this.

Reporting the errors handled by Rails

When Rails handles an error like in the examples listed above, it could report them properly to the developer, instead of logging them where no-one will see.

Reporting warnings

Similarly there are places in Rails were we emit warnings, but they aren’t easy to notice. Many error reporting service have some kind of severity attribute, so Rails could report these as exceptions with severity: warning.

Simplified collection of unhandled errors

The hooking mechanism would be simplified. The various error reporting gems out there are doing a lot of work to hook into Rack / Sidekiq / Resque etc. It’s brittle and hard to maintain and setup. It’s also often not covering all entry points such as rails runner being invoked from a crontab, etc.

If Rails was in charge of collecting unhandled errors, it could do it more easily, likely as part of ActiveSupport::Executor, and it would automatically cover all Rails entry points.

Simplified errors notification service setup

The setup of the various error reporting service could be heavily simplified. They could ship a Railtie in their gem and automatically register themselves to receive the exceptions. No need to add a middleware in Rack, or require some code to hook into Sidekiq and such.

Interface Requirements

I believe that along the error itself with its backtrace, there are two essential attributes that need to be communicated:

  • handled: true/false: Wether the error was properly handled by the application, or if it was “final” (e.g. displayed a 500)
  • severity: error/warning: Even when it’s handled by the application, an error can either be “nice to know about”, or important to fix quickly. As such I think it’s important to be able to communicate this.

Of course all reporting services don’t just log the error, they collect various contextual data alongside it. This often include request or job parameters, sometimes app specific data such as an user_id, etc.

So the interface would need to be extensible.

We can probably draw some inspiration from the recent ActiveRecord::QueryLogs, the need is similar, as we enter some paths we do want to append some data to the error reporter context. e.g. Job class and parameters when performing a job.

Or even refactor ActiveRecord::QueryLogs.set_context, to move it to a more generic namespace, and share it with the error reporter, as many of the data will be the same.

And it is also important to be able to pass extra data when reporting an error, e.g.:

Rails.error.report(some_error, context: { foo: "bar" })

The signature could be something like:

def report(error, handled: true, severity: :error, context: {})
end

There is likely some other interface requirements I’m not thinking about, so I’d like to involve as many of these gems maintainers as possible to collect some feedback.

Feedback

At this early stage I would love to hear some feedback from the maintainers of the various error monitoring gems out there:

@imjoehaines (Bugsnag) @st0012 (Sentry) @joshuap, @shalvah (HoneyBadger) @kyrylo (Airbrake) @waltjones (Rollbar) @samuel-holt, @mogest (Raygun) @composit, @dlanderson, @okapusta (Scout) @angelatan2, @amhuntsman, @tannalynn, @jasonrclark (NewRelic)

Sorry if I forgot other popular services. And apologies if you didn’t wish to be pinged.

If not, I’d have a few questions:

  • Would you integrate with such interface?
  • What kind of metadata do you need provided by this interface?

cc @rafaelfranca @eileencodes @jhawthorn

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 124
  • Comments: 16 (16 by maintainers)

Commits related to this issue

Most upvoted comments

It might be a good idea to look through how each of the available error reporting clients work to see what sorts of hoops they had to jump through

Yes, I did that. There’s a section about it in the main issue description. The main challenge is to hook into all the entry points (rack, sidekiq/resque/queue, cron, etc). That’s what make their setup more complicated that it need to be, and that’s were Rails could make them entirely plug-and-play.

what are your goals?

Again I believe I did line this out in the issue description. The goal number one is to allow Rails to report internal exceptions to the developer without breaking the current request / job nor simply writing them to log where most likely no-one will see them.

But to do that we need error reporters to hook into that centralized interface, so to incentive that, we need to make that interface more convenient than hooking directly un the underlying runtimes.

@tenderlove either place is fine

it’d be great to have a gem to also add the error subscriber concept to Rails 6

Ok. I’ll keep that in mind and consider it once I’m done with Rails 7 (assuming this actually ship for 7).

@Widdershin thanks a ton for that feedback!

It would be ideal if this interface could provide a way of accessing this information without the use of an additional middleware.

Absolutely, that will be possible. My current plan is to piggy back on ActiveRecord::QueryLogs.set_context (after moving it to Active Support), so it would be automatically added to the context object here: https://github.com/rails/rails/blob/6d983e86da8c819e69b8e9b8173b3eeb4b7c5496/actionpack/lib/action_controller/metal/query_tags.rb#L13

As for getting the user id from there, since Rails doesn’t deal with the authentication I think it won’t change much, but libraries such as Devise will be able to do something like ToBeDefined.set_context(user: current_user).

On another note, I’ll likely start the early implementation Monday, if anyone has some feedback to give before I start.