DEV Community

Gergő Móricz
Gergő Móricz

Posted on

Tracking errors in Sentry (in node.js)

Introduction

Sentry is an open source error tracking tool, that helps developers monitor and fix crashes in real time. It's really fast to set up, and supports many platforms and programming languages.

On the Web UI of sentry, you can see all errors that your code captured, a stack trace, context and breadcrumbs (if you implement it).

Setting up your Sentry project

After you've registered on Sentry and logged in, press New Project in the top-right corner.

alt-image

In this tutorial, we're covering the generic Node.js usage of Sentry, so make sure to choose Node.js instead of Express.

After giving your project a name and clicking on the shiny Create button, a documention page should pop up.

alt-image

Below the "Configuring the Client" headline, there should be a codeblock containing your Sentry DSN. Save that code somewhere, because we'll need it later, so we can communicate with Sentry via our client module, Raven.

Setting up Raven

After creating your project on the Sentry website, it's time to spin up your editor and start coding.

First, you'll need to add the Raven module to your project.

npm install raven --save
Enter fullscreen mode Exit fullscreen mode

Open the code you've saved earlier from the Sentry website, and paste it along the other require statements in your file. To make the code ready for public Git providers, don't forget to remove the hardcoded Sentry DSN, and move it to an external gitignored config file, or to environment variables.

Capturing errors

Let's present the different methods of capturing errors with a sample code.

function doSomething(data) {
  if (data) { // Check if our data is defined or not
    return data; // Return data
  } else {
    throw new Error("Missing required data parameter."); // Throw error
  }
}

doSomething(); // Call doSomething without defining data -> error thrown
Enter fullscreen mode Exit fullscreen mode

Our function doSomething will check if it's parameter data is defined: if it is, it just returns it, but if it isn't, it throws an error. Just after defining doSomething, our code calls it, without supplying the data parameter, therefore throwing an error.

There are two main methods for capturing issues with the Raven client:

  1. Wrapping code into a context, or
  2. Manually capturing errors.

Method 1 - Wrapping code into a context

There are two methods of wrapping code into a context: Raven.context, and Raven.wrap. There aren't many differences, except that Raven.wrap returns a function, while Raven.context doesn't, meaning that you'd want to use Raven.wrap for code like callbacks, and Raven.context for just generic code wrapping.

var Raven = require('raven');

Raven.config('https://13c3c4f3c6094d749436722b3031f787:6fe68b76c7e34d2cb69831355d6e3beb@sentry.io/301629').install();

function doSomething(data) {
  if (data) { // Check if our data is defined or not
    return data; // Return data
  } else {
    throw new Error("Missing required data parameter."); // Throw error
  }
}

Raven.context(function() { // Wrap the following code with Raven.context
  doSomething(); // Call doSomething without defining data -> error thrown
});

