Tracking errors in Sentry (in node.js)
Gerg艖 M贸ricz
Posted on March 27, 2018
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.
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.
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
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
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:
- Wrapping code into a context, or
- 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
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
});
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
}
});
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.");
}
});
}
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!
});
}
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
});
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();
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
});
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.
Posted on March 27, 2018
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
October 8, 2021