最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Sentry.io Managing error scopescontexts on asynchronous Node.js Server - Stack Overflow

programmeradmin1浏览0评论

Basically you have concurrent requests in Node.js. And you probably want to enrich possible errors with data specific to each request. This request-specific data can be gathered in different parts of the app via

  • Sentry.configureScope(scope => scope.setSomeUsefulData(...))
  • Sentry.addBreadcrumb({ ... })

Later on somewhere in a deep nested asynchronous function call The Error gets thrown. How does Sentry know which of the data previously gathered is actually relevant to this particular error, considering requests are handled simultaneously and at the point where an error happens there's no access to some sentry "scope" to get data relevant to this particular request which resulted in the error.

Or do I have to pass sentry scope through all my function calls? Like

server.on('request', (requestContext) => {
  // Create new Sentry scope
  Sentry.configureScope(sentryScope => {
    Products.getProductById(id, sentryScope); // And pass it on
  });
});

// all the way down until...

function parseNumber(input, sentryScope) {
  // ...
}

Or does sentry use some sort of magic to map specific data to relevant events? Or am I missing something?

Basically you have concurrent requests in Node.js. And you probably want to enrich possible errors with data specific to each request. This request-specific data can be gathered in different parts of the app via

  • Sentry.configureScope(scope => scope.setSomeUsefulData(...))
  • Sentry.addBreadcrumb({ ... })

Later on somewhere in a deep nested asynchronous function call The Error gets thrown. How does Sentry know which of the data previously gathered is actually relevant to this particular error, considering requests are handled simultaneously and at the point where an error happens there's no access to some sentry "scope" to get data relevant to this particular request which resulted in the error.

Or do I have to pass sentry scope through all my function calls? Like

server.on('request', (requestContext) => {
  // Create new Sentry scope
  Sentry.configureScope(sentryScope => {
    Products.getProductById(id, sentryScope); // And pass it on
  });
});

// all the way down until...

function parseNumber(input, sentryScope) {
  // ...
}

Or does sentry use some sort of magic to map specific data to relevant events? Or am I missing something?

Share Improve this question edited Oct 25, 2019 at 4:16 disfated asked Oct 24, 2019 at 22:17 disfateddisfated 11k13 gold badges41 silver badges52 bronze badges
Add a ment  | 

1 Answer 1

Reset to default 10

It appears they use Node.js Domains (https://nodejs/api/domain.html) to create a separate "context" for each request.

From the Sentry documentation, you need to use a requestHandler for this to work correctly.

E.g. for express (https://docs.sentry.io/platforms/node/express/):

const express = require('express');
const app = express();
const Sentry = require('@sentry/node');

Sentry.init({ dsn: 'https://[email protected]/1240240' });

// The request handler must be the first middleware on the app
app.use(Sentry.Handlers.requestHandler());

// All controllers should live here
app.get('/', function rootHandler(req, res) {
  res.end('Hello world!');
});

The handler looks something like this:

(From: @sentry/node/dist/handlers.js)

function requestHandler(options) {
    return function sentryRequestMiddleware(req, res, next) {
        if (options && options.flushTimeout && options.flushTimeout > 0) {
            // tslint:disable-next-line: no-unbound-method
            var _end_1 = res.end;
            res.end = function (chunk, encoding, cb) {
                var _this = this;
                sdk_1.flush(options.flushTimeout)
                    .then(function () {
                    _end_1.call(_this, chunk, encoding, cb);
                })
                    .then(null, function (e) {
                    utils_1.logger.error(e);
                });
            };
        }
        var local = domain.create();
        local.add(req);
        local.add(res);
        local.on('error', next);
        local.run(function () {
            core_1.getCurrentHub().configureScope(function (scope) {
                return scope.addEventProcessor(function (event) { return parseRequest(event, req, options); });
            });
            next();
        });
    };
}
exports.requestHandler = requestHandler;

So, you can see the new Domain being created on new requests.

I found this info mostly by discovering this issue: https://github./getsentry/sentry-javascript/issues/1939

If you wanted to add "context" to other async parts of your Node backend (E.g. if you had workers / threads / something work not initiated from a HTTP request..), there are examples in the above issue that show how that can be done (by manually creating Hubs / Domains..).

If you're interested, keeping some sense of context in Node.js with various async functions / callbacks / setTimeouts can also be done now with async_hooks: https://nodejs/api/async_hooks.html

This is a good intro:

https://itnext.io/request-id-tracing-in-node-js-applications-c517c7dab62d?

And some libraries implementing this:

https://github./Jeff-Lewis/cls-hooked

https://github./vicanso/async-local-storage

For example, something like this should work:

const ALS = require("async-local-storage");

function withScope(fn, requestId) {
  ALS.scope();
  ALS.set("requestId", requestId, false);
  return await fn();
}

async function work() {
  try {
    await part1();
    await part2();
  } catch(e) {
     Sentry.withScope((scope) => {
       scope.setTag("requestId", ALS.get("requestId"));
       Sentry.captureException(new Error("..."))
     }) 
  }
}

withScope(work, "1234-5678");

You would have to keep track of the breadcrumbs yourself though, e.g. to add a breadcrumb:

ALS.set("breadcrumbs", [...ALS.get("breadcrumbs"), new_breadcrumb]);

And you would need to manually set these in the beforeSend() callback in Sentry:

beforeSend: function(data) {
  data.breadcrumbs = ALS.get("breadcrumbs");
  return data;
}
发布评论

评论列表(0)

  1. 暂无评论