Custom code on exception, for all controllers

We use the Rollbar JS library to report exceptions to our Rollbar account. Unfortunately we don’t have any report of exceptions happening in our StimulusJS controllers.

When I add a try {} catch (e) {} block on my controller code, it works fine and I can report the exception with Rollbar.error(). But we have no event sent on uncatched JS exceptions, unlike exceptions happening from our vanilla JS code.

Rollbar provides a guide for Node.JS, AngularJS and React:

I have 2 questions:

  1. (specific to Rollbar) Why are these exceptions not sent to Rollbar, unlike the ones from our vanilla JS code?
  2. Is there any way to write custom code on any exception happening from a StimulusJS controller?

I am currently looking at:

handleError(error: Error, message: string, detail: object) {
  console.error(`%s\n\n%o\n\n%o`, message, error, detail)
}

This is definitely where I would like to add a call to Rollbar.error(). How could I override this Application function?

I’m having the same problem with Sentry. It seems like it would make sense to enable adding additional global handlers that can deal with these errors, as well. Should this only be a single handler? An array of handlers? Obviously a single handler introduce less complexity (and if need be that handler could encapsulate multiple handlers). If multiple handlers, should it expose an array or set directly or protect the internal data structure via add/remove methods?

I’m happy to put together a pull request for this. At the moment I’m leaning towards just a single handler as I don’t see this being a dynamic property - I imagine it’ll just be set once globally and left alone after that.

Feel free to weigh in, I’d love to hear anyone’s thoughts!

@mhulet - In the interim, I’ve just overridden the application’s error handler and added in Sentry’s manual handler, which I imagine could be done with Rollbar, as well. Note, this is taken from a Rails 6 app using their default Stimulus configuration.

// Default bits
const application = Application.start();
const context = require.context("controllers", true, /_controller\.js$/);
application.load(definitionsFromContext(context));

// New bits
const defaultErrorHandler = application.handleError;
const sentryErrorHandler = (err) => {
  defaultErrorHandler(err);
  Sentry.captureException(err);
};

application.handleError = sentryErrorHandler;
2 Likes

Thanks, this works exactly as expected for Rollbar on a Rails 5 app.

This is what I ended up doing for Rollbar.

if (window.hasOwnProperty("Rollbar"))
  const defaultErrorHandler = application.handleError;
  const rollbarErrorHandler = (error, message, detail) => {
    defaultErrorHandler(error, message, detail);
    Rollbar.error(error);
  };

  application.handleError = rollbarErrorHandler;
}
1 Like

Minor change to the second line:

const defaultErrorHandler = application.handleError.bind(application);

The bind is required because Stimulus is attempting to use this in their default handler.

1 Like

@Brandon_Cannaday exactly it is required from Stimulus 2.0.
More reads on this subject also here https://www.betterstimulus.com/error_handling/global-error-handler.html

2 Likes

How do I get application running environment name inside javascript/controllers/application.js? I want to ignore error being reported from development and test environment.

I have added environment name <body data-environment="<%= Rails.env %>"> in application.html.erb file but unable to get that in javascript/controllers/application.js.

import { Application } from "@hotwired/stimulus"
import Rollbar from "rollbar"

// let bodyElm = document.getElementsByTagName("BODY")
// let applicationEnvironment = elm.dataset.environment

let rollbar = new Rollbar({
  accessToken: '345k340j340j3044390j304323md',
  captureUncaught: true,
  captureUnhandledRejections: true,
  environment: applicationEnvironment,
  checkIgnore: function(isUncaught, args, payload) {
    if (applicationEnvironment == 'development') return true
  }
});

const application = Application.start()

const defaultErrorHandler = application.handleError.bind(application);

const rollbarErrorHandler = (error, message, detail) => {
  defaultErrorHandler(error, message, detail);
  Rollbar.error(error);
};

application.handleError = rollbarErrorHandler;

application.debug = false
window.Stimulus   = application

export { application }