Global Error Handler
by Adrien Poly
Updated for Stimulus 2
Pain Point
You want to catch errors pertaining to Stimulus (connect
errors, targets
not being accessible etc.) as well as application errors within your controllers and send them to an error tracking service of your choice (e.g. Sentry, Honeybadger etc.).
Solution
First define a global Application Controller that your Stimulus controllers will inherit from.
Within this ApplicationController define a new handleError
function. Here this function creates a small context object with the id of the current user (you can pass all context information that can help you debug: environment, subscription status, etc).
Stimulus includes a largely undocumented error handler (see links below). By default, this error handle catches all Stimulus internal errors and output a prettified console.log
.
Within our handleError
function we can call this application.handleError
with our custom context to have a single function being responsible to manage and report errors.
// app/javascript/controllers/application_controller.js import { Controller } from "@hotwired/stimulus"; export default class extends ApplicationController { handleError = (error) => { const context = { controller: this.identifier, user_id: this.userId, }; this.application.handleError( error, `Error in controller: ${this.identifier}`, context ); }; get userId() { return this.metaValue("user_id"); } }
In your controllers now you can catch errors and pass them to this.handleError
for reporting.
// app/javascript/controllers/some_controller.js export default class extends ApplicationController { someFunc() { try { // ... } catch (err) { this.handleError(err); } } }
Adding a reporting service
Here is an example to configure Sentry for reporting Stimulus errors :
// app/javascript/packs/application.js import { Application } from "@hotwired/stimulus"; import { definitionsFromContext } from "stimulus/webpack-helpers"; const application = Application.start(); const context = require.context("./controllers", true, /\.js$/); application.load(definitionsFromContext(context)); // memorize default handler const defaultErrorHandler = application.handleError.bind(application); // configure Sentry to log errors and prepend the default handler const sentryErrorHandler = (error, message, detail = {}) => { defaultErrorHandler(error, message, detail); Sentry.captureException(error, { message, ...detail }); }; // overwrite the default handler with our new composed handler application.handleError = sentryErrorHandler;
This new instrumented handler will now send global errors to Sentry. The example above shows how you can furthermore use this function to catch errors in our own application code in try/catch
blocks.
Rationale
The rationale behind all this, is to have a single error handler for all errors within a Stimulus Controller. Whether they are Stimulus or application errors they all go through the same path. Once this unique handler is in place it is easy to add context to the error message and dispatch errors to various reporting systems such as Sentry, Honeybadger or others.