Sending AngularJS Errors To New Relic, Raygun, Sentry, etc.
A few weeks ago, I looked at how to capture global JavaScript errors and pipe them into an AngularJS application. That approach works; but, it requires you to deal with the global error handler which is an unfortunate API. If you are already using a client-side error service like New Relic, Raygun, or Sentry, it might be better to let them do the heavy lifting. Then, instead of piping global errors into AngularJS, we can send AngularJS errors to one of these client-side error handling services.
Run this demo in my JavaScript Demos project on GitHub.
In an AngularJS application, all errors that occur within an AngularJS workflow are sent to the $exceptionHandler service. Now, we could overwrite the definition of the $exceptionHandler service; but, doing so feels a little heavy-handed. Instead, we're simply going to decorate it such that we can intercept errors that get thrown.
In the following demo, this $exceptionHandler proxy doesn't reference our target JavaScript error handler directly. Instead, it just sets up the mechanics of the interception, grabbing the errors and sending them to our local error reporting service. It's then up to the error reporting service to deal with the implementation and integration of the 3rd-party API.
This might seem like an unnecessary level of indirection, but I think it has two benefits:
- It decouples the mechanism of reporting from implementation and choice of service.
- It gives us something that we can inject into other components that may need to explicitly report errors without having to know the details.
That said, let's take a look at the code. We're going to be using the configuration phase, of the AngularJS application, to create our $exceptionHandler proxy. The proxy will then hand off the error to our error reporter, which will then try to hand it off to New Relic:
<!doctype html>
<html ng-app="Demo">
<head>
<meta charset="utf-8" />
<title>
Sending AngularJS Errors To New Relic, Raygun, Sentry, etc.
</title>
</head>
<body ng-controller="AppController">
<h1>
Sending AngularJS Errors To New Relic, Raygun, Sentry, etc.
</h1>
<p>
<a href="#" ng-click="causeError()">Cause an error</a>.
</p>
<!-- Load scripts. -->
<script type="text/javascript" src="../../vendor/angularjs/angular-1.3.16.min.js"></script>
<script type="text/javascript">
// Create an application module for our demo.
angular.module( "Demo", [] );
// --------------------------------------------------------------------------- //
// --------------------------------------------------------------------------- //
// I control the root of the application.
angular.module( "Demo" ).controller(
"AppController",
function( $scope ) {
// I trigger a JavaScript error by referencing an undefined value.
$scope.causeError = function() {
var foo = bar;
};
}
);
// --------------------------------------------------------------------------- //
// --------------------------------------------------------------------------- //
// All of the errors in an AngularJS application get piped into the
// $exceptionHandler service. In order to get them to show up in something like
// the New Relic Browser API, we're going to decorate the $exceptionHandler
// service so that we can intercept the errors and hand them off to our reporter.
// --
// NOTE: I am not overriding the $exceptionHandler service in totality since I
// want this facet of the application to remain as decoupled from the
// implementation as possible.
angular.module( "Demo" ).config(
function addReporting( $provide ) {
// Provide an interceptor for the error workflow.
$provide.decorator(
"$exceptionHandler",
function( $delegate, errors ) {
// Return the new $exceptionHandler implementation which will
// report the error to our internal error handler before passing
// it off to the original $exceptionHandler implementation (which
// may, itself, be some other delegate provided by another part
// of the application).
return(
function exceptionHandlerProxy( error, cause ) {
errors.report( error );
$delegate( error, cause );
}
);
}
);
}
);
// --------------------------------------------------------------------------- //
// --------------------------------------------------------------------------- //
// I report JavaScript errors.
angular.module( "Demo" ).factory(
"errors",
function errorLoggerFactory( $window, $log ) {
// Return the public API.
return({
report: report
});
// I report errors to the remote server.
function report( error ) {
// In this case, we're going to be using New Relic's Browser API to
// report JavaScript errors that originate from within the AngularJS
// application try / catch blocks (which is why the global error
// handler doesn't see them). But, since New Relic is a per-server
// cost, we might not have it enabled in every environment. Let's
// check to see if the API exists before we try to use it.
if ( $window.NREUM && $window.NREUM.noticeError ) {
try {
$window.NREUM.noticeError( error );
// If logging errors is causing an error, just swallow those
// errors; attempting to log these errors might lock the browser
// in an infinite loop.
} catch ( newRelicError ) {
$log.error( newRelicError );
}
} else {
$log.info( "New Relic not available to record error." );
}
}
}
);
</script>
</body>
</html>
I don't actually have New Relic installed locally (since it has a per-server cost); but, when we run this page, we can clearly see that the $log.info() method was invoked in our errors service which is where our New Relic, Raygun, Sentry, etc. integration is implemented:
Once you have this integration in place, you can always swap out the choice of service. But, having all errors going to a single point of record is definitely a good move. This is the only way you can get a truly holistic understanding of what problems your users are experiencing in your single page applications (SPA).
Want to use code from this post? Check out the license.
Reader Comments