Hello! We’ve got some exciting improvements to our Node.js client (raven-node) to share, and we’re seeking feedback.
In short, we’ve added proper asynchronous context tracking, fixed up callback timing, and brought over some options from our browser JavaScript client (Raven.js).
The API has changed a bit, mostly to bring it in line with Raven.js, but also to support the new context tracking functionality and make common use cases easier. We’ve largely maintained backwards compatibility, but there are new recommended usage patterns. If you’ve used our browser JS client, this should feel familiar.
This post isn’t meant to be a complete documentation of all changes, but it’ll give you an overview and run you through how to update your usage of Raven-node to take advantage of the nicer API and new capabilities.
We’re calling this the “1.0.0 Beta”, so give it a try and use this thread to let us know what you think or if you find anything strange. We’re looking to incorporate feedback, polish rough edges, and release a true “1.0.0” in the next week or two.
Installation
npm install raven@beta
Usage
First, setting up looks a little different:
var Raven = require('raven');
// Configure dsn and install global handler for uncaught exceptions
Raven.config('___DSN___').install();
You can still use captureException
to manually report errors:
try {
throw new Error
} catch (e) {
// You can get eventId either as the synchronous return value, or via the callback
var eventId = Raven.captureException(e, function (sendErr, eventId) {
// This callback fires once the report has been sent to Sentry
if (sendErr) {
console.error('Failed to send captured exception to Sentry');
} else {
console.log('Captured exception and send to Sentry successfully');
}
});
}
Note that the callback passed to captureException
now fires after transmission of the event; previously, it fired once the stack trace and surrounding source were parsed, but before transmission.
The new recommended Raven usage pattern, though, is to run your entire program inside a Raven context:
var Raven = require('raven');
Raven.config('___DSN___').install();
Raven.context(function () {
// all your stuff goes here
});
Raven will automatically catch and report any unhandled errors originating inside this function (or anything it calls, etc), so you don’t have to manually captureException
all over. See also context data.
If you’re using Express, Raven’s middleware functions do this automatically; see below.
With Express
Here’s what things look like in the Express case:
var Raven = require('raven');
var express = require('express');
Raven.config('___DSN___').install();
var app = express();
// Use Raven.requestHandler() as your *first* middleware:
app.use(Raven.requestHandler());
// Your other middleware, routes, handlers, etc go here in between
// Then use Raven.errorHandler() as your *first* error middleware:
app.use(Raven.errorHandler());
// Then use your own error middleware afterwards to, say, serve an error page
app.use(function (err, req, res, next) {
// Sentry eventId is available here as res.sentry if it was an error we captured
});
Note that anywhere in between our handlers will be wrapped in a context, so you can use our context methods.
With Promises
Now Raven.install()
can automatically set up an unhandledRejection
handler for you:
Raven.config('___DSN___').install({ unhandledRejection: true });
This will have Raven catch and report any unhandled Promise rejections in addition to any uncaught exceptions.
Context Data
Raven-node now tracks asynchronous contexts using domains.
Previously we used domains only to catch errors, but now we also use them to associate state with different “contexts”.
A context most commonly corresponds to a request; if you’re using our Express middleware, each request is automatically wrapped in its own context, and from inside any of your middleware or handlers you can use Raven’s context methods. A context might also correspond to a connection lifecycle or a job being processed on a worker process.
Raven.setContext({
user: {
username: 'lewis'
}
});
Raven.mergeContext({
tags: {
component: 'api'
}
});
console.log(Raven.getContext())
// { user: ..., tags: ... }
Alternatively, you can run a function in a context with Raven.context
. Here we can see the separation of data associated with different contexts:
Raven.context(function () {
// prints
Raven.setContext({ user: { username: 'lewis' } });
setTimeout(function () {
// prints lewis
console.log(Raven.getContext().user.username);
// lewis logged out
Raven.setContext({ user: null });
}, 500);
});
Raven.context(function () {
// does not clobber state of above context
Raven.setContext({ user: { username: 'ben' } });
setTimeout(function () {
// was not clobbered by later setContext above; prints ben
console.log(Raven.getContext().user.username);
}, 1000);
})
This context separation now enables Raven to correctly associate user data, tags, extra data, etc with whatever tangible processing task resulted in the error, be it a request, worker job, or otherwise.
Other New Stuff
We’ve added a shouldSendCallback
config field to complement the existing dataCallback
.
Raven.setShouldSendCallback(function (data) {
// Randomly omit half of our reports
return Math.random() > 0.5;
});
You can also now wrap an (err, result)
style callback with Raven.intercept
to automatically capture any first-argument-errors. For example, instead of:
database.query('SELECT ...', function (err, result) {
if (err) return Raven.captureException(err);
doSomethingWith(result);
}));
you can do:
database.query('SELECT ...', Raven.intercept(function (err, result) {
// Here, err will never be an Error object; if it was,
// we caught it and reported it instead, so you can skip "if (err) ..." patterns
doSomethingWith(result);
}));
Summary of changes
-
Raven.config(...)
instead ofnew raven.Client(...)
-
Raven.install()
instead ofclient.patchGlobal()
- The callback to
Raven.captureException
now fires after transmission - Promises
unhandledRejection
option toinstall()
- Context methods -
setContext
,mergeContext
,getContext
shouldSendCallback
intercept()
- Backwards compatibility was mostly maintained, but lots of stuff was deprecated
- We’ll print console messages if you’re doing anything the old way
- We’ll also print console messages in certain situations where behavior might be surprising, like if no DSN is configured
- You can disable these alerts with
Raven.disableConsoleAlerts();