From 454153caf95875a19a5f8d8d870daba58460493e Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Wed, 12 Apr 2023 11:32:49 +0200 Subject: [PATCH 1/4] feat(node): Add docs for async context apis --- .../common/configuration/async-context.mdx | 40 +++++++++++++++++ src/platforms/node/guides/express/index.mdx | 2 +- .../node/guides/express/performance/index.mdx | 3 +- .../automatic-instrumentation.mdx | 2 +- src/platforms/node/guides/koa/index.mdx | 44 +++++++++---------- .../node/guides/serverless-cloud/index.mdx | 2 +- src/wizard/node/express.md | 2 +- src/wizard/node/serverlesscloud.md | 2 +- 8 files changed, 67 insertions(+), 30 deletions(-) create mode 100644 src/platforms/node/common/configuration/async-context.mdx diff --git a/src/platforms/node/common/configuration/async-context.mdx b/src/platforms/node/common/configuration/async-context.mdx new file mode 100644 index 0000000000000..cac3381307d0c --- /dev/null +++ b/src/platforms/node/common/configuration/async-context.mdx @@ -0,0 +1,40 @@ +--- +title: Async Context +sidebar_order: 80 +description: "Learn more about how to isolate Sentry scope and breadcrumbs across requests." +--- + +You can use the `runWithAsyncContext` method to isolate Sentry scope and breadcrumbs to a single request if you are using SDK v7.48.0 or higher. This is useful if you are finding that breadcrumbs and scope are leaking across requests. + +```js +const Sentry = require("@sentry/node"); + +function requestHandlerMiddleware(req, res, next) { + // Any breadcrumbs or tags added will be isolated to the request + return Sentry.runWithAsyncContext( + () => { + return next(req, res); + }, + { emitters: [req, res] } + ); +} +``` + +Under the hood the SDK uses Node's [AsyncLocalStorage API](https://nodejs.org/api/async_context.html#class-asynclocalstorage) to perform the isolation. + +On lower SDK versions you'll have to use [domains](https://nodejs.org/api/domain.html) to isolate Sentry scope and breadcrumbs to a single request. + +```js +const domain = require("domain"); + +function myRequestHandler(req, res, next) { + const localDomain = domain.create(); + + localDomain.add(req); + localDomain.add(res); + + return localDomain.bind(() => { + return next(req, res); + })(); +} +``` diff --git a/src/platforms/node/guides/express/index.mdx b/src/platforms/node/guides/express/index.mdx index d45a8199e97d0..544f5681706ae 100644 --- a/src/platforms/node/guides/express/index.mdx +++ b/src/platforms/node/guides/express/index.mdx @@ -167,7 +167,7 @@ Sentry.init({ tracesSampleRate: 1.0, }); -// RequestHandler creates a separate execution context using domains, so that every +// RequestHandler creates a separate execution context, so that every // transaction/span/breadcrumb is attached to its own Hub instance app.use(Sentry.Handlers.requestHandler()); // TracingHandler creates a trace for every incoming request diff --git a/src/platforms/node/guides/express/performance/index.mdx b/src/platforms/node/guides/express/performance/index.mdx index 3684ae755118b..e70f25f8787a5 100644 --- a/src/platforms/node/guides/express/performance/index.mdx +++ b/src/platforms/node/guides/express/performance/index.mdx @@ -35,7 +35,7 @@ Sentry.init({ tracesSampleRate: 1.0, }); -// RequestHandler creates a separate execution context using domains, so that every +// RequestHandler creates a separate execution context, so that every // transaction/span/breadcrumb is attached to its own Hub instance app.use(Sentry.Handlers.requestHandler()); // TracingHandler creates a trace for every incoming request @@ -132,6 +132,7 @@ app.get("/success", function successHandler(req, res) { ## Connecting Services If you are also using Performance Monitoring for [JavaScript](/platforms/javascript/performance/), depending on where your request originates, you can connect traces: + 1. For requests that start in your backend, by [adding a meta tag](/platforms/javascript/performance/connect-services/#pageload) in your HTML template that contains tracing information. 2. For requests that start in JavaScript, by the SDK [setting a header](/platforms/javascript/performance/connect-services/#navigation-and-other-xhr-requests) on requests to your backend. diff --git a/src/platforms/node/guides/express/performance/instrumentation/automatic-instrumentation.mdx b/src/platforms/node/guides/express/performance/instrumentation/automatic-instrumentation.mdx index 2cdc10c624211..f969aac63e630 100644 --- a/src/platforms/node/guides/express/performance/instrumentation/automatic-instrumentation.mdx +++ b/src/platforms/node/guides/express/performance/instrumentation/automatic-instrumentation.mdx @@ -37,7 +37,7 @@ Sentry.init({ tracesSampleRate: 1.0, }); -// RequestHandler creates a separate execution context using domains, so that every +// RequestHandler creates a separate execution context, so that every // transaction/span/breadcrumb is attached to its own Hub instance app.use(Sentry.Handlers.requestHandler()); // TracingHandler creates a trace for every incoming request diff --git a/src/platforms/node/guides/koa/index.mdx b/src/platforms/node/guides/koa/index.mdx index 091de44ce8902..46380c7f3f883 100644 --- a/src/platforms/node/guides/koa/index.mdx +++ b/src/platforms/node/guides/koa/index.mdx @@ -49,7 +49,6 @@ const Sentry = require("@sentry/node"); const { stripUrlQueryAndFragment } = require("@sentry/utils"); const Koa = require("koa"); const app = new Koa(); -const domain = require("domain"); Sentry.init({ dsn: "___PUBLIC_DSN___", @@ -64,30 +63,25 @@ Sentry.init({ ], }); -// not mandatory, but adding domains does help a lot with breadcrumbs const requestHandler = (ctx, next) => { return new Promise((resolve, reject) => { - const local = domain.create(); - local.add(ctx); - local.on("error", err => { - ctx.status = err.status || 500; - ctx.body = err.message; - ctx.app.emit("error", err, ctx); - reject(err); - }); - local.run(async () => { - Sentry.getCurrentHub().configureScope(scope => - scope.addEventProcessor(event => - Sentry.addRequestDataToEvent(event, ctx.request, { - include: { - user: false, - }, - }); + Sentry.runWithAsyncContext( + async hub => { + hub.configureScope(scope => + scope.addEventProcessor(event => + Sentry.addRequestDataToEvent(event, ctx.request, { + include: { + user: false, + }, + }) + ) ); - ); - await next(); - resolve(); - }); + + await next(); + resolve(); + }, + { emitters: [ctx] } + ); }); }; @@ -99,7 +93,9 @@ const tracingMiddleWare = async (ctx, next) => { // connect to trace of upstream app let traceparentData; if (ctx.request.get("sentry-trace")) { - traceparentData = Sentry.extractTraceparentData(ctx.request.get("sentry-trace")); + traceparentData = Sentry.extractTraceparentData( + ctx.request.get("sentry-trace") + ); } const transaction = Sentry.startTransaction({ @@ -136,7 +132,7 @@ app.use(tracingMiddleWare); // usual error handler app.on("error", (err, ctx) => { - Sentry.withScope((scope) => { + Sentry.withScope(scope => { scope.addEventProcessor(event => { return Sentry.addRequestDataToEvent(event, ctx.request); }); diff --git a/src/platforms/node/guides/serverless-cloud/index.mdx b/src/platforms/node/guides/serverless-cloud/index.mdx index 5222464aac73a..5bb1db7fa4d05 100644 --- a/src/platforms/node/guides/serverless-cloud/index.mdx +++ b/src/platforms/node/guides/serverless-cloud/index.mdx @@ -142,7 +142,7 @@ Sentry.init({ // tracesSampleRate: parseFloat(params.SENTRY_TRACES_SAMPLE_RATE), }); -// RequestHandler creates a separate execution context using domains, so that every +// RequestHandler creates a separate execution context, so that every // transaction/span/breadcrumb is attached to its own Hub instance api.use(Sentry.Handlers.requestHandler()); // TracingHandler creates a trace for every incoming request diff --git a/src/wizard/node/express.md b/src/wizard/node/express.md index c542b17ac1f71..084117d204e64 100644 --- a/src/wizard/node/express.md +++ b/src/wizard/node/express.md @@ -44,7 +44,7 @@ Sentry.init({ tracesSampleRate: 1.0, }); -// RequestHandler creates a separate execution context using domains, so that every +// RequestHandler creates a separate execution context, so that every // transaction/span/breadcrumb is attached to its own Hub instance app.use(Sentry.Handlers.requestHandler()); // TracingHandler creates a trace for every incoming request diff --git a/src/wizard/node/serverlesscloud.md b/src/wizard/node/serverlesscloud.md index 6cc2c304a3fa1..c398d2e024439 100644 --- a/src/wizard/node/serverlesscloud.md +++ b/src/wizard/node/serverlesscloud.md @@ -43,7 +43,7 @@ Sentry.init({ // tracesSampleRate: parseFloat(params.SENTRY_TRACES_SAMPLE_RATE), }); -// RequestHandler creates a separate execution context using domains, so that every +// RequestHandler creates a separate execution context, so that every // transaction/span/breadcrumb is attached to its own Hub instance api.use(Sentry.Handlers.requestHandler()); // TracingHandler creates a trace for every incoming request From 578478fefe743543f22efbe4d702743f3a958cb0 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Thu, 13 Apr 2023 12:10:02 +0200 Subject: [PATCH 2/4] Update src/platforms/node/common/configuration/async-context.mdx Co-authored-by: Shana Matthews --- src/platforms/node/common/configuration/async-context.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platforms/node/common/configuration/async-context.mdx b/src/platforms/node/common/configuration/async-context.mdx index cac3381307d0c..88d51cc229783 100644 --- a/src/platforms/node/common/configuration/async-context.mdx +++ b/src/platforms/node/common/configuration/async-context.mdx @@ -20,7 +20,7 @@ function requestHandlerMiddleware(req, res, next) { } ``` -Under the hood the SDK uses Node's [AsyncLocalStorage API](https://nodejs.org/api/async_context.html#class-asynclocalstorage) to perform the isolation. +Under the hood, the SDK uses Node's [AsyncLocalStorage API](https://nodejs.org/api/async_context.html#class-asynclocalstorage) to perform the isolation. On lower SDK versions you'll have to use [domains](https://nodejs.org/api/domain.html) to isolate Sentry scope and breadcrumbs to a single request. From ac5307d534bfaade38988e6a6246198330be5c76 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Thu, 13 Apr 2023 14:39:53 +0200 Subject: [PATCH 3/4] address pr review --- src/platforms/node/common/configuration/async-context.mdx | 3 +++ src/platforms/node/guides/express/index.mdx | 4 ++-- src/platforms/node/guides/express/performance/index.mdx | 4 ++-- .../performance/instrumentation/automatic-instrumentation.mdx | 4 ++-- src/platforms/node/guides/serverless-cloud/index.mdx | 4 ++-- src/wizard/node/express.md | 4 ++-- src/wizard/node/serverlesscloud.md | 4 ++-- 7 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/platforms/node/common/configuration/async-context.mdx b/src/platforms/node/common/configuration/async-context.mdx index 88d51cc229783..93c989c796959 100644 --- a/src/platforms/node/common/configuration/async-context.mdx +++ b/src/platforms/node/common/configuration/async-context.mdx @@ -15,6 +15,8 @@ function requestHandlerMiddleware(req, res, next) { () => { return next(req, res); }, + // emitters are event emitters that need to be tracked by async + // context since they emit async events { emitters: [req, res] } ); } @@ -30,6 +32,7 @@ const domain = require("domain"); function myRequestHandler(req, res, next) { const localDomain = domain.create(); + // track event emitters in domain localDomain.add(req); localDomain.add(res); diff --git a/src/platforms/node/guides/express/index.mdx b/src/platforms/node/guides/express/index.mdx index 544f5681706ae..3b571526ef92d 100644 --- a/src/platforms/node/guides/express/index.mdx +++ b/src/platforms/node/guides/express/index.mdx @@ -167,8 +167,8 @@ Sentry.init({ tracesSampleRate: 1.0, }); -// RequestHandler creates a separate execution context, so that every -// transaction/span/breadcrumb is attached to its own Hub instance +// RequestHandler creates a separate execution context, so that all +// transactions/spans/breadcrumbs are isolated across requests app.use(Sentry.Handlers.requestHandler()); // TracingHandler creates a trace for every incoming request app.use(Sentry.Handlers.tracingHandler()); diff --git a/src/platforms/node/guides/express/performance/index.mdx b/src/platforms/node/guides/express/performance/index.mdx index e70f25f8787a5..5e8df518abd8a 100644 --- a/src/platforms/node/guides/express/performance/index.mdx +++ b/src/platforms/node/guides/express/performance/index.mdx @@ -35,8 +35,8 @@ Sentry.init({ tracesSampleRate: 1.0, }); -// RequestHandler creates a separate execution context, so that every -// transaction/span/breadcrumb is attached to its own Hub instance +// RequestHandler creates a separate execution context, so that all +// transactions/spans/breadcrumbs are isolated across requests app.use(Sentry.Handlers.requestHandler()); // TracingHandler creates a trace for every incoming request app.use(Sentry.Handlers.tracingHandler()); diff --git a/src/platforms/node/guides/express/performance/instrumentation/automatic-instrumentation.mdx b/src/platforms/node/guides/express/performance/instrumentation/automatic-instrumentation.mdx index f969aac63e630..f81c4548b41f7 100644 --- a/src/platforms/node/guides/express/performance/instrumentation/automatic-instrumentation.mdx +++ b/src/platforms/node/guides/express/performance/instrumentation/automatic-instrumentation.mdx @@ -37,8 +37,8 @@ Sentry.init({ tracesSampleRate: 1.0, }); -// RequestHandler creates a separate execution context, so that every -// transaction/span/breadcrumb is attached to its own Hub instance +// RequestHandler creates a separate execution context, so that all +// transactions/spans/breadcrumbs are isolated across requests app.use(Sentry.Handlers.requestHandler()); // TracingHandler creates a trace for every incoming request app.use(Sentry.Handlers.tracingHandler()); diff --git a/src/platforms/node/guides/serverless-cloud/index.mdx b/src/platforms/node/guides/serverless-cloud/index.mdx index 5bb1db7fa4d05..98d81edb37aca 100644 --- a/src/platforms/node/guides/serverless-cloud/index.mdx +++ b/src/platforms/node/guides/serverless-cloud/index.mdx @@ -142,8 +142,8 @@ Sentry.init({ // tracesSampleRate: parseFloat(params.SENTRY_TRACES_SAMPLE_RATE), }); -// RequestHandler creates a separate execution context, so that every -// transaction/span/breadcrumb is attached to its own Hub instance +// RequestHandler creates a separate execution context, so that all +// transactions/spans/breadcrumbs are isolated across requests api.use(Sentry.Handlers.requestHandler()); // TracingHandler creates a trace for every incoming request api.use(Sentry.Handlers.tracingHandler()); diff --git a/src/wizard/node/express.md b/src/wizard/node/express.md index 084117d204e64..f29d648595b96 100644 --- a/src/wizard/node/express.md +++ b/src/wizard/node/express.md @@ -44,8 +44,8 @@ Sentry.init({ tracesSampleRate: 1.0, }); -// RequestHandler creates a separate execution context, so that every -// transaction/span/breadcrumb is attached to its own Hub instance +// RequestHandler creates a separate execution context, so that all +// transactions/spans/breadcrumbs are isolated across requests app.use(Sentry.Handlers.requestHandler()); // TracingHandler creates a trace for every incoming request app.use(Sentry.Handlers.tracingHandler()); diff --git a/src/wizard/node/serverlesscloud.md b/src/wizard/node/serverlesscloud.md index c398d2e024439..23f19d464fdb1 100644 --- a/src/wizard/node/serverlesscloud.md +++ b/src/wizard/node/serverlesscloud.md @@ -43,8 +43,8 @@ Sentry.init({ // tracesSampleRate: parseFloat(params.SENTRY_TRACES_SAMPLE_RATE), }); -// RequestHandler creates a separate execution context, so that every -// transaction/span/breadcrumb is attached to its own Hub instance +// RequestHandler creates a separate execution context, so that all +// transactions/spans/breadcrumbs are isolated across requests api.use(Sentry.Handlers.requestHandler()); // TracingHandler creates a trace for every incoming request api.use(Sentry.Handlers.tracingHandler()); From 7f2ff05d9b5ec7fbc451be4d7e5351727f6a61d3 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Thu, 13 Apr 2023 16:22:16 +0200 Subject: [PATCH 4/4] update API as per new changes --- .../common/configuration/async-context.mdx | 16 ++-------- src/platforms/node/guides/koa/index.mdx | 32 +++++++++---------- 2 files changed, 18 insertions(+), 30 deletions(-) diff --git a/src/platforms/node/common/configuration/async-context.mdx b/src/platforms/node/common/configuration/async-context.mdx index 93c989c796959..bfe49e6418681 100644 --- a/src/platforms/node/common/configuration/async-context.mdx +++ b/src/platforms/node/common/configuration/async-context.mdx @@ -11,14 +11,9 @@ const Sentry = require("@sentry/node"); function requestHandlerMiddleware(req, res, next) { // Any breadcrumbs or tags added will be isolated to the request - return Sentry.runWithAsyncContext( - () => { - return next(req, res); - }, - // emitters are event emitters that need to be tracked by async - // context since they emit async events - { emitters: [req, res] } - ); + return Sentry.runWithAsyncContext(() => { + return next(req, res); + }); } ``` @@ -31,11 +26,6 @@ const domain = require("domain"); function myRequestHandler(req, res, next) { const localDomain = domain.create(); - - // track event emitters in domain - localDomain.add(req); - localDomain.add(res); - return localDomain.bind(() => { return next(req, res); })(); diff --git a/src/platforms/node/guides/koa/index.mdx b/src/platforms/node/guides/koa/index.mdx index 46380c7f3f883..0a51f3cb1f9fe 100644 --- a/src/platforms/node/guides/koa/index.mdx +++ b/src/platforms/node/guides/koa/index.mdx @@ -65,23 +65,21 @@ Sentry.init({ const requestHandler = (ctx, next) => { return new Promise((resolve, reject) => { - Sentry.runWithAsyncContext( - async hub => { - hub.configureScope(scope => - scope.addEventProcessor(event => - Sentry.addRequestDataToEvent(event, ctx.request, { - include: { - user: false, - }, - }) - ) - ); - - await next(); - resolve(); - }, - { emitters: [ctx] } - ); + Sentry.runWithAsyncContext(async () => { + const hub = Sentry.getCurrentHub(); + hub.configureScope(scope => + scope.addEventProcessor(event => + Sentry.addRequestDataToEvent(event, ctx.request, { + include: { + user: false, + }, + }) + ) + ); + + await next(); + resolve(); + }); }); };