Front-end errors reporting at OuiCar

Front-end errors reporting at OuiCar

By Xavier Tholot

At Ouicar, we don’t like bugs.

As we explained through our previous posts, we use many tools like Flow or our Story Book, to help us developing a high-quality product and decrease as much as possible the risks of crashes.

However, even though we test our software as much as we can and we try to explore all the cases we imagine, we have been facing many bugs on the client side.

What kind of bugs did we encounter

Most of the bugs we encountered were un-tested cases, that we had not anticipated. For example, a google map library used when the internet connection was shut down, or a null pointer exception when someone indicates he lives at an address that doesn’t have a street number…

But some of them were also due to the client’s software. There are so many types of devices, using possibly a different version of a different browser, that it makes it impossible for a developer to test all the cases that could happen in the reality. For example, did you know that window.requestAnimationFrame wasn’t working on Safari < 5 and Android < 4.4.2 ? We thought it wasn’t worth it to take this error into consideration but this made almost 2.000 of our users crash their application… And until now, we had no way to measure it.

Sentry to the rescue

It became obvious that we were missing visibility on our clients errors. It became a critical need for us to have a tool that could report any error happening on our front-end.

Sentry is one of the most popular bug reporter, and offers many interesting features such as SourceMaps handling, or Github, Slack and Email integrations. That is the tool we have chosen to use at Ouicar. In this article we will explain how we installed it, and how we use it to improve our software quality.

Basic setup

To begin with Sentry, we did a very basic installation as it is documented here.

Only two lines of code are needed to start reporting your errors:

Import the Sentry library (raven.js):
<script src="https://cdn.ravenjs.com/3.6.0/raven.min.js"></script>
Initialize Sentry with your API key:
Raven.config('your public dsn').install()

This will override the window.onerror function to report all the non-caught errors happening on your website to Sentry.

Here is how an error looks like on Sentry once it’s received:

Sentry error

Production setup

However, when we pushed this in production, we were a bit disappointed about the way errors were reported. As our production Javascript is minified, the errors we received were unreadable. We had to go further in the configuration to make our reports look good.

Hopefully, Sentry offers a great SourceMaps management feature. You just have to upload your SourceMaps to Sentry every time you release your application, and Sentry will be able to figure out where your error happened in your initial code.

Here is how our error reporting looks like when we upload our SourceMaps to Sentry:

Sentry sourcemaps example

This feature also introduces a versioning of the application: every release is registered by Sentry, and we are able to see if new errors appear in a specific release.

Sentry releases list

Going deeper into errors reporting

Although most of our Javascript errors are handled by Sentry with this configuration, we have been interested in tracking more errors that were specific to our architecture. Errors that can’t be caught by Sentry can be sent manually using Raven.captureException().

For information, here are the technologies we use at Ouicar:

  • React 0.14
  • React-Redux 4.0
  • Flow 0.30
  • Redux-form 4.0
  • Q / ES6 Promises

Catching promises errors:

As explained in the Sentry documentation,

By default, Raven.js does not capture unhandled promise rejections.

Either your promise library has an error handler that you can use to catch your rejected promises, or you will have to do like us and manually catch them. The function window.onunhandledrejection is called by default by ES6 Promises and Q library when a promise is rejected. If you want to log errors to Sentry for rejected promises, here is how to do:

window.onunhandledrejection = function(evt) {
    Raven.captureException(evt.reason, {level: 'warning'});
};

We mostly use Promises for APIs, so if we have a promise rejection, it most likely means that we have an API error. We put a warning label on those because there is not necessarily a Javascript error inside our code, but a promise rejection which is slightly different.

Logging errors from Redux

Thanks to Middlewares, it’s easy to intercept errors in Redux. Some plugins exist on Github to integrate Sentry with Redux, but we have decided to configure it ourselves.

Redux official documentation explains how to create your own Middleware to catch reducers errors. Here is the code of our Sentry Middleware:

// @flow
export default (store: {getState: Function}) => (next: Function) => (action: mixed): mixed => {
	try {
		return next(action);
	} catch (error) {
		console.error('Caught an exception!', error);
		const payload = {
			extra: {
				action: typeof action === 'function' ? action.toString() : action,
				state: store.getState()
			}
		};
		if (typeof Raven !== 'undefined' && typeof Raven.captureException === 'function') {
			Raven.captureException(error, payload);
		}
		throw error;
	}
};

