diff --git a/dev-packages/node-integration-tests/suites/express/tracing/tracesSampler/scenario-normalizedRequest.js b/dev-packages/node-integration-tests/suites/express/tracing/tracesSampler/scenario-normalizedRequest.js new file mode 100644 index 000000000000..da31780f2c5f --- /dev/null +++ b/dev-packages/node-integration-tests/suites/express/tracing/tracesSampler/scenario-normalizedRequest.js @@ -0,0 +1,34 @@ +const { loggingTransport } = require('@sentry-internal/node-integration-tests'); +const Sentry = require('@sentry/node'); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + transport: loggingTransport, + tracesSampler: samplingContext => { + // The sampling decision is based on whether the data in `normalizedRequest` is available --> this is what we want to test for + return ( + samplingContext.normalizedRequest.url.includes('/test-normalized-request?query=123') && + samplingContext.normalizedRequest.method && + samplingContext.normalizedRequest.query_string === 'query=123' && + !!samplingContext.normalizedRequest.headers + ); + }, +}); + +// express must be required after Sentry is initialized +const express = require('express'); +const cors = require('cors'); +const { startExpressServerAndSendPortToRunner } = require('@sentry-internal/node-integration-tests'); + +const app = express(); + +app.use(cors()); + +app.get('/test-normalized-request', (_req, res) => { + res.send('Success'); +}); + +Sentry.setupExpressErrorHandler(app); + +startExpressServerAndSendPortToRunner(app); diff --git a/dev-packages/node-integration-tests/suites/express/tracing/tracesSampler/server.js b/dev-packages/node-integration-tests/suites/express/tracing/tracesSampler/server.js index c096871cb755..b60ea07b636f 100644 --- a/dev-packages/node-integration-tests/suites/express/tracing/tracesSampler/server.js +++ b/dev-packages/node-integration-tests/suites/express/tracing/tracesSampler/server.js @@ -15,7 +15,6 @@ Sentry.init({ samplingContext.attributes['http.method'] === 'GET' ); }, - debug: true, }); // express must be required after Sentry is initialized diff --git a/dev-packages/node-integration-tests/suites/express/tracing/tracesSampler/test.ts b/dev-packages/node-integration-tests/suites/express/tracing/tracesSampler/test.ts index a19299787f91..07cc8d094d8f 100644 --- a/dev-packages/node-integration-tests/suites/express/tracing/tracesSampler/test.ts +++ b/dev-packages/node-integration-tests/suites/express/tracing/tracesSampler/test.ts @@ -22,3 +22,23 @@ describe('express tracesSampler', () => { }); }); }); + +describe('express tracesSampler includes normalizedRequest data', () => { + afterAll(() => { + cleanupChildProcesses(); + }); + + describe('CJS', () => { + test('correctly samples & passes data to tracesSampler', done => { + const runner = createRunner(__dirname, 'scenario-normalizedRequest.js') + .expect({ + transaction: { + transaction: 'GET /test-normalized-request', + }, + }) + .start(done); + + runner.makeRequest('get', '/test-normalized-request?query=123'); + }); + }); +}); diff --git a/packages/core/src/tracing/sampling.ts b/packages/core/src/tracing/sampling.ts index 9109e78e0343..9ad01bc98a27 100644 --- a/packages/core/src/tracing/sampling.ts +++ b/packages/core/src/tracing/sampling.ts @@ -1,5 +1,6 @@ -import type { Options, SamplingContext } from '../types-hoist'; +import type { Options, RequestEventData, SamplingContext } from '../types-hoist'; +import { getIsolationScope } from '../currentScopes'; import { DEBUG_BUILD } from '../debug-build'; import { logger } from '../utils-hoist/logger'; import { hasTracingEnabled } from '../utils/hasTracingEnabled'; @@ -20,13 +21,22 @@ export function sampleSpan( return [false]; } + // Casting this from unknown, as the type of `sdkProcessingMetadata` is only changed in v9 and `normalizedRequest` is set in SentryHttpInstrumentation + const normalizedRequest = getIsolationScope().getScopeData().sdkProcessingMetadata + .normalizedRequest as RequestEventData; + + const enhancedSamplingContext = { + ...samplingContext, + normalizedRequest: samplingContext.normalizedRequest || normalizedRequest, + }; + // we would have bailed already if neither `tracesSampler` nor `tracesSampleRate` nor `enableTracing` were defined, so one of these should // work; prefer the hook if so let sampleRate; if (typeof options.tracesSampler === 'function') { - sampleRate = options.tracesSampler(samplingContext); - } else if (samplingContext.parentSampled !== undefined) { - sampleRate = samplingContext.parentSampled; + sampleRate = options.tracesSampler(enhancedSamplingContext); + } else if (enhancedSamplingContext.parentSampled !== undefined) { + sampleRate = enhancedSamplingContext.parentSampled; } else if (typeof options.tracesSampleRate !== 'undefined') { sampleRate = options.tracesSampleRate; } else { diff --git a/packages/core/src/types-hoist/samplingcontext.ts b/packages/core/src/types-hoist/samplingcontext.ts index ecce87d7fbc7..1cb15490e5b2 100644 --- a/packages/core/src/types-hoist/samplingcontext.ts +++ b/packages/core/src/types-hoist/samplingcontext.ts @@ -1,3 +1,4 @@ +import type { RequestEventData } from '../types-hoist/request'; import type { ExtractedNodeRequestData, WorkerLocation } from './misc'; import type { SpanAttributes } from './span'; @@ -35,10 +36,16 @@ export interface SamplingContext extends CustomSamplingContext { location?: WorkerLocation; /** - * Object representing the incoming request to a node server. Passed by default when using the TracingHandler. + * Object representing the incoming request to a node server. + * @deprecated This attribute is currently never defined and will be removed in v9. Use `normalizedRequest` instead */ request?: ExtractedNodeRequestData; + /** + * Object representing the incoming request to a node server in a normalized format. + */ + normalizedRequest?: RequestEventData; + /** The name of the span being sampled. */ name: string;