Node.js Client 1.0.0-Beta Release

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 of new raven.Client(...)
  • Raven.install() instead of client.patchGlobal()
  • The callback to Raven.captureException now fires after transmission
  • Promises unhandledRejection option to install()
  • 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();
2 Likes