This way, we catch errors happening in the reducers and we also provide some additional information by passing the state of the application at the moment of the error and the action inducing the error. Thus, it becomes easy to reproduce an error by importing the state to Redux DevTools and dispatch the same action manually.

Detecting inconsistencies between Flow types and runtime variables

Flow is a static type checker for Javascript, which means it doesn’t check types at runtime but analyzes the code and alerts the developer if there is a type inconsistency.

At Ouicar, we use flow everywhere we can and we like the idea of having predictable object types in our Javascript. But sometimes, there can be an inconsistency between the types we have defined and the reality, and The best example that illustrates this is the Shape of the JSON DTO (Data Transfer Object) returned by our API.

We have defined all our DTO types in Flow to be able to predict the shape it will have when we code. However, Flow cannot check statically that the types we defined are correct. This has to be done at runtime, and that’s why we introduced the Decode library that is detailed in this article by Guillaume Claret.

We found interesting that every time a client has a decode alert, we receive an alert on Sentry, so we can adjust the Flow types accordingly.

The principle is very simple: if a decode fails, the decode function will throw an error (which is formatted to be as readable as possible), and we send it to Sentry.

export function runWithLogs<A>(value: mixed, decode: t<A>): A {
	try {
		decode(value);
	} catch (error) {
		Log.logToSentry(error);
	}
	return (value: any);
}

Here is how it looks like on Sentry:

Sentry releases list

We can see here the prospect_connect field is missing on our DTO, but we defined it as mandatory in our Flow type.

Catching submit errors on the Forms

We also found interesting to track the forms errors. It will enable us to understand where our users are doing mistakes filling it, and help us improving the UX of these forms.

We didn’t want to send those logs to Sentry for operational reasons, as we wanted this tool to stay purely technical. These form logs are more product-oriented, so we chose to send them to another logging tool, Logmatic, which works just the same way as Sentry.

In order to catch our redux-form errors, we wrote our own redux-form middleware:

import * as UtilLog from 'ouicar/util/log';
export default function reduxFormMiddleware({ getState, dispatch }) {
	return (next) => (action) => {
		const returnValue = next(action);
		if (action.type === 'redux-form/SUBMIT_FAILED') {
			UtilLog.logToLogmatic('Redux-form error', {
				formName: action.form,
				state: getState().form[action.form]
			});
		}
		return returnValue;
	};
}

This will catch all the failed submits of our redux forms and send a log to Logmatic that will detail the name of the form that failed, and its state.

How do we fix our errors ?

Now that we have those tools perfectly installed and that the logs are popping into our dashboard, we have to read them, and fix them if needed.

The bug forces the software to adapt, evolve into something new because of it. Work around it or work through it. No matter what, it changes. It becomes something new. The next version. The inevitable upgrade. - Mr. Robot

Our strategy regarding bugs fixing with Sentry is very simple: we are alerted of incoming new errors through Slack thanks to Sentry’s Slack integration, and if the error seems critical or important, we create a ticket to solve it.

There also is a great Github integration with Sentry that enables to create an issue directly from Sentry’s dashboard but we don’t use it, as we don’t use Github issues anymore to manage our project. Although, if you are familiar with Github Issues you should definitely check this!

Closing thoughts

Since we have set up our front-end logging tools, things have radically changed:

  • We have fixed bugs that we were not aware of, that had been here from a very long time.
  • Regressions are now automatically detected when a new release is put in production, and we don’t have to wait for users feedbacks to be aware about it.
  • We detect errors sooner, we fix them earlier, and less users are impacted by bugs than before.
  • Thanks to our warning logs, we have improved the quality of our development desk. Our Flow types are updated as soon as an API changes, which is very important to us and reduces risks of null pointers or bad parsing when using the API.

At the end of the day, what we have gained is visibility. Previously we could not see what happened on our clients. Now we have full details about errors, inconsistencies in our code, and UX problems.

We are very interested about how other tech teams use logging tools such as Sentry. If you have other ideas to share with us, new interesting data to log or other techniques, please let us know so that we can complete this article, and maybe our codebase ;-)

OuiCar's Picture

About OuiCar

OuiCar is a community market place to find and rent cars anywhere in France. But we are not just car renters. We also like to experiment new technologies and share ideas with others.

Paris http://www.ouicar.fr

Comments