Ionic and Rollbar, the Perfect Pair

Chris Hand
5 min readJul 1, 2018

--

Tell you what. There’s almost nothing more depressing than looking at your production error logging seeing this:

You think, oh… ok, so we’re not catching errors very well. Let’s pop in to that error and see what’s going on. So you dig in a little big to find:

Yes. Well. Thank you.

No line numbers, no variable names, no file names. Why? Because of minification and uglification. In ionic, if you do a production build, all your code will be bundled up in a nice small package so it’s as efficient as possible. This is great, unless you want to catch and handle errors well.

In this article we’ll be talking specifically about handling uncaught errors. Errors you expect are easy. Ones you don’t can be hard.

This article assumes you’re already generating source maps for your project, you just don’t know how to make them useful. In an ionic project, you’ll find your sourcemaps at the root of your project under a (sometimes hidden) folder .sourcemaps.

Here’s what we’ll need to do:

  1. Install the Rollbar JS plugin
  2. Initialize it as our default error handler
  3. Map our source files
  4. Upload our source maps to rollbar

So let’s get at it.

Step 1: Install the Rollbar JS Plugin

Rollbar has a very nice guide for integrating itself into your project. I recommend you follow it: https://docs.rollbar.com/docs/angular

In short:

npm install rollbar --save

Ok done.

Step 2: Initialize it as our default error handler

Now, add Rollbar to your Angular Module. In a production app you would almost certainly break this up into multiple files, but for brevity I’ll post it all here:

const rollbarConfig = {
accessToken: 'POST_CLIENT_ITEM_ACCESS_TOKEN',
captureUncaught: true,
captureUnhandledRejections: true,
};

export const RollbarService = new InjectionToken<Rollbar>('rollbar');

@Injectable()
export class RollbarErrorHandler implements ErrorHandler {
constructor(@Inject(RollbarService) private rollbar: Rollbar) {}

handleError(err:any) : void {
this.rollbar.error(err.originalError || err);
}
}

export function rollbarFactory() {
return new Rollbar(rollbarConfig);
}

@NgModule({
imports: [ BrowserModule ],
declarations: [ AppComponent ],
bootstrap: [ AppComponent ],
providers: [
{ provide: ErrorHandler, useClass: RollbarErrorHandler },
{ provide: RollbarService, useFactory: rollbarFactory }
]
})
export class AppModule { }

There are a few things we’re doing here:

  • We’re setting up our Rollbar config. This is important, we’ll do more to it later. At the least, you’ll need your access token.
  • We’re defining a RollbarErrorHandler class as our default Angular error handler class, and delegating those errors to rollbar. Now when an uncaught error occurs, it’ll get logged to rollbar.
  • We’re defining a RollbarService class we could inject into our components to manually log to rollbar. We won’t do much with that here.

Ok, we’re set up. On to the next step.

Step 3: Map our source files

Keep in mind, that we’re using the Rollbar JS sdk here. Usually this is used on a web site. If it was used on a website, your minified JS file would have a URL on the server.

https://my-awesome-site/js/main-js-file.js

When Rollbar get’s an uncaught error, this file will be the source of the error. Rollbar needs to map up the source file with the map file you give it to know whether it has the right source file. Normally, in an Ionic app, this file will be something served from the file system, something like:

file:///android_asset/www/js/main.js

No problem, we could map that up to Rollbar and sit pretty. But the whole point of an Ionic app is to be able to deploy to both iOS and Android. And, unfortunately, on iOS, the file location will be a dynamic hash. Luckily, Rollbar provides a configuration function transform specifically for apps hosted on many domains: https://docs.rollbar.com/docs/source-maps/#section-using-source-maps-on-many-domains.

We aren’t hosting on multiple domains, but we can use this feature to change the file path sent to the server so we can easily link it to the right file.

In my case, I don’t care about the correct file path, or even the domain. All I know is that when I get an error from my main.js file, I want it linked to my source map.

So… my configuration looks like this:

const rollbarConfig = {
accessToken: EnvironmentConstants.rollbarToken,
captureUncaught: true,
captureUnhandledRejections: true,
transform: function(payload: any) {
if (payload && payload.body && payload.body.trace && payload.body.trace.frames) {
let frames = payload.body.trace.frames;
for (let i = 0; i < frames.length; i++) {
if (frames[i].filename.indexOf('main.js') > -1) {
payload.body.trace.frames[i].filename = 'file://main.js';
}
}
}
},
payload: {
environment: EnvironmentConstants.target,
client: {
javascript: {
code_version: EnvironmentConstants.version,
source_map_enabled: true,
guess_uncaught_frames: true,
},
},
},
};

Check out the tranform function. In here, I look to see if the payload has frames in it. These are the stack trace Rollbar would send to the server. In my case, if that stack trace has main.js in it at all, I simple change the file name to file://main.js . Now the file name sent to the server will be consistent across iOS and Android.

Alright… last step:

Step 4: Upload our source maps to rollbar

Now… build your project and grab that main.js.map file. That’s what we’ll upload to Rollbar.

In Rollbar, navigate to

https://rollbar.com/{project}/{app}/settings/source_maps/

And click Upload Source Map. You’ll see a dialog and you’ll want to populate it like this:

You’ll give it the source map file, tell it what the minified JS URL is and specify the version. Obviously, different release versions will match up with different versions, so put your appropriate version.

And that’s it!

The next time you get an error in Rollbar you’ll see something like this:

Which is a LOT easier to debug. Cheers!

--

--

Chris Hand
Chris Hand

Written by Chris Hand

Helping teams take ownership of their product and empower themselves to do great things.

Responses (3)