Are you confident that your apps have no bugs? Do you not need a support team because no user ever complains about something not working? Then this post is not for you!
We use a lot of tools at simplabs to ensure reasonably high quality code, but occasionally something slips through. The important thing then is to notice and fix it quickly. This post will focus on the "notice" part by using Sentry error reporting in Ember.js apps.
The ember-cli-sentry
addon has been around since 2015 and
nicely integrates the raven-js
library with Ember.js. What is
raven-js
? It is the official browser JavaScript client for Sentry.
Well... it was the official browser JavaScript client for Sentry. In 2018 it
has been replaced by @sentry/browser
with an entirely new
API.
What does that mean for ember-cli-sentry
? We don't know! We have been
experimenting with @sentry/browser
though and we are starting to think that a
dedicated Ember.js addon for Sentry might not be needed anymore. In the rest of
this post we will show you how we integrated @sentry/browser
into one of our
apps and how to migrate from ember-cli-sentry
to @sentry/browser
.
TypeError: Cannot read property 'name' of undefined
at Object.ie [as get](/assets/vendor-394212fdd48a8a8e9508401b5be54d75.js:1347:16)
at ? (/assets/vendor-394212fdd48a8a8e9508401b5be54d75.js:9923:46)
at e(/assets/vendor-394212fdd48a8a8e9508401b5be54d75.js:9947:46)
at n.filter(/assets/vendor-394212fdd48a8a8e9508401b5be54d75.js:9827:26)
at n._performFilter(/assets/vendor-394212fdd48a8a8e9508401b5be54d75.js:9834:196)
at n.search(/assets/vendor-394212fdd48a8a8e9508401b5be54d75.js:9793:162)
Sourcemaps allow you to map from compiled and minified code to the original code you wrote in your editor. That makes it a lot easier to decipher stack traces like the one above because it will show you the actual file and function names.
By default in Ember.js, sourcemaps are disabled for production builds. In this
case we would like to have them though, so that Sentry has access to them and
can decipher the cryptic stack traces for us. We can enable sourcemap generation
for all environments in the ember-cli-build.js
file:
let app = new EmberApp(defaults, {
sourcemaps: {
enabled: true,
},
});
If you now run ember build --prod
it should generate .map
files next to the
JavaScript files in the dist/assets/
folder. Make sure to upload those files
to your server too, so that Sentry can access them.
In case you don't want other people to be able to read your original source code
you can also upload the sourcemaps directly to Sentry. If you use
ember-cli-deploy
to publish your apps then you can use the
ember-cli-deploy-sentry
addon to do this
automatically for each deployment.
@sentry/browser
ember-cli-sentry
uses an instance-initializer to automatically configure and
initialize the raven-js
library. Those initializers are great for certain
cases, but it could happen that a bug in a different initializer is triggered
before the Sentry client was setup to listen for errors. For this reason we will
not use an initializer to setup @sentry/browser
.
Instead, we will adjust our app/app.js
file to initialize @sentry/browser
right before we start the app:
import { startSentry } from './sentry';
startSentry();
const App = Application.extend({
// ...
});
From the above snippet you can see that we chose to put the Sentry-specific
logic into a separate file: app/sentry.js
. That file looks roughly like this:
import * as Sentry from '@sentry/browser';
import { Ember } from '@sentry/integrations/esm/ember';
import config from './config/environment';
export function startSentry() {
Sentry.init({
...config.sentry,
integrations: [new Ember()],
});
}
First we import the @sentry/browser
library into the file using a
wildcard import
and we import their official Ember.js plugin from the @sentry/integrations
package:
import * as Sentry from '@sentry/browser';
import { Ember } from '@sentry/integrations/esm/ember';
We are using @sentry/integrations/esm/ember
here instead of just
@sentry/integrations
because we want to make sure that we only bundle the
Ember.js plugin, but not e.g. the Vue plugin too.
At this point you might be wondering: but, we never installed those libraries?!
And you are totally right! At this point we need to install the libraries using
your favorite JavaScript package manager. In this case we'll use yarn
:
yarn add @sentry/browser @sentry/integrations
To make those available in the app we'll also need ember-auto-import
, which
makes importing from node_modules
much easier:
yarn add --dev ember-auto-import
Now we have everything we need installed and can go on with the snippet above.
You can see that the Sentry.init()
call uses the sentry
object from your
application configuration, so let's add that to the config/environment.js
file:
module.exports = function(environment) {
let ENV = {
sentry: {
environment,
}
};
if (environment === 'production') {
ENV.sentry.dsn = 'https://<key>@sentry.io/<project>';
}
return ENV;
}
... and we're done! @sentry/browser
is now sufficiently set up and will report
any errors on the page to the server configured in the dsn
property above.
Some errors we just don't care about. One example of that is the
TransitionAborted
error that the Ember.js router reports when a route
transition was cancelled. We can ignore such errors by implementing the
beforeSend()
hook of @sentry/browser
:
Sentry.init({
// ...
beforeSend(event, hint) {
let error = hint.originalException;
// ignore aborted route transitions from the Ember.js router
if (error && error.name === 'TransitionAborted') {
return null;
}
return event;
},
});
With ember-cli-sentry
you could use the raven
service to manually report
messages or exceptions to Sentry. With @sentry/browser
you can do the same:
import * as Sentry from '@sentry/browser';
Sentry.captureMessage('Something is broken! 😱');
Sentry.captureException(new Error('with stack trace!! ✨'));
Sentry, by default, records the IP of the user that has experienced the error.
But when your app has user accounts and an authentication system, it is much
more useful to know which specific user this has happened to. @sentry/browser
allows us to add additional context to the events it sends to the server.
We could do that manually in the beforeSend()
hook, but it is much easier to
use the
configureScope()
function for this:
Sentry.configureScope(scope => {
scope.setUser({
id: 42,
email: "john.doe@example.com"
});
});
This will automatically add the user information for all errors/events that
follow after this call. If you only want to add such information for a single
event you can use the
withScope()
function instead.
We could mock the raven
service of ember-cli-sentry
in tests like this:
this.owner.register('service:raven', Service.extend({
captureException(error) {
// ...
}
}));
But with @sentry/browser
this becomes a little more complicated since there is
no service anymore that could be mocked like that. We have experimented with
this and found that pushing an additional scope on the stack and configuring a
mock client for that scope seems to result in a useful way to assert whether an
exception was correctly reported.
The details of this require its own blog post though, since they are a little less straight-forward than what we described above.
If you have questions, want to know more or need help setting all of this up for your apps please contact us. We're happy to help!
Continue Reading