setTimeout(Raven.wrap(function() {
  doSomething(); // Call doSomething without defining data -> error thrown
}, 1000); // Run doSomething delayed with setTimeout and Raven.wrap
Enter fullscreen mode Exit fullscreen mode

There's a major advantage for using this method instead of manually capturing errors: code inside a context (a.k.a. code inside Raven.wrap or Raven.context) has access to methods that enable associating data with the context, which can be useful for debugging.

var Raven = require('raven');

Raven.config('https://13c3c4f3c6094d749436722b3031f787:6fe68b76c7e34d2cb69831355d6e3beb@sentry.io/301629').install();

function doSomething(data) {
  if (data) { // Check if our data is defined or not
    return data; // Return data
  } else {
    throw new Error("Missing required data parameter."); // Throw error
  }
}

Raven.context(function() { // Wrap the following code with Raven.context
  Raven.setContext({ // Set context data with the specified object
    tags: {
      component: 'main'
    }
  });
  Raven.mergeContext({ // Extend context data with the specified object
    tags: {
      method: 'doSomething'
    }
  });
  console.log(Raven.getContext());

  doSomething(); // Call doSomething without defining data -> error thrown
});
Enter fullscreen mode Exit fullscreen mode

Keep in mind that setContext, mergeContext and getContext only work in code inside a context.

You can store anything inside your context data, for example, you could associate errors with user data, like this:

Raven.setContext({
  user: {
    email: 'matt@example.com',
    id: 123
  }
});
Enter fullscreen mode Exit fullscreen mode

Method 2 - Manually capturing errors

With Raven's caputreException, you can capture non-thrown exceptions, or you can capture thrown exceptions using a try-catch block.

var Raven = require('raven');

Raven.config('https://13c3c4f3c6094d749436722b3031f787:6fe68b76c7e34d2cb69831355d6e3beb@sentry.io/301629').install();

function doSomething(data) {
  if (data) { // Check if our data is defined or not
    return data; // Return data
  } else {
    throw new Error("Missing required data parameter."); // Throw error
  }
}

try {
  doSomething(); // Call doSomething without defining data -> error thrown
} catch (e) {
  Raven.captureException(e, function(sendErr) { // Capture exception
    if (sendErr) { // Check if send failed
      console.error("Failed to send exception to Sentry.");
    } else {
      console.log("Exception sent to Sentry.");
    }
  });
}
Enter fullscreen mode Exit fullscreen mode

You can use captureMessage to send non-exception error messages to Sentry.

var Raven = require('raven');

Raven.config('https://13c3c4f3c6094d749436722b3031f787:6fe68b76c7e34d2cb69831355d6e3beb@sentry.io/301629').install();

function doSomething(data) {
  if (data) { // Check if our data is defined or not
    return; // Return nothing
  } else {
    return "Missing required data parameter."; // Return error
  }
}

var err = doSomething(); // Call doSomething without defining data -> error returned
if (err) {
  Raven.captureMessage(err, function(err) {
    // Sent!
  });
}
Enter fullscreen mode Exit fullscreen mode

Although you do not have access to setContext and other context-only functions, you can supply additional data to captureException and captureMessage that should be captured.

Raven.captureException(err, {
  user: { id: 123 }, // User-related info
  req: req, // Request object from HTTP web server (handled by Raven Express)
  tags: { component: 'main', method: 'doSomething' }, // Tags
  extra: { catType: cat.type }, // Any other data you'd specify with setContext
  level: 'error' // Event level
});
Enter fullscreen mode Exit fullscreen mode

For more additional information you can provide, visit the Raven Node docs.

Breadcrumbs, breadcrumbs everywhere!

For additional info on data before the exception happened, Breadcrumbs are the solution. Breadcrumbs are a trail of events that occurred in your application leading up to a captured error. They can be as simple as a log message, or they can contain rich metadata about the state of your appliction, like network requests, database queries, or even earlier occurring errors.

Raven for Node supports a feature called autoBreadcrumbs, which automatically records useful breadcrumbs, like HTTP(S) requests, log statements (with console.log, .warn, etc.), and PostgreSQL queries via the pg module.

To enable it, add this setting to Raven.config, and you're good to go!

Raven.config('https://13c3c4f3c6094d749436722b3031f787:6fe68b76c7e34d2cb69831355d6e3beb@sentry.io/301629', {
  autoBreadcrumbs: true // Enable autoBreadcrumbs
}).install();
Enter fullscreen mode Exit fullscreen mode

This may be just enough for you, but if it isn't, fear not! There's an easy way of capturing breadcrumbs manually.

var Raven = require('raven');

Raven.config('https://13c3c4f3c6094d749436722b3031f787:6fe68b76c7e34d2cb69831355d6e3beb@sentry.io/301629').install();

function doSomething(data) {
  if (data) { // Check if our data is defined or not
    return data; // Return data
  } else {
    throw new Error("Missing required data parameter."); // Throw error
  }
}

var ourJson = "{}"; // Blank JSON object, not containing needed data.

Raven.context(function() { // Wrap the following code with Raven.context
  var parsedJson = JSON.parse(ourJson); // Parse JSON
  Raven.captureBreadcrumb({ // Capture breadcrumb
    message: 'Parsed JSON',
    category: 'log',
    data: {
      raw: ourJson,
      parsedJson: parsedJson
    }
  });
  doSomething(parsedJson.data); // Call doSomething with non-existing data -> error thrown
});
Enter fullscreen mode Exit fullscreen mode

If we view the error on the Web UI, we can see our breadcrumb containing our raw JSON and our parsed JSON, this way we can see if the JSON doesn't have the needed data (which is the case here), or if we parsed the JSON wrong.

This can be really useful for debugging info that is coming from an outside source, so you can see if your code is wrong, or if the users' input is wrong.

Top comments (0)