If a tree raises an exception in the forest and there is no one to catch it, do you have a problem?
One of the best tools I have discovered this year is Sentry error reporting. It is available for multiple platforms, but I am using it mostly to catch and report client-side JavaScript exceptions.
The Sentry client library is called Raven and it has two features:
1: one can use the Raven to send an error with additional information to Sentry webservice
1 | try { |
2: Raven can install global exception catcher for most modern browsers to intercept uncaught errors.
1 | <script src="//cdn.ravenjs.com/1.1.2/jquery,native/raven.min.js"></script> |
Sentry allowed us to discover several errors we would have never found using more conventional unit or end 2 end testing, and these errors would never be reported by users.
In this post I will describe how to integrate Sentry error reporting with Angular code to catch all possible errors in addition to the global error handler installed by Raven.
Report errors that happen inside Angular code
Angular has a global $exceptionHandler
factory that catches errors
that happen inside controllers, services, etc. The default implementation just
prints the error to the console and rethrows it. You can create a utility module
to be reused in your apps that forwards the error to Sentry
1 | angular.module('ErrorCatcher', []) |
Make your top level app depend on ErrorCatcher
and any exceptions in angular
code will be sent to Sentry.
Note the second argument 'cause'. Usually it is undefined, but sometimes it might have a value usually if the exception happens while parsing text. For example the link function sends the offending element's starting tag to the exception handler as the second argument
1 | function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) { |
You can test errors thrown inside the Angular digest loop by executing from the browser console
1 | angular.element(document.body).injector().get('$rootScope') |
Instead of document.body
you can subsitute the selected element using angular.element($0)
.
You can also store this code snippet together with other code snippets.
Report AJAX errors
Error responses from the server can be intercepted and reported to Sentry
by installing $http
response interceptor. Add your own interceptor factory
to the ErrorCatcher
module defined above:
1 | angular.module('ErrorCatcher', []) |
Now you will know about error server responses, which to your users look like errors.
Do not report cancelled requests
If the user browses to a different page while the AJAX request is pending, the request
gets cancelled and trips the error handler. To avoid reporting the cancelled requests
as errors, check the rejection.status
property. If it is undefined, the request was cancelled
and should not be reported.
Correctly throwing an error
Two tips on throwing an error:
1: Always throw an instance of Error class, never throw a string or an object. Getting stack trace is only possible via Error object, for example.
1 | throw new Error('broken') // good |
2: Throwing an error stops code execution. If the error is not serious enough, throw it asynchronously
1 | if (!some_condition) { |
You could even create an utility function for this purpose
1 | function asyncThrow(err) { |
I prefer to use defer function available
in Lodash and similar libraries. It runs the supplied function by scheduling
it onto even loop (just like setTimeout
). Using defer
makes the async
intent more clear that using setTimeout
1 | if (!some_condition) { |
Make sure to use a descriptive message, since the error's stack will probably be incorrect due to async throw.
Bonus 1
I wrote useful-express-partials which includes a Jade template for setting up Raygun error reporting.
- Know unknown unknowns with Sentry
- Exception Handling in an AngularJS Web Application - An excellent blog post on handling errors in Angular and sending info to Sentry