From 7fd3807be1ccd025f2639697144468d7835ac5fd Mon Sep 17 00:00:00 2001 From: RulaKhaled Date: Mon, 17 Aug 2026 16:58:35 +0200 Subject: [PATCH 1/8] ref(node)!: Consolidate httpIntegration options Public option names now match httpServerIntegration / httpServerSpansIntegration. Nested instrumentation hooks are first-class outgoing hooks instead of a leftover OTEL-era nest. Fixes #22255 --- .../tracing/instrument-filterStatusCode.mjs | 2 +- .../suites/express/tracing/instrument.mjs | 2 +- .../instrument-always.mjs | 2 +- .../instrument-medium.mjs | 2 +- .../instrument-none.mjs | 4 +- .../instrument-small.mjs | 2 +- .../maxIncomingRequestBodySize/test.ts | 14 +- .../httpIntegration/instrument-options.mjs | 6 +- .../suites/tracing/httpIntegration/test.ts | 8 +- docs/migration/v11-end-state.md | 57 +++++++- packages/core/src/integrations/http/types.ts | 2 +- .../http/SentryHttpInstrumentation.ts | 81 +---------- .../http/httpServerIntegration.ts | 2 +- packages/node/src/integrations/http/index.ts | 132 +++--------------- packages/nuxt/src/server/sdk.ts | 5 +- 15 files changed, 105 insertions(+), 216 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/express/tracing/instrument-filterStatusCode.mjs b/dev-packages/node-integration-tests/suites/express/tracing/instrument-filterStatusCode.mjs index 884d250718ae..6638b7e92cb8 100644 --- a/dev-packages/node-integration-tests/suites/express/tracing/instrument-filterStatusCode.mjs +++ b/dev-packages/node-integration-tests/suites/express/tracing/instrument-filterStatusCode.mjs @@ -9,7 +9,7 @@ Sentry.init({ transport: loggingTransport, integrations: [ Sentry.httpIntegration({ - dropSpansForIncomingRequestStatusCodes: [499, [300, 399]], + ignoreStatusCodes: [499, [300, 399]], }), ], }); diff --git a/dev-packages/node-integration-tests/suites/express/tracing/instrument.mjs b/dev-packages/node-integration-tests/suites/express/tracing/instrument.mjs index d58a18e483c7..c727f3046e61 100644 --- a/dev-packages/node-integration-tests/suites/express/tracing/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/express/tracing/instrument.mjs @@ -11,7 +11,7 @@ Sentry.init({ transport: loggingTransport, integrations: [ Sentry.httpIntegration({ - ignoreIncomingRequestBody: url => { + ignoreRequestBody: url => { if (url.includes('/test-post-ignore-body')) { return true; } diff --git a/dev-packages/node-integration-tests/suites/express/with-http/maxIncomingRequestBodySize/instrument-always.mjs b/dev-packages/node-integration-tests/suites/express/with-http/maxIncomingRequestBodySize/instrument-always.mjs index ffeead2bc0b5..2ed2c4b03868 100644 --- a/dev-packages/node-integration-tests/suites/express/with-http/maxIncomingRequestBodySize/instrument-always.mjs +++ b/dev-packages/node-integration-tests/suites/express/with-http/maxIncomingRequestBodySize/instrument-always.mjs @@ -7,5 +7,5 @@ Sentry.init({ release: '1.0', tracesSampleRate: 1.0, transport: loggingTransport, - integrations: [Sentry.httpIntegration({ maxIncomingRequestBodySize: 'always' })], + integrations: [Sentry.httpIntegration({ maxRequestBodySize: 'always' })], }); diff --git a/dev-packages/node-integration-tests/suites/express/with-http/maxIncomingRequestBodySize/instrument-medium.mjs b/dev-packages/node-integration-tests/suites/express/with-http/maxIncomingRequestBodySize/instrument-medium.mjs index 7f8aa77414bb..2ea1a18c449c 100644 --- a/dev-packages/node-integration-tests/suites/express/with-http/maxIncomingRequestBodySize/instrument-medium.mjs +++ b/dev-packages/node-integration-tests/suites/express/with-http/maxIncomingRequestBodySize/instrument-medium.mjs @@ -7,5 +7,5 @@ Sentry.init({ release: '1.0', tracesSampleRate: 1.0, transport: loggingTransport, - integrations: [Sentry.httpIntegration({ maxIncomingRequestBodySize: 'medium' })], + integrations: [Sentry.httpIntegration({ maxRequestBodySize: 'medium' })], }); diff --git a/dev-packages/node-integration-tests/suites/express/with-http/maxIncomingRequestBodySize/instrument-none.mjs b/dev-packages/node-integration-tests/suites/express/with-http/maxIncomingRequestBodySize/instrument-none.mjs index 74139dcce86d..b3c2c06d3dd9 100644 --- a/dev-packages/node-integration-tests/suites/express/with-http/maxIncomingRequestBodySize/instrument-none.mjs +++ b/dev-packages/node-integration-tests/suites/express/with-http/maxIncomingRequestBodySize/instrument-none.mjs @@ -9,8 +9,8 @@ Sentry.init({ transport: loggingTransport, integrations: [ Sentry.httpIntegration({ - maxIncomingRequestBodySize: 'none', - ignoreIncomingRequestBody: url => url.includes('/ignore-request-body'), + maxRequestBodySize: 'none', + ignoreRequestBody: url => url.includes('/ignore-request-body'), }), ], }); diff --git a/dev-packages/node-integration-tests/suites/express/with-http/maxIncomingRequestBodySize/instrument-small.mjs b/dev-packages/node-integration-tests/suites/express/with-http/maxIncomingRequestBodySize/instrument-small.mjs index 5e056b9dff30..2331b64953b4 100644 --- a/dev-packages/node-integration-tests/suites/express/with-http/maxIncomingRequestBodySize/instrument-small.mjs +++ b/dev-packages/node-integration-tests/suites/express/with-http/maxIncomingRequestBodySize/instrument-small.mjs @@ -8,5 +8,5 @@ Sentry.init({ tracesSampleRate: 1.0, transport: loggingTransport, dataCollection: { httpBodies: [] }, - integrations: [Sentry.httpIntegration({ maxIncomingRequestBodySize: 'small' })], + integrations: [Sentry.httpIntegration({ maxRequestBodySize: 'small' })], }); diff --git a/dev-packages/node-integration-tests/suites/express/with-http/maxIncomingRequestBodySize/test.ts b/dev-packages/node-integration-tests/suites/express/with-http/maxIncomingRequestBodySize/test.ts index 6e5843b54b9b..2cc5595f4d20 100644 --- a/dev-packages/node-integration-tests/suites/express/with-http/maxIncomingRequestBodySize/test.ts +++ b/dev-packages/node-integration-tests/suites/express/with-http/maxIncomingRequestBodySize/test.ts @@ -12,7 +12,7 @@ const MAX_GENERAL = 1024 * 1024; // 1MB const MAX_MEDIUM = 10_000; const MAX_SMALL = 1000; -describe('express with httpIntegration and not defined maxIncomingRequestBodySize', () => { +describe('express with httpIntegration and not defined maxRequestBodySize', () => { afterAll(() => { cleanupChildProcesses(); }); @@ -60,7 +60,7 @@ describe('express with httpIntegration and not defined maxIncomingRequestBodySiz }); }); -describe('express with httpIntegration, disabled httpBodies, and explicit maxIncomingRequestBodySize', () => { +describe('express with httpIntegration, disabled httpBodies, and explicit maxRequestBodySize', () => { afterAll(() => { cleanupChildProcesses(); }); @@ -88,7 +88,7 @@ describe('express with httpIntegration, disabled httpBodies, and explicit maxInc }); }); -describe('express with httpIntegration and maxIncomingRequestBodySize: "none"', () => { +describe('express with httpIntegration and maxRequestBodySize: "none"', () => { afterAll(() => { cleanupChildProcesses(); }); @@ -114,7 +114,7 @@ describe('express with httpIntegration and maxIncomingRequestBodySize: "none"', await runner.completed(); }); - test('does not capture any request bodies with "none" setting and "ignoreIncomingRequestBody"', async () => { + test('does not capture any request bodies with "none" setting and "ignoreRequestBody"', async () => { const runner = createRunner() .expect({ transaction: { @@ -149,7 +149,7 @@ describe('express with httpIntegration and maxIncomingRequestBodySize: "none"', }); }); -describe('express with httpIntegration and maxIncomingRequestBodySize: "always"', () => { +describe('express with httpIntegration and maxRequestBodySize: "always"', () => { afterAll(() => { cleanupChildProcesses(); }); @@ -197,7 +197,7 @@ describe('express with httpIntegration and maxIncomingRequestBodySize: "always"' }); }); -describe('express with httpIntegration and maxIncomingRequestBodySize: "small"', () => { +describe('express with httpIntegration and maxRequestBodySize: "small"', () => { afterAll(() => { cleanupChildProcesses(); }); @@ -266,7 +266,7 @@ describe('express with httpIntegration and maxIncomingRequestBodySize: "small"', }); }); -describe('express with httpIntegration and maxIncomingRequestBodySize: "medium"', () => { +describe('express with httpIntegration and maxRequestBodySize: "medium"', () => { afterAll(() => { cleanupChildProcesses(); }); diff --git a/dev-packages/node-integration-tests/suites/tracing/httpIntegration/instrument-options.mjs b/dev-packages/node-integration-tests/suites/tracing/httpIntegration/instrument-options.mjs index 96aea9ff619e..6908a019faa3 100644 --- a/dev-packages/node-integration-tests/suites/tracing/httpIntegration/instrument-options.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/httpIntegration/instrument-options.mjs @@ -10,9 +10,9 @@ Sentry.init({ integrations: [ Sentry.httpIntegration({ - incomingRequestSpanHook: (span, req, res) => { - span.setAttribute('incomingRequestSpanHook', 'yes'); - Sentry.setExtra('incomingRequestSpanHookCalled', { + onSpanCreated: (span, req, res) => { + span.setAttribute('onSpanCreated', 'yes'); + Sentry.setExtra('onSpanCreatedCalled', { reqUrl: req.url, reqMethod: req.method, resUrl: res.req.url, diff --git a/dev-packages/node-integration-tests/suites/tracing/httpIntegration/test.ts b/dev-packages/node-integration-tests/suites/tracing/httpIntegration/test.ts index e1053a706d16..a7579c7f9d07 100644 --- a/dev-packages/node-integration-tests/suites/tracing/httpIntegration/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/httpIntegration/test.ts @@ -20,9 +20,9 @@ describe('httpIntegration', () => { cleanupChildProcesses(); }); - describe('instrumentation options', () => { + describe('onSpanCreated option', () => { createEsmAndCjsTests(__dirname, 'server.mjs', 'instrument-options.mjs', (createRunner, test) => { - test('allows to configure incomingRequestSpanHook', async () => { + test('allows to configure onSpanCreated', async () => { const runner = createRunner() .expect({ transaction: { @@ -33,14 +33,14 @@ describe('httpIntegration', () => { data: { 'url.full': expect.stringMatching(/\/test$/), 'http.response.status_code': 200, - incomingRequestSpanHook: 'yes', + onSpanCreated: 'yes', }, op: 'http.server', status: 'ok', }, }, extra: expect.objectContaining({ - incomingRequestSpanHookCalled: { + onSpanCreatedCalled: { reqUrl: expect.stringMatching(/\/test$/), reqMethod: 'GET', resUrl: expect.stringMatching(/\/test$/), diff --git a/docs/migration/v11-end-state.md b/docs/migration/v11-end-state.md index e127ce331818..06e19049e21d 100644 --- a/docs/migration/v11-end-state.md +++ b/docs/migration/v11-end-state.md @@ -516,11 +516,11 @@ Two consequences to be aware of when upgrading: - **Issue grouping:** Grouping in Sentry differs for events with and without stack traces, so you may see new issue groups after upgrading. - **Release health:** Events with a stack trace are counted as errors, so a `captureMessage` call (including messages emitted by `captureConsoleIntegration`) now marks the current session as _errored_. This affects errored-session counts but does **not** mark sessions as crashed, so crash-free session rate is unaffected. If you use `captureMessage` for purely informational output, consider using Sentry Logs instead, which is better suited and does not affect release health. -### Incoming HTTP span hooks moved to `incomingRequestSpanHook` +### Incoming HTTP span hooks moved to `onSpanCreated` Affected SDKs: `@sentry/node` and dependents. -The deprecated `httpIntegration` / `httpServerSpansIntegration` hooks `instrumentation.requestHook`, `instrumentation.responseHook`, and `instrumentation.applyCustomAttributesOnSpan` no longer run for incoming request spans. Use `incomingRequestSpanHook` (on `httpIntegration`) or `onSpanCreated` (on `httpServerSpansIntegration`) instead: +The deprecated `httpIntegration` / `httpServerSpansIntegration` hooks `instrumentation.requestHook`, `instrumentation.responseHook`, and `instrumentation.applyCustomAttributesOnSpan` no longer run for incoming request spans. Use `onSpanCreated` instead. Outgoing request spans use `outgoingRequestHook`, `outgoingResponseHook`, and `outgoingRequestApplyCustomAttributes`. ```js // before @@ -534,14 +534,12 @@ Sentry.httpIntegration({ // after Sentry.httpIntegration({ - incomingRequestSpanHook: (span, req, res) => { + onSpanCreated: (span, req, res) => { span.setAttribute('custom', true); }, }); ``` -`httpIntegration`'s `instrumentation` option is still honored for **outgoing** request spans. - ### Node HTTP transport `keepAlive` defaults to `true` Affected SDKs: `@sentry/node` and dependents. @@ -843,7 +841,54 @@ Sentry.init({ - (Express) The deprecated `patchExpressModule(options)` signature was removed. Use `patchExpressModule(moduleExports, getOptions)` instead. - The `@sentry/node-core/light/otlp` entry point was removed, along with its optional `@opentelemetry/exporter-trace-otlp-http` peer dependency. `otlpIntegration` is now exported directly from every server-side SDK, so `Sentry.otlpIntegration()` needs no extra import or install. - The `otlpIntegration` options `setupOtlpTracesExporter` and `collectorUrl` were removed, and the integration no longer sets up a span exporter, span processor, or tracer provider. Configure your own exporter and point it at `Sentry.getOtlpTracesEndpoint(dsn)`, or at your collector's URL if you route through one. See [Connecting Sentry to your OpenTelemetry traces](#connecting-sentry-to-your-opentelemetry-traces). -- The deprecated `httpServerSpansIntegration` `instrumentation.{requestHook,responseHook,applyCustomAttributesOnSpan}` option was removed. Use `onSpanCreated`, or `httpIntegration({ incomingRequestSpanHook })`, to mutate incoming request spans. +- The deprecated `httpServerSpansIntegration` `instrumentation.{requestHook,responseHook,applyCustomAttributesOnSpan}` option was removed. Use `onSpanCreated` to mutate incoming request spans, or `outgoingRequestHook` / `outgoingResponseHook` / `outgoingRequestApplyCustomAttributes` for outgoing request spans. + +#### `httpIntegration` options were consolidated + +`httpIntegration` option names now match `httpServerIntegration` / `httpServerSpansIntegration` and the other server SDKs. The deprecated `instrumentation` hooks were removed. + +| Removed option | Replacement | +| --------------------------------------------- | ------------------------------------------------------------------------------- | +| `trackIncomingRequestsAsSessions` | `sessions` | +| `maxIncomingRequestBodySize` | `maxRequestBodySize` | +| `ignoreIncomingRequestBody` | `ignoreRequestBody` | +| `dropSpansForIncomingRequestStatusCodes` | `ignoreStatusCodes` | +| `incomingRequestSpanHook` | `onSpanCreated` | +| `instrumentation.requestHook` | `onSpanCreated` (incoming) or `outgoingRequestHook` (outgoing) | +| `instrumentation.responseHook` | `onSpanCreated` (incoming) or `outgoingResponseHook` (outgoing) | +| `instrumentation.applyCustomAttributesOnSpan` | `onSpanCreated` (incoming) or `outgoingRequestApplyCustomAttributes` (outgoing) | + +```js +// before +Sentry.httpIntegration({ + trackIncomingRequestsAsSessions: false, + maxIncomingRequestBodySize: 'small', + ignoreIncomingRequestBody: url => url.includes('/health'), + dropSpansForIncomingRequestStatusCodes: [404], + incomingRequestSpanHook: (span, req, res) => { + span.setAttribute('custom', true); + }, + instrumentation: { + responseHook: () => { + void flushIfServerless(); + }, + }, +}); + +// after +Sentry.httpIntegration({ + sessions: false, + maxRequestBodySize: 'small', + ignoreRequestBody: url => url.includes('/health'), + ignoreStatusCodes: [404], + onSpanCreated: (span, req, res) => { + span.setAttribute('custom', true); + }, + outgoingResponseHook: () => { + void flushIfServerless(); + }, +}); +``` ### `@sentry/cloudflare` diff --git a/packages/core/src/integrations/http/types.ts b/packages/core/src/integrations/http/types.ts index 1ff9e18a0dbd..1dbb9cc54731 100644 --- a/packages/core/src/integrations/http/types.ts +++ b/packages/core/src/integrations/http/types.ts @@ -223,7 +223,7 @@ export interface HttpInstrumentationOptions { sessions?: boolean; /** - * Number of milliseconds until sessions tracked with `trackIncomingRequestsAsSessions` will be flushed as a session aggregate. + * Number of milliseconds until sessions tracked with `sessions` will be flushed as a session aggregate. * * Defaults to `60000` (60s). */ diff --git a/packages/node/src/integrations/http/SentryHttpInstrumentation.ts b/packages/node/src/integrations/http/SentryHttpInstrumentation.ts index 773b87cc33f7..3dee2b179969 100644 --- a/packages/node/src/integrations/http/SentryHttpInstrumentation.ts +++ b/packages/node/src/integrations/http/SentryHttpInstrumentation.ts @@ -1,6 +1,6 @@ import { subscribe } from 'node:diagnostics_channel'; import { context, trace } from '@opentelemetry/api'; -import type { ClientRequest, IncomingMessage, ServerResponse } from 'node:http'; +import type { ClientRequest, IncomingMessage } from 'node:http'; import type { HttpClientRequest, HttpIncomingMessage, HttpInstrumentationOptions, Span } from '@sentry/core'; import { getHttpClientSubscriptions, @@ -27,9 +27,7 @@ interface OutgoingHttpRequestInstrumentationOptions { breadcrumbs?: boolean; /** - * Whether to create spans for outgoing requests (user preference). - * This only takes effect if `createSpansForOutgoingRequests` is not disabled. - * If `createSpansForOutgoingRequests` is explicitly set false, this option is ignored. + * Whether to create spans for outgoing requests. * * @default `true` */ @@ -40,14 +38,7 @@ interface OutgoingHttpRequestInstrumentationOptions { * * @default `true` */ - propagateTraceInOutgoingRequests?: boolean; - - /** - * @deprecated Use spans option instead. - * - * @default `true` - */ - createSpansForOutgoingRequests?: boolean; + propagateTrace?: boolean; /** * Do not instrument outgoing HTTP requests to URLs where the given callback returns `true`. @@ -62,7 +53,6 @@ interface OutgoingHttpRequestInstrumentationOptions { /** * Hooks for outgoing request spans, only called when spans are created for outgoing requests * (i.e. when `spans` is enabled). - * These mirror the OTEL HttpInstrumentation hooks for backwards compatibility. */ outgoingRequestHook?: (span: Span, request: ClientRequest | HttpClientRequest) => void; outgoingResponseHook?: (span: Span, response: IncomingMessage | HttpIncomingMessage) => void; @@ -73,82 +63,23 @@ interface OutgoingHttpRequestInstrumentationOptions { ) => void; } -export type SentryHttpInstrumentationOptions = OutgoingHttpRequestInstrumentationOptions & { - // All options below do not do anything anymore in this instrumentation, and will be removed in the future. - // They are only kept here for backwards compatibility - the respective functionality is now handled by the httpServerIntegration/httpServerSpansIntegration. - - /** - * @deprecated This no longer does anything. - */ - extractIncomingTraceFromHeader?: boolean; - - /** - * @deprecated This no longer does anything. - */ - ignoreStaticAssets?: boolean; - - /** - * @deprecated This no longer does anything. - */ - disableIncomingRequestSpans?: boolean; - - /** - * @deprecated This no longer does anything. - */ - ignoreSpansForIncomingRequests?: (urlPath: string, request: IncomingMessage) => boolean; - - /** - * @deprecated This no longer does anything. - */ - ignoreIncomingRequestBody?: (url: string, request: http.RequestOptions) => boolean; - - /** - * @deprecated This no longer does anything. - */ - maxIncomingRequestBodySize?: 'none' | 'small' | 'medium' | 'always'; - - /** - * @deprecated This no longer does anything. - */ - trackIncomingRequestsAsSessions?: boolean; - - /** - * @deprecated This no longer does anything. - */ - instrumentation?: { - requestHook?: (span: Span, req: ClientRequest | IncomingMessage) => void; - responseHook?: (span: Span, response: IncomingMessage | ServerResponse) => void; - applyCustomAttributesOnSpan?: ( - span: Span, - request: ClientRequest | IncomingMessage, - response: IncomingMessage | ServerResponse, - ) => void; - }; - - /** - * @deprecated This no longer does anything. - */ - sessionFlushingDelayMS?: number; -}; +export type SentryHttpInstrumentationOptions = OutgoingHttpRequestInstrumentationOptions; /** * This instruments the http modules for outgoing requests. * It uses the diagnostics channel if available, otherwise it falls back to monkey-patching. * * The instrumentation will start spans, create breadcrumbs, and propagate trace headers in outgoing requests (depending on the settings). - * - * @TODO Cleanup options in v11 */ export function instrumentHttpOutgoingRequests( instrumentationOptions: OutgoingHttpRequestInstrumentationOptions = {}, ): void { const { outgoingRequestApplyCustomAttributes: applyCustomAttributesOnSpan, ...options } = instrumentationOptions; const patchOptions = { - propagateTrace: options.propagateTraceInOutgoingRequests ?? true, applyCustomAttributesOnSpan, ...options, - // oxlint-disable-next-line typescript/no-deprecated - spans: options.createSpansForOutgoingRequests !== false && (options.spans ?? true), + propagateTrace: options.propagateTrace ?? true, + spans: options.spans ?? true, ignoreOutgoingRequests(url, request) { return isTracingSuppressed() || !!options.ignoreOutgoingRequests?.(url, getRequestOptions(request)); }, diff --git a/packages/node/src/integrations/http/httpServerIntegration.ts b/packages/node/src/integrations/http/httpServerIntegration.ts index 21e697a7bdf0..3d5610a5cfa4 100644 --- a/packages/node/src/integrations/http/httpServerIntegration.ts +++ b/packages/node/src/integrations/http/httpServerIntegration.ts @@ -39,7 +39,7 @@ export interface HttpServerIntegrationOptions { sessions?: boolean; /** - * Number of milliseconds until sessions tracked with `trackIncomingRequestsAsSessions` will be flushed as a session aggregate. + * Number of milliseconds until sessions tracked with `sessions` will be flushed as a session aggregate. * * Defaults to `60000` (60s). */ diff --git a/packages/node/src/integrations/http/index.ts b/packages/node/src/integrations/http/index.ts index 36b71950c721..747826ff0472 100644 --- a/packages/node/src/integrations/http/index.ts +++ b/packages/node/src/integrations/http/index.ts @@ -1,5 +1,5 @@ import type { RequestOptions } from 'node:http'; -import type { HttpClientRequest, HttpIncomingMessage, HttpServerResponse, Span } from '@sentry/core'; +import type { HttpClientRequest, Span } from '@sentry/core'; import { URL_FULL } from '@sentry/conventions/attributes'; import { defineIntegration, @@ -18,9 +18,7 @@ import { instrumentHttpOutgoingRequests } from './SentryHttpInstrumentation'; const INTEGRATION_NAME = 'Http' as const; -// TODO(v11): Consolidate all the various HTTP integration options into one, -// and deprecate the duplicated and aliased options. -interface HttpOptions { +interface HttpOptions extends HttpServerIntegrationOptions, HttpServerSpansIntegrationOptions { /** * Whether breadcrumbs should be recorded for outgoing requests. * Defaults to true @@ -36,21 +34,6 @@ interface HttpOptions { */ spans?: boolean; - /** - * Whether the integration should create [Sessions](https://docs.sentry.io/product/releases/health/#sessions) for incoming requests to track the health and crash-free rate of your releases in Sentry. - * Read more about Release Health: https://docs.sentry.io/product/releases/health/ - * - * Defaults to `true`. - */ - trackIncomingRequestsAsSessions?: boolean; - - /** - * Number of milliseconds until sessions tracked with `trackIncomingRequestsAsSessions` will be flushed as a session aggregate. - * - * Defaults to `60000` (60s). - */ - sessionFlushingDelayMS?: number; - /** * Whether to inject trace propagation headers (sentry-trace, baggage, traceparent) into outgoing HTTP requests. * @@ -75,84 +58,28 @@ interface HttpOptions { ignoreOutgoingRequests?: (url: string, request: RequestOptions) => boolean; /** - * Do not capture spans for incoming HTTP requests to URLs where the given callback returns `true`. - * Spans will be non recording if tracing is disabled. - * - * The `urlPath` param consists of the URL path and query string (if any) of the incoming request. - * For example: `'/users/details?id=123'` - * - * The `request` param contains the original {@type IncomingMessage} object of the incoming request. - * You can use it to filter on additional properties like method, headers, etc. - */ - ignoreIncomingRequests?: (urlPath: string, request: HttpIncomingMessage) => boolean; - - /** - * A hook that can be used to mutate the span for incoming requests. - * This is triggered after the span is created, but before it is recorded. - */ - incomingRequestSpanHook?: (span: Span, request: HttpIncomingMessage, response: HttpServerResponse) => void; - - /** - * Whether to automatically ignore common static asset requests like favicon.ico, robots.txt, etc. - * This helps reduce noise in your transactions. - * - * @default `true` - */ - ignoreStaticAssets?: boolean; - - /** - * Do not capture spans for incoming HTTP requests with the given status codes. - * By default, spans with some 3xx and 4xx status codes are ignored (see @default). - * Expects an array of status codes or a range of status codes, e.g. [[300,399], 404] would ignore 3xx and 404 status codes. - * - * @default `[[401, 404], [301, 303], [305, 399]]` - */ - dropSpansForIncomingRequestStatusCodes?: (number | [number, number])[]; - - /** - * Do not capture the request body for incoming HTTP requests to URLs where the given callback returns `true`. - * This can be useful for long running requests where the body is not needed and we want to avoid capturing it. - * - * @param url Contains the entire URL, including query string (if any), protocol, host, etc. of the incoming request. - * @param request Contains the {@type RequestOptions} object used to make the incoming request. + * If true, do not generate spans for incoming requests at all. + * This is used by Remix to avoid generating spans for incoming requests, as it generates its own spans. */ - ignoreIncomingRequestBody?: (url: string, request: RequestOptions) => boolean; + disableIncomingRequestSpans?: boolean; /** - * Controls the maximum size of incoming HTTP request bodies attached to events. - * - * Available options: - * - 'none': No request bodies will be attached - * - 'small': Request bodies up to 1,000 bytes will be attached - * - 'medium': Request bodies up to 10,000 bytes will be attached (default) - * - 'always': Request bodies will always be attached - * - * Note that even with 'always' setting, bodies exceeding 1MB will never be attached - * for performance and security reasons. - * - * @default 'medium' + * Called after an outgoing request span is created. + * Only invoked when spans are created for outgoing requests. */ - maxIncomingRequestBodySize?: 'none' | 'small' | 'medium' | 'always'; + outgoingRequestHook?: SentryHttpInstrumentationOptions['outgoingRequestHook']; /** - * If true, do not generate spans for incoming requests at all. - * This is used by Remix to avoid generating spans for incoming requests, as it generates its own spans. + * Called when the outgoing request receives a response. + * Only invoked when spans are created for outgoing requests. */ - disableIncomingRequestSpans?: boolean; + outgoingResponseHook?: SentryHttpInstrumentationOptions['outgoingResponseHook']; /** - * Hooks for outgoing HTTP request spans. - * These no longer run for incoming request spans; use `incomingRequestSpanHook` for those. + * Called when both the outgoing request and response are available. + * Only invoked when spans are created for outgoing requests. */ - instrumentation?: { - requestHook?: (span: Span, req: HttpIncomingMessage | HttpClientRequest) => void; - responseHook?: (span: Span, response: HttpIncomingMessage | HttpServerResponse) => void; - applyCustomAttributesOnSpan?: ( - span: Span, - request: HttpIncomingMessage | HttpClientRequest, - response: HttpIncomingMessage | HttpServerResponse, - ) => void; - }; + outgoingRequestApplyCustomAttributes?: SentryHttpInstrumentationOptions['outgoingRequestApplyCustomAttributes']; } /** @@ -161,25 +88,10 @@ interface HttpOptions { */ export const httpIntegration = defineIntegration((options: HttpOptions = {}) => { const spans = options.spans ?? true; - const disableIncomingRequestSpans = options.disableIncomingRequestSpans; - const enableServerSpans = spans && !disableIncomingRequestSpans; - - const serverOptions = { - sessions: options.trackIncomingRequestsAsSessions, - sessionFlushingDelayMS: options.sessionFlushingDelayMS, - ignoreRequestBody: options.ignoreIncomingRequestBody, - maxRequestBodySize: options.maxIncomingRequestBodySize, - } satisfies HttpServerIntegrationOptions; - - const serverSpansOptions: HttpServerSpansIntegrationOptions = { - ignoreIncomingRequests: options.ignoreIncomingRequests, - ignoreStaticAssets: options.ignoreStaticAssets, - ignoreStatusCodes: options.dropSpansForIncomingRequestStatusCodes, - onSpanCreated: options.incomingRequestSpanHook, - }; + const enableServerSpans = spans && !options.disableIncomingRequestSpans; - const server = httpServerIntegration(serverOptions); - const serverSpans = httpServerSpansIntegration(serverSpansOptions); + const server = httpServerIntegration(options); + const serverSpans = httpServerSpansIntegration(options); return { name: INTEGRATION_NAME, @@ -196,9 +108,7 @@ export const httpIntegration = defineIntegration((options: HttpOptions = {}) => const sentryHttpInstrumentationOptions: SentryHttpInstrumentationOptions = { breadcrumbs: options.breadcrumbs, spans, - propagateTraceInOutgoingRequests: options.tracePropagation ?? true, - // oxlint-disable-next-line typescript/no-deprecated -- deprecated alias kept until removal - createSpansForOutgoingRequests: spans, + propagateTrace: options.tracePropagation ?? true, ignoreOutgoingRequests: options.ignoreOutgoingRequests, outgoingRequestHook: (span: Span, request: HttpClientRequest) => { // Sanitize data URLs to prevent long base64 strings in span attributes @@ -211,10 +121,10 @@ export const httpIntegration = defineIntegration((options: HttpOptions = {}) => [URL_FULL]: sanitizedUrl, }); } - options.instrumentation?.requestHook?.(span, request); + options.outgoingRequestHook?.(span, request); }, - outgoingResponseHook: options.instrumentation?.responseHook, - outgoingRequestApplyCustomAttributes: options.instrumentation?.applyCustomAttributesOnSpan, + outgoingResponseHook: options.outgoingResponseHook, + outgoingRequestApplyCustomAttributes: options.outgoingRequestApplyCustomAttributes, }; // This is Sentry-specific instrumentation for outgoing request diff --git a/packages/nuxt/src/server/sdk.ts b/packages/nuxt/src/server/sdk.ts index 7ff099f291e9..fa0d5d45a5f4 100644 --- a/packages/nuxt/src/server/sdk.ts +++ b/packages/nuxt/src/server/sdk.ts @@ -106,13 +106,16 @@ function getNuxtDefaultIntegrations(options: NodeOptions): Integration[] { ...getDefaultNodeIntegrations(options).filter(integration => integration.name !== 'Http'), // The httpIntegration is added as defaultIntegration, so users can still overwrite it httpIntegration({ - incomingRequestSpanHook: () => { + onSpanCreated: () => { // Flush eagerly on serverless platforms, where the function may be frozen before the transport // sends, handing the flush to a platform `waitUntil` where one exists so it doesn't block. On a // long-running server this is a no-op, so pending outcomes keep aggregating on the flush interval // instead of shipping one client_report envelope per response. void flushIfServerless(); }, + outgoingResponseHook: () => { + void flushIfServerless(); + }, }), ]; } From 750ab8cda98f83790ce52d09472d79c049bb9169 Mon Sep 17 00:00:00 2001 From: RulaKhaled Date: Mon, 17 Aug 2026 20:21:51 +0200 Subject: [PATCH 2/8] fix(nuxt): Do not flush on every outgoing HTTP response Serverless freeze happens at the end of the incoming request, which onSpanCreated and Nitro's event-handler patch already cover. Flushing again on each outbound response starts extra 2s flushes on platforms without waitUntil. --- packages/nuxt/src/server/sdk.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/nuxt/src/server/sdk.ts b/packages/nuxt/src/server/sdk.ts index fa0d5d45a5f4..656266748858 100644 --- a/packages/nuxt/src/server/sdk.ts +++ b/packages/nuxt/src/server/sdk.ts @@ -113,9 +113,6 @@ function getNuxtDefaultIntegrations(options: NodeOptions): Integration[] { // instead of shipping one client_report envelope per response. void flushIfServerless(); }, - outgoingResponseHook: () => { - void flushIfServerless(); - }, }), ]; } From 3ea615bdb999c1f12e6e60f8dfff461586356f3c Mon Sep 17 00:00:00 2001 From: RulaKhaled Date: Wed, 19 Aug 2026 10:43:22 +0200 Subject: [PATCH 3/8] ref(nuxt): Drop redundant httpIntegration flush override patchEventHandler already flushes after the Nitro handler. The onSpanCreated hook ran at span start, before the request had events to send. Co-Authored-By: Cursor Grok 4.6 --- packages/nuxt/src/server/sdk.ts | 36 ++++----------------------------- 1 file changed, 4 insertions(+), 32 deletions(-) diff --git a/packages/nuxt/src/server/sdk.ts b/packages/nuxt/src/server/sdk.ts index 656266748858..9a92712c0ca8 100644 --- a/packages/nuxt/src/server/sdk.ts +++ b/packages/nuxt/src/server/sdk.ts @@ -1,19 +1,7 @@ import * as path from 'node:path'; -import type { Client, Event, EventProcessor, Integration } from '@sentry/core'; -import { - applySdkMetadata, - debug, - DEFAULT_ENVIRONMENT, - DEV_ENVIRONMENT, - flushIfServerless, - getGlobalScope, -} from '@sentry/core'; -import { - getDefaultIntegrations as getDefaultNodeIntegrations, - httpIntegration, - init as initNode, - type NodeOptions, -} from '@sentry/node'; +import type { Client, Event, EventProcessor } from '@sentry/core'; +import { applySdkMetadata, debug, DEFAULT_ENVIRONMENT, DEV_ENVIRONMENT, getGlobalScope } from '@sentry/core'; +import { getDefaultIntegrations as getDefaultNodeIntegrations, init as initNode } from '@sentry/node'; import { DEBUG_BUILD } from '../common/debug-build'; import type { SentryNuxtServerOptions } from '../common/types'; @@ -34,7 +22,7 @@ export function init(options: SentryNuxtServerOptions): Client | undefined { const sentryOptions = { environment: options.environment ?? process.env.SENTRY_ENVIRONMENT ?? envFallback, - defaultIntegrations: getNuxtDefaultIntegrations(options), + defaultIntegrations: getDefaultNodeIntegrations(options), ...options, }; @@ -101,22 +89,6 @@ export function clientSourceMapErrorFilter(options: SentryNuxtServerOptions): Ev ); } -function getNuxtDefaultIntegrations(options: NodeOptions): Integration[] { - return [ - ...getDefaultNodeIntegrations(options).filter(integration => integration.name !== 'Http'), - // The httpIntegration is added as defaultIntegration, so users can still overwrite it - httpIntegration({ - onSpanCreated: () => { - // Flush eagerly on serverless platforms, where the function may be frozen before the transport - // sends, handing the flush to a platform `waitUntil` where one exists so it doesn't block. On a - // long-running server this is a no-op, so pending outcomes keep aggregating on the flush interval - // instead of shipping one client_report envelope per response. - void flushIfServerless(); - }, - }), - ]; -} - /** * Checks if the event is a cache event. */ From 396297e0903402f6fd6032772ea6d20644ba0f91 Mon Sep 17 00:00:00 2001 From: RulaKhaled Date: Thu, 20 Aug 2026 14:53:13 +0200 Subject: [PATCH 4/8] ref(nuxt): Let initNode resolve default integrations Precomputing them here pinned the selection to the raw options, before `initNode` resolves `SENTRY_TRACES_SAMPLE_RATE`. Anyone enabling tracing purely through the environment got `hasSpansEnabled() === false` at this call site and lost every performance integration, while the channel injection inside `initNode` was still gated on the resolved value. Now that the Http override is gone there is nothing left to customize, so the whole array can be left to `initNode`. --- packages/nuxt/src/server/sdk.ts | 3 +-- packages/nuxt/test/server/sdk.test.ts | 10 +++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/nuxt/src/server/sdk.ts b/packages/nuxt/src/server/sdk.ts index 9a92712c0ca8..90f3972c8a01 100644 --- a/packages/nuxt/src/server/sdk.ts +++ b/packages/nuxt/src/server/sdk.ts @@ -1,7 +1,7 @@ import * as path from 'node:path'; import type { Client, Event, EventProcessor } from '@sentry/core'; import { applySdkMetadata, debug, DEFAULT_ENVIRONMENT, DEV_ENVIRONMENT, getGlobalScope } from '@sentry/core'; -import { getDefaultIntegrations as getDefaultNodeIntegrations, init as initNode } from '@sentry/node'; +import { init as initNode } from '@sentry/node'; import { DEBUG_BUILD } from '../common/debug-build'; import type { SentryNuxtServerOptions } from '../common/types'; @@ -22,7 +22,6 @@ export function init(options: SentryNuxtServerOptions): Client | undefined { const sentryOptions = { environment: options.environment ?? process.env.SENTRY_ENVIRONMENT ?? envFallback, - defaultIntegrations: getDefaultNodeIntegrations(options), ...options, }; diff --git a/packages/nuxt/test/server/sdk.test.ts b/packages/nuxt/test/server/sdk.test.ts index a83a857dbfe2..c812cc1fe441 100644 --- a/packages/nuxt/test/server/sdk.test.ts +++ b/packages/nuxt/test/server/sdk.test.ts @@ -41,14 +41,14 @@ describe('Nuxt Server SDK', () => { expect(init({})).not.toBeUndefined(); }); - it('uses default integrations when not provided in options', () => { + it('delegates default integrations to initNode when not provided in options', () => { + // Resolving them here would pin the selection to the raw options, before `initNode` + // resolves `SENTRY_TRACES_SAMPLE_RATE`, and would drop the performance integrations + // for anyone enabling tracing purely through the environment. init({ dsn: 'https://public@dsn.ingest.sentry.io/1337' }); expect(nodeInit).toHaveBeenCalledTimes(1); - const callArgs = nodeInit.mock.calls[0]?.[0]; - expect(callArgs).toBeDefined(); - expect(callArgs?.defaultIntegrations).toBeDefined(); - expect(Array.isArray(callArgs?.defaultIntegrations)).toBe(true); + expect(nodeInit).toHaveBeenCalledWith(expect.not.objectContaining({ defaultIntegrations: expect.anything() })); }); it('allows options.defaultIntegrations to override default integrations', () => { From f36f3bcd6ba734950a11dd2c53b11853bbd49c69 Mon Sep 17 00:00:00 2001 From: RulaKhaled Date: Thu, 20 Aug 2026 15:16:07 +0200 Subject: [PATCH 5/8] docs(migration): Correct outgoing hook attribution for httpIntegration `httpServerSpansIntegration` only instruments incoming requests, so pointing its migration note at `outgoingRequestHook` and friends sent readers looking for options that integration does not have. Those live on `httpIntegration`. Also drops the before/after block under the consolidation table. It restated every row it sat under, and the sibling "Removed option" table for `@sentry/nextjs` carries no example either. --- docs/migration/v11-end-state.md | 36 ++------------------------------- 1 file changed, 2 insertions(+), 34 deletions(-) diff --git a/docs/migration/v11-end-state.md b/docs/migration/v11-end-state.md index 06e19049e21d..ecce49d78f4d 100644 --- a/docs/migration/v11-end-state.md +++ b/docs/migration/v11-end-state.md @@ -520,7 +520,7 @@ Two consequences to be aware of when upgrading: Affected SDKs: `@sentry/node` and dependents. -The deprecated `httpIntegration` / `httpServerSpansIntegration` hooks `instrumentation.requestHook`, `instrumentation.responseHook`, and `instrumentation.applyCustomAttributesOnSpan` no longer run for incoming request spans. Use `onSpanCreated` instead. Outgoing request spans use `outgoingRequestHook`, `outgoingResponseHook`, and `outgoingRequestApplyCustomAttributes`. +The deprecated `httpIntegration` / `httpServerSpansIntegration` hooks `instrumentation.requestHook`, `instrumentation.responseHook`, and `instrumentation.applyCustomAttributesOnSpan` no longer run for incoming request spans. Use `onSpanCreated` instead. For outgoing request spans, `httpIntegration` has `outgoingRequestHook`, `outgoingResponseHook`, and `outgoingRequestApplyCustomAttributes`. ```js // before @@ -841,7 +841,7 @@ Sentry.init({ - (Express) The deprecated `patchExpressModule(options)` signature was removed. Use `patchExpressModule(moduleExports, getOptions)` instead. - The `@sentry/node-core/light/otlp` entry point was removed, along with its optional `@opentelemetry/exporter-trace-otlp-http` peer dependency. `otlpIntegration` is now exported directly from every server-side SDK, so `Sentry.otlpIntegration()` needs no extra import or install. - The `otlpIntegration` options `setupOtlpTracesExporter` and `collectorUrl` were removed, and the integration no longer sets up a span exporter, span processor, or tracer provider. Configure your own exporter and point it at `Sentry.getOtlpTracesEndpoint(dsn)`, or at your collector's URL if you route through one. See [Connecting Sentry to your OpenTelemetry traces](#connecting-sentry-to-your-opentelemetry-traces). -- The deprecated `httpServerSpansIntegration` `instrumentation.{requestHook,responseHook,applyCustomAttributesOnSpan}` option was removed. Use `onSpanCreated` to mutate incoming request spans, or `outgoingRequestHook` / `outgoingResponseHook` / `outgoingRequestApplyCustomAttributes` for outgoing request spans. +- The deprecated `httpServerSpansIntegration` `instrumentation.{requestHook,responseHook,applyCustomAttributesOnSpan}` option was removed. Use `onSpanCreated` instead. `httpServerSpansIntegration` only covers incoming requests; the outgoing hooks (`outgoingRequestHook`, `outgoingResponseHook`, `outgoingRequestApplyCustomAttributes`) are on `httpIntegration`. #### `httpIntegration` options were consolidated @@ -858,38 +858,6 @@ Sentry.init({ | `instrumentation.responseHook` | `onSpanCreated` (incoming) or `outgoingResponseHook` (outgoing) | | `instrumentation.applyCustomAttributesOnSpan` | `onSpanCreated` (incoming) or `outgoingRequestApplyCustomAttributes` (outgoing) | -```js -// before -Sentry.httpIntegration({ - trackIncomingRequestsAsSessions: false, - maxIncomingRequestBodySize: 'small', - ignoreIncomingRequestBody: url => url.includes('/health'), - dropSpansForIncomingRequestStatusCodes: [404], - incomingRequestSpanHook: (span, req, res) => { - span.setAttribute('custom', true); - }, - instrumentation: { - responseHook: () => { - void flushIfServerless(); - }, - }, -}); - -// after -Sentry.httpIntegration({ - sessions: false, - maxRequestBodySize: 'small', - ignoreRequestBody: url => url.includes('/health'), - ignoreStatusCodes: [404], - onSpanCreated: (span, req, res) => { - span.setAttribute('custom', true); - }, - outgoingResponseHook: () => { - void flushIfServerless(); - }, -}); -``` - ### `@sentry/cloudflare` - The `@sentry/cloudflare/nodejs_compat` subpath export was removed. Since `nodejs_compat` is now required for all users, the main `@sentry/cloudflare` entry point includes everything that was previously only available via the subpath. From 2f9e859745085ae5cdd7be5248e14c62c250fb92 Mon Sep 17 00:00:00 2001 From: RulaKhaled Date: Thu, 20 Aug 2026 15:38:45 +0200 Subject: [PATCH 6/8] docs(migration): Show both replacements for the incoming span hook sample `instrumentation.requestHook` ran for incoming and outgoing spans in v10, so a reader migrating an outgoing hook would copy the sample onto `onSpanCreated` and get a hook that never fires for client spans. Restores the consolidation before/after block dropped earlier in this branch. --- docs/migration/v11-end-state.md | 43 ++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/docs/migration/v11-end-state.md b/docs/migration/v11-end-state.md index ecce49d78f4d..299a2d084422 100644 --- a/docs/migration/v11-end-state.md +++ b/docs/migration/v11-end-state.md @@ -522,6 +522,8 @@ Affected SDKs: `@sentry/node` and dependents. The deprecated `httpIntegration` / `httpServerSpansIntegration` hooks `instrumentation.requestHook`, `instrumentation.responseHook`, and `instrumentation.applyCustomAttributesOnSpan` no longer run for incoming request spans. Use `onSpanCreated` instead. For outgoing request spans, `httpIntegration` has `outgoingRequestHook`, `outgoingResponseHook`, and `outgoingRequestApplyCustomAttributes`. +In v10 these hooks ran for both directions, so which replacement you want depends on which spans your hook was mutating: + ```js // before Sentry.httpIntegration({ @@ -532,12 +534,19 @@ Sentry.httpIntegration({ }, }); -// after +// after — incoming (server) spans Sentry.httpIntegration({ onSpanCreated: (span, req, res) => { span.setAttribute('custom', true); }, }); + +// after — outgoing (client) spans +Sentry.httpIntegration({ + outgoingRequestHook: (span, req) => { + span.setAttribute('custom', true); + }, +}); ``` ### Node HTTP transport `keepAlive` defaults to `true` @@ -858,6 +867,38 @@ Sentry.init({ | `instrumentation.responseHook` | `onSpanCreated` (incoming) or `outgoingResponseHook` (outgoing) | | `instrumentation.applyCustomAttributesOnSpan` | `onSpanCreated` (incoming) or `outgoingRequestApplyCustomAttributes` (outgoing) | +```js +// before +Sentry.httpIntegration({ + trackIncomingRequestsAsSessions: false, + maxIncomingRequestBodySize: 'small', + ignoreIncomingRequestBody: url => url.includes('/health'), + dropSpansForIncomingRequestStatusCodes: [404], + incomingRequestSpanHook: (span, req, res) => { + span.setAttribute('custom', true); + }, + instrumentation: { + responseHook: () => { + void flushIfServerless(); + }, + }, +}); + +// after +Sentry.httpIntegration({ + sessions: false, + maxRequestBodySize: 'small', + ignoreRequestBody: url => url.includes('/health'), + ignoreStatusCodes: [404], + onSpanCreated: (span, req, res) => { + span.setAttribute('custom', true); + }, + outgoingResponseHook: () => { + void flushIfServerless(); + }, +}); +``` + ### `@sentry/cloudflare` - The `@sentry/cloudflare/nodejs_compat` subpath export was removed. Since `nodejs_compat` is now required for all users, the main `@sentry/cloudflare` entry point includes everything that was previously only available via the subpath. From b74c635ebf8f2e18e83992f4541403b5441dbae6 Mon Sep 17 00:00:00 2001 From: RulaKhaled Date: Thu, 20 Aug 2026 15:38:45 +0200 Subject: [PATCH 7/8] test(node): Cover httpIntegration outgoing request span hooks `outgoingRequestHook`, `outgoingResponseHook` and `outgoingRequestApplyCustomAttributes` had no coverage after #23396 dropped the incoming `instrumentation.*` assertions without replacing the outgoing side. Each hook derives its attribute from the objects it is handed, so a hook wired to the wrong span, request or response fails rather than passing silently. --- .../httpIntegration/server-outgoingHooks.js | 64 +++++++++++++++++++ .../suites/tracing/httpIntegration/test.ts | 27 ++++++++ 2 files changed, 91 insertions(+) create mode 100644 dev-packages/node-integration-tests/suites/tracing/httpIntegration/server-outgoingHooks.js diff --git a/dev-packages/node-integration-tests/suites/tracing/httpIntegration/server-outgoingHooks.js b/dev-packages/node-integration-tests/suites/tracing/httpIntegration/server-outgoingHooks.js new file mode 100644 index 000000000000..63d708ef7a75 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/httpIntegration/server-outgoingHooks.js @@ -0,0 +1,64 @@ +const { loggingTransport } = require('@sentry-internal/node-integration-tests'); +const Sentry = require('@sentry/node'); + +const url = process.env.SERVER_URL; + +Sentry.init({ + traceLifecycle: 'static', + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + transport: loggingTransport, + + integrations: [ + Sentry.httpIntegration({ + // Each hook derives its attribute from the objects it is handed, so a hook that fires with + // the wrong span, request or response fails the assertion rather than passing silently. + outgoingRequestHook: (span, request) => { + span.setAttribute('outgoingRequestHook', request.method); + }, + outgoingResponseHook: (span, response) => { + span.setAttribute('outgoingResponseHook', response.statusCode); + }, + outgoingRequestApplyCustomAttributes: (span, request, response) => { + span.setAttribute('outgoingRequestApplyCustomAttributes', `${request.method} ${response.statusCode}`); + }, + }), + ], +}); + +const http = require('http'); + +// 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('/testOutgoing', (_req, response) => { + makeHttpRequest(`${url}/api/users/42`).then(() => { + response.send({ response: 'done' }); + }); +}); + +Sentry.setupExpressErrorHandler(app); + +startExpressServerAndSendPortToRunner(app); + +function makeHttpRequest(url) { + return new Promise((resolve, reject) => { + http + .get(url, res => { + res.on('data', () => {}); + res.on('end', () => { + resolve(); + }); + }) + .on('error', error => { + reject(error); + }); + }); +} diff --git a/dev-packages/node-integration-tests/suites/tracing/httpIntegration/test.ts b/dev-packages/node-integration-tests/suites/tracing/httpIntegration/test.ts index a7579c7f9d07..74d99f0f9eb7 100644 --- a/dev-packages/node-integration-tests/suites/tracing/httpIntegration/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/httpIntegration/test.ts @@ -56,6 +56,33 @@ describe('httpIntegration', () => { }); }); + describe('outgoing request span hooks', () => { + test('runs outgoingRequestHook, outgoingResponseHook and outgoingRequestApplyCustomAttributes', async () => { + const [SERVER_URL, closeTestServer] = await createTestServer() + .get('/api/users/42', () => {}, 200) + .start(); + + const runner = createRunner(__dirname, 'server-outgoingHooks.js') + .withEnv({ SERVER_URL }) + .expect({ + transaction: event => { + const clientSpans = event.spans?.filter(span => span.op === 'http.client'); + expect(clientSpans).toHaveLength(1); + + // All three hooks run before the span ends, so every attribute has to survive to the envelope. + const data = clientSpans![0]?.data; + expect(data?.['outgoingRequestHook']).toBe('GET'); + expect(data?.['outgoingResponseHook']).toBe(200); + expect(data?.['outgoingRequestApplyCustomAttributes']).toBe('GET 200'); + }, + }) + .start(); + runner.makeRequest('get', '/testOutgoing'); + await runner.completed(); + closeTestServer(); + }); + }); + describe('http.server spans', () => { createEsmAndCjsTests(__dirname, 'server.mjs', 'instrument.mjs', (createRunner, test) => { test('captures correct attributes for GET requests', async () => { From a8d509033a6ed8bf243ba5d1a2894f3dad36570a Mon Sep 17 00:00:00 2001 From: RulaKhaled Date: Thu, 20 Aug 2026 18:34:38 +0200 Subject: [PATCH 8/8] ref(node): Drop the vestigial SentryHttpInstrumentationOptions alias MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It was an intersection of the outgoing options with nine deprecated no-ops. Removing those collapsed it into a bare alias of `OutgoingHttpRequestInstrumentationOptions`, leaving two names for one type — and the surviving name points at `SentryHttpInstrumentation`, an export that no longer exists. Neither name is re-exported from `@sentry/node`, so this is internal only. --- .../integrations/http/SentryHttpInstrumentation.ts | 4 +--- packages/node/src/integrations/http/index.ts | 12 ++++++------ 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/node/src/integrations/http/SentryHttpInstrumentation.ts b/packages/node/src/integrations/http/SentryHttpInstrumentation.ts index 3dee2b179969..597909b8d7db 100644 --- a/packages/node/src/integrations/http/SentryHttpInstrumentation.ts +++ b/packages/node/src/integrations/http/SentryHttpInstrumentation.ts @@ -18,7 +18,7 @@ const FULLY_SUPPORTS_HTTP_DIAGNOSTICS_CHANNEL = (NODE_VERSION.major === 23 && NODE_VERSION.minor >= 2) || NODE_VERSION.major >= 24; -interface OutgoingHttpRequestInstrumentationOptions { +export interface OutgoingHttpRequestInstrumentationOptions { /** * Whether breadcrumbs should be recorded for outgoing requests. * @@ -63,8 +63,6 @@ interface OutgoingHttpRequestInstrumentationOptions { ) => void; } -export type SentryHttpInstrumentationOptions = OutgoingHttpRequestInstrumentationOptions; - /** * This instruments the http modules for outgoing requests. * It uses the diagnostics channel if available, otherwise it falls back to monkey-patching. diff --git a/packages/node/src/integrations/http/index.ts b/packages/node/src/integrations/http/index.ts index 747826ff0472..ca720400dea3 100644 --- a/packages/node/src/integrations/http/index.ts +++ b/packages/node/src/integrations/http/index.ts @@ -13,7 +13,7 @@ import type { HttpServerIntegrationOptions } from './httpServerIntegration'; import { httpServerIntegration } from './httpServerIntegration'; import type { HttpServerSpansIntegrationOptions } from './httpServerSpansIntegration'; import { httpServerSpansIntegration } from './httpServerSpansIntegration'; -import type { SentryHttpInstrumentationOptions } from './SentryHttpInstrumentation'; +import type { OutgoingHttpRequestInstrumentationOptions } from './SentryHttpInstrumentation'; import { instrumentHttpOutgoingRequests } from './SentryHttpInstrumentation'; const INTEGRATION_NAME = 'Http' as const; @@ -67,19 +67,19 @@ interface HttpOptions extends HttpServerIntegrationOptions, HttpServerSpansInteg * Called after an outgoing request span is created. * Only invoked when spans are created for outgoing requests. */ - outgoingRequestHook?: SentryHttpInstrumentationOptions['outgoingRequestHook']; + outgoingRequestHook?: OutgoingHttpRequestInstrumentationOptions['outgoingRequestHook']; /** * Called when the outgoing request receives a response. * Only invoked when spans are created for outgoing requests. */ - outgoingResponseHook?: SentryHttpInstrumentationOptions['outgoingResponseHook']; + outgoingResponseHook?: OutgoingHttpRequestInstrumentationOptions['outgoingResponseHook']; /** * Called when both the outgoing request and response are available. * Only invoked when spans are created for outgoing requests. */ - outgoingRequestApplyCustomAttributes?: SentryHttpInstrumentationOptions['outgoingRequestApplyCustomAttributes']; + outgoingRequestApplyCustomAttributes?: OutgoingHttpRequestInstrumentationOptions['outgoingRequestApplyCustomAttributes']; } /** @@ -105,7 +105,7 @@ export const httpIntegration = defineIntegration((options: HttpOptions = {}) => setupOnce() { server.setupOnce(); - const sentryHttpInstrumentationOptions: SentryHttpInstrumentationOptions = { + const outgoingRequestOptions: OutgoingHttpRequestInstrumentationOptions = { breadcrumbs: options.breadcrumbs, spans, propagateTrace: options.tracePropagation ?? true, @@ -131,7 +131,7 @@ export const httpIntegration = defineIntegration((options: HttpOptions = {}) => // breadcrumbs & trace propagation. It uses the diagnostic channels on // node versions that support it, falling back to monkey-patching when // needed. - instrumentHttpOutgoingRequests(sentryHttpInstrumentationOptions); + instrumentHttpOutgoingRequests(outgoingRequestOptions); }, processEvent(event) { // Always run this, even if spans are disabled