Node.js error tracking

To track Node.js application errors we use Raven, which is a Javascript SDK for Sentry.

Error tracking in main application code

Errors that are thrown while executing requests are caught by Raven error handler, which is used as express middleware.

Error tracking in background jobs

Background jobs are outside of request execution scope, so errors that are thrown in background jobs are reported to Sentry explicitly.

Example, showing how errors are caught and reported from Bull background job queues:

import Queue from 'bull'
import Raven from 'raven'

const queue = new Queue('exampleQueue')

queue.on('error', (error) => { Raven.captureException(error) })

queue.process((job) => {
  ...
})

Operational errors and application errors

There are two types of errors: operational errors and application (programmer) errors.

Operational errors:

  • Appear in a correctly-written application.
  • Are a part of normal application flow.
  • Are handled by the application without terminating the execution flow.
  • Are not reported to error tracker.
  • Information about the error is reported back to the user (e.g. user should know when the user enters invalid email).

Application errors:

  • Appear when the application has bugs.
  • Should happen as rarely as possible.
  • Are the result of code execution failure.
  • Are reported to error tracker.
  • User does not need to see error information except for generic error message (e.g. “Something went wrong”).

Validation errors are a type of operational errors and are thrown by the application after user input validation checks. They are standard javascript Error object with additional validationError property. This way we differentiate validation errors from system errors.

In request handler (could be a controller, Relay mutation, interactor, etc.) we catch all errors and check if the error has validationError property. If it does, we handle it as needed, otherwise re-throw error for Raven to catch it.

Example code, showing how validation errors are handled:

  // Error builder
  const validationError = message => (
    Object.assign(new Error(message), { validationError: true })
  )

  // Handle errors in express request handler
  try {
    if (emailExists(email)) {
      throw validationError('Email is already in use!')
    }
  } catch (error) {
    if (error.validationError) {
      return res.status(400).json({ errors: error.message })
    }

    throw error
  }

  // Handle errors in express graphql middleware
  router.all(
    '/graphql',
    graphqlHTTP({
      schema,
      formatError: (error) => {
        if (!error.validationError) {
          Raven.captureException(error)
        }

        return error
      }
    })
  )