Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,31 +19,6 @@ Sentry.init({
resMethod: res.req.method,
});
},
instrumentation: {
requestHook: (span, req) => {
span.setAttribute('attr1', 'yes');
Sentry.setExtra('requestHookCalled', {
url: req.url,
method: req.method,
});
},
responseHook: (span, res) => {
span.setAttribute('attr2', 'yes');
Sentry.setExtra('responseHookCalled', {
url: res.req.url,
method: res.req.method,
});
},
applyCustomAttributesOnSpan: (span, req, res) => {
span.setAttribute('attr3', 'yes');
Sentry.setExtra('applyCustomAttributesOnSpanCalled', {
reqUrl: req.url,
reqMethod: req.method,
resUrl: res.req.url,
resMethod: res.req.method,
});
},
},
}),
],
});
Original file line number Diff line number Diff line change
Expand Up @@ -22,48 +22,6 @@ describe('httpIntegration', () => {

describe('instrumentation options', () => {
createEsmAndCjsTests(__dirname, 'server.mjs', 'instrument-options.mjs', (createRunner, test) => {
test('allows to pass instrumentation options to integration', async () => {
const runner = createRunner()
.expect({
transaction: {
contexts: {
trace: {
span_id: expect.stringMatching(/[a-f\d]{16}/),
trace_id: expect.stringMatching(/[a-f\d]{32}/),
data: {
'url.full': expect.stringMatching(/\/test$/),
'http.response.status_code': 200,
attr1: 'yes',
attr2: 'yes',
attr3: 'yes',
},
op: 'http.server',
status: 'ok',
},
},
extra: {
requestHookCalled: {
url: expect.stringMatching(/\/test$/),
method: 'GET',
},
responseHookCalled: {
url: expect.stringMatching(/\/test$/),
method: 'GET',
},
applyCustomAttributesOnSpanCalled: {
reqUrl: expect.stringMatching(/\/test$/),
reqMethod: 'GET',
resUrl: expect.stringMatching(/\/test$/),
resMethod: 'GET',
},
},
},
})
.start();
runner.makeRequest('get', '/test');
await runner.completed();
});

test('allows to configure incomingRequestSpanHook', async () => {
const runner = createRunner()
.expect({
Expand Down
42 changes: 42 additions & 0 deletions docs/migration/v11-end-state.md
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,47 @@ 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`

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:

```js
// before
Sentry.httpIntegration({
instrumentation: {
requestHook: (span, req) => {
span.setAttribute('custom', true);
},
},
});

// after
Sentry.httpIntegration({
incomingRequestSpanHook: (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.

The Node HTTP transport now reuses sockets by default (`keepAlive: true`). The previous default of `false` existed because of a memory leak in Node 8, which is no longer relevant (minimum Node is 20.19.0). Idle sockets are still closed after 2 seconds. Pass `keepAlive: false` in transport options to restore the previous behavior:

```js
Sentry.init({
dsn: '__DSN__',
transportOptions: {
keepAlive: false,
},
});
```

### `tracePropagationTargets` matching is now case-insensitive

Affected SDKs: All SDKs.
Expand Down Expand Up @@ -796,6 +837,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`, or `httpIntegration({ incomingRequestSpanHook })`, to mutate incoming request spans.

### `@sentry/cloudflare`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,7 @@ export function instrumentHttpOutgoingRequests(
let _currentListener: ChannelListener | undefined;
function instrumentHttpOutgoingRequestsViaChannel(options: HttpInstrumentationOptions): void {
const { [HTTP_ON_CLIENT_REQUEST]: onHttpClientRequestCreated } = getHttpClientSubscriptions(options);
// If it was previously subscribed, first unsubscribe it
// TODO(v11): We can likely remove this when we drop preload support
// Replace a previous subscription so a later call does not stack duplicate listeners.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

context: #22260 said this was likely removable once @sentry/node/preload went away, but instrumentHttpOutgoingRequests() is still public and can be called more than once (last call wins). subscribe() stacks, so without unsubscribing first a second call would duplicate outgoing spans/breadcrumbs

if (_currentListener) {
unsubscribe(HTTP_ON_CLIENT_REQUEST, _currentListener);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import {
} from '@sentry/conventions/attributes';
import type {
Event,
HttpClientRequest,
HttpIncomingMessage,
HttpServerResponse,
Integration,
Expand Down Expand Up @@ -93,19 +92,6 @@ export interface HttpServerSpansIntegrationOptions {
*/
ignoreStatusCodes?: (number | [number, number])[];

/**
* @deprecated This is deprecated in favor of `incomingRequestSpanHook`.
*/
instrumentation?: {
requestHook?: (span: Span, req: HttpClientRequest | HttpIncomingMessage) => void;
responseHook?: (span: Span, response: HttpIncomingMessage | HttpServerResponse) => void;
applyCustomAttributesOnSpan?: (
span: Span,
request: HttpClientRequest | HttpIncomingMessage,
response: HttpIncomingMessage | HttpServerResponse,
) => void;
};

/**
* 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.
Expand All @@ -124,8 +110,6 @@ const _httpServerSpansIntegration = ((options: HttpServerSpansIntegrationOptions
];

const { onSpanCreated } = options;
// eslint-disable-next-line typescript/no-deprecated
const { requestHook, responseHook, applyCustomAttributesOnSpan } = options.instrumentation ?? {};

return {
name: INTEGRATION_NAME,
Expand Down Expand Up @@ -203,10 +187,6 @@ const _httpServerSpansIntegration = ((options: HttpServerSpansIntegrationOptions
},
});

// TODO v11: Remove the following three hooks, only onSpanCreated should remain
requestHook?.(span, request);
responseHook?.(span, response);
applyCustomAttributesOnSpan?.(span, request, response);
onSpanCreated?.(span, request, response);

return withActiveSpan(span, () => {
Expand Down
5 changes: 2 additions & 3 deletions packages/node/src/integrations/http/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,8 @@ interface HttpOptions {
disableIncomingRequestSpans?: boolean;

/**
* Additional instrumentation options that are passed to the underlying HttpInstrumentation.
* Hooks for outgoing HTTP request spans.
* These no longer run for incoming request spans; use `incomingRequestSpanHook` for those.
*/
instrumentation?: {
requestHook?: (span: Span, req: HttpIncomingMessage | HttpClientRequest) => void;
Expand Down Expand Up @@ -174,8 +175,6 @@ export const httpIntegration = defineIntegration((options: HttpOptions = {}) =>
ignoreIncomingRequests: options.ignoreIncomingRequests,
ignoreStaticAssets: options.ignoreStaticAssets,
ignoreStatusCodes: options.dropSpansForIncomingRequestStatusCodes,
// oxlint-disable-next-line typescript/no-deprecated -- pass through the deprecated option for back-compat
instrumentation: options.instrumentation,
onSpanCreated: options.incomingRequestSpanHook,
};

Expand Down
7 changes: 2 additions & 5 deletions packages/node/src/transports/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export interface NodeTransportOptions extends BaseTransportOptions {
caCerts?: string | Buffer | Array<string | Buffer>;
/** Custom HTTP module. Defaults to the native 'http' and 'https' modules. */
httpModule?: HTTPModule;
/** Allow overriding connection keepAlive, defaults to false */
/** Allow overriding connection keepAlive, defaults to true */
keepAlive?: boolean;
}

Expand Down Expand Up @@ -68,10 +68,7 @@ export function makeNodeTransport(options: NodeTransportOptions): Transport {
);

const nativeHttpModule = isHttps ? https : http;
const keepAlive = options.keepAlive === undefined ? false : options.keepAlive;

// TODO(v11): Evaluate if we can set keepAlive to true. This would involve testing for memory leaks in older node
// versions(>= 8) as they had memory leaks when using it: #2555
const keepAlive = options.keepAlive ?? true;
const agent = proxy
? (new HttpsProxyAgent(proxy) as http.Agent)
: new nativeHttpModule.Agent({ keepAlive, maxSockets: 30, timeout: 2000 });
Expand Down
19 changes: 17 additions & 2 deletions packages/node/test/transports/http.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
serializeEnvelope,
} from '@sentry/core';
import * as http from 'http';
import * as nodeHttp from 'node:http';
import { afterEach, describe, expect, it, type Mock, vi } from 'vitest';
import { createGunzip } from 'zlib';
import * as httpProxyAgent from '../../src/proxy';
Expand Down Expand Up @@ -117,7 +118,7 @@ describe('makeNewHttpTransport()', () => {
await transport.send(EVENT_ENVELOPE);
});

it('allows overriding keepAlive', async () => {
it('uses keepAlive by default', async () => {
await setupTestServer({ statusCode: SUCCESS }, req => {
expect(req.headers).toEqual(
expect.objectContaining({
Expand All @@ -127,10 +128,24 @@ describe('makeNewHttpTransport()', () => {
);
});

const transport = makeNodeTransport({ keepAlive: true, ...defaultOptions });
const transport = makeNodeTransport(defaultOptions);
await transport.send(EVENT_ENVELOPE);
});

it('allows disabling keepAlive', () => {
const AgentSpy = vi.spyOn(nodeHttp, 'Agent');

try {
makeNodeTransport({ keepAlive: false, ...defaultOptions });

expect(AgentSpy).toHaveBeenCalledWith(
expect.objectContaining({ keepAlive: false, maxSockets: 30, timeout: 2000 }),
);
} finally {
AgentSpy.mockRestore();
}
});

it('should correctly send user-provided headers to server', async () => {
await setupTestServer({ statusCode: SUCCESS }, req => {
expect(req.headers).toEqual(
Expand Down
14 changes: 6 additions & 8 deletions packages/nuxt/src/server/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,12 @@ 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({
instrumentation: {
responseHook: () => {
// 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();
},
incomingRequestSpanHook: () => {
// 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();
Comment thread
RulaKhaled marked this conversation as resolved.
},
}),
];
Expand Down
Loading