Skip to content

Commit c120396

Browse files
committed
feat(node): Update httpIntegration handling of incoming requests
1 parent 6518d12 commit c120396

File tree

12 files changed

+420
-191
lines changed

12 files changed

+420
-191
lines changed

.size-limit.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ module.exports = [
224224
import: createImport('init'),
225225
ignore: [...builtinModules, ...nodePrefixedBuiltinModules],
226226
gzip: true,
227-
limit: '116 KB',
227+
limit: '51 KB',
228228
},
229229
// Node SDK (ESM)
230230
{
@@ -233,14 +233,14 @@ module.exports = [
233233
import: createImport('init'),
234234
ignore: [...builtinModules, ...nodePrefixedBuiltinModules],
235235
gzip: true,
236-
limit: '147 KB',
236+
limit: '150 KB',
237237
},
238238
{
239239
name: '@sentry/node - without tracing',
240240
path: 'packages/node/build/esm/index.js',
241241
import: createImport('initWithoutDefaultIntegrations', 'getDefaultIntegrationsWithoutPerformance'),
242242
gzip: true,
243-
limit: '110 KB',
243+
limit: '95 KB',
244244
ignore: [...builtinModules, ...nodePrefixedBuiltinModules],
245245
modifyWebpackConfig: function (config) {
246246
const webpack = require('webpack');
@@ -263,7 +263,7 @@ module.exports = [
263263
import: createImport('init'),
264264
ignore: [...builtinModules, ...nodePrefixedBuiltinModules],
265265
gzip: true,
266-
limit: '135 KB',
266+
limit: '105 KB',
267267
},
268268
];
269269

CHANGELOG.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44

55
- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott
66

7-
## Unreleased
8-
97
### Important Changes
108

119
- **fix(browser): Ensure IP address is only inferred by Relay if `sendDefaultPii` is `true`**
@@ -22,6 +20,13 @@ user IP addresses, if you set `sendDefaultPii: true` in your `Sentry.init` optio
2220

2321
We apologize for any inconvenience caused!
2422

23+
- **feat(node)**: Update `httpIntegration` handling of incoming requests
24+
25+
This version updates the handling of the Node SDK of incoming requests. Instead of relying on @opentelemetry/instrumentation-http for this, we now handle this internally, ensuring that we can optimize performance as much as possible and avoid interop problems.
26+
27+
This change should not affect users, unless they are relying on very in-depth implementation details. Importantly, this also drops the `_experimentalConfig` option of the integration - this will no longer do anything.
28+
Finally, you can still pass `instrumentation.{requestHook,responseHook,applyCustomAttributesOnSpan}` options, but they are deprecated and will be removed in v11. Instead, you can use the new `incomingRequestSpanHook` configuration option if you want to adjust the incoming request span.
29+
2530
## 10.3.0
2631

2732
- feat(core): MCP Server - Capture prompt results from prompt function calls (#17284)

dev-packages/node-integration-tests/suites/express/tracing/updateName/test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ describe('express tracing', () => {
2929

3030
// Also calling `updateName` AND setting a source doesn't change anything - Otel has no concept of source, this is sentry-internal.
3131
// Therefore, only the source is updated but the name is still overwritten by Otel.
32-
test("calling `span.updateName` and setting attribute source doesn't update the final name in express but it updates the source", async () => {
32+
test('calling `span.updateName` and setting attribute source updates the final name in express', async () => {
3333
const runner = createRunner(__dirname, 'server.js')
3434
.expect({
3535
transaction: {
36-
transaction: 'GET /test/:id/span-updateName-source',
36+
transaction: 'new-name',
3737
transaction_info: {
3838
source: 'custom',
3939
},

dev-packages/node-integration-tests/suites/tracing/httpIntegration/instrument-options.mjs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,20 @@ import { loggingTransport } from '@sentry-internal/node-integration-tests';
44
Sentry.init({
55
dsn: 'https://[email protected]/1337',
66
release: '1.0',
7-
// disable attaching headers to /test/* endpoints
8-
tracePropagationTargets: [/^(?!.*test).*$/],
97
tracesSampleRate: 1.0,
108
transport: loggingTransport,
119

1210
integrations: [
1311
Sentry.httpIntegration({
12+
incomingRequestSpanHook: (span, req, res) => {
13+
span.setAttribute('incomingRequestSpanHook', 'yes');
14+
Sentry.setExtra('incomingRequestSpanHookCalled', {
15+
reqUrl: req.url,
16+
reqMethod: req.method,
17+
resUrl: res.req.url,
18+
resMethod: res.req.method,
19+
});
20+
},
1421
instrumentation: {
1522
requestHook: (span, req) => {
1623
span.setAttribute('attr1', 'yes');

dev-packages/node-integration-tests/suites/tracing/httpIntegration/server-experimental.js

Lines changed: 0 additions & 38 deletions
This file was deleted.

dev-packages/node-integration-tests/suites/tracing/httpIntegration/test.ts

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,38 @@ describe('httpIntegration', () => {
5050
runner.makeRequest('get', '/test');
5151
await runner.completed();
5252
});
53+
54+
test('allows to configure incomingRequestSpanHook', async () => {
55+
const runner = createRunner()
56+
.expect({
57+
transaction: {
58+
contexts: {
59+
trace: {
60+
span_id: expect.stringMatching(/[a-f0-9]{16}/),
61+
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
62+
data: {
63+
url: expect.stringMatching(/\/test$/),
64+
'http.response.status_code': 200,
65+
incomingRequestSpanHook: 'yes',
66+
},
67+
op: 'http.server',
68+
status: 'ok',
69+
},
70+
},
71+
extra: expect.objectContaining({
72+
incomingRequestSpanHookCalled: {
73+
reqUrl: expect.stringMatching(/\/test$/),
74+
reqMethod: 'GET',
75+
resUrl: expect.stringMatching(/\/test$/),
76+
resMethod: 'GET',
77+
},
78+
}),
79+
},
80+
})
81+
.start();
82+
runner.makeRequest('get', '/test');
83+
await runner.completed();
84+
});
5385
});
5486
});
5587

@@ -138,30 +170,6 @@ describe('httpIntegration', () => {
138170
});
139171
});
140172

141-
test('allows to pass experimental config through to integration', async () => {
142-
const runner = createRunner(__dirname, 'server-experimental.js')
143-
.expect({
144-
transaction: {
145-
contexts: {
146-
trace: {
147-
span_id: expect.stringMatching(/[a-f0-9]{16}/),
148-
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
149-
data: {
150-
url: expect.stringMatching(/\/test$/),
151-
'http.response.status_code': 200,
152-
'http.server_name': 'sentry-test-server-name',
153-
},
154-
op: 'http.server',
155-
status: 'ok',
156-
},
157-
},
158-
},
159-
})
160-
.start();
161-
runner.makeRequest('get', '/test');
162-
await runner.completed();
163-
});
164-
165173
describe("doesn't create a root span for incoming requests ignored via `ignoreIncomingRequests`", () => {
166174
test('via the url param', async () => {
167175
const runner = createRunner(__dirname, 'server-ignoreIncomingRequests.js')

packages/node-core/src/integrations/http/SentryHttpInstrumentation.ts

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { ChannelListener } from 'node:diagnostics_channel';
22
import { subscribe, unsubscribe } from 'node:diagnostics_channel';
33
import type * as http from 'node:http';
44
import type * as https from 'node:https';
5+
import type { Span } from '@opentelemetry/api';
56
import { context } from '@opentelemetry/api';
67
import { isTracingSuppressed } from '@opentelemetry/core';
78
import type { InstrumentationConfig } from '@opentelemetry/instrumentation';
@@ -28,12 +29,22 @@ export type SentryHttpInstrumentationOptions = InstrumentationConfig & {
2829
*/
2930
breadcrumbs?: boolean;
3031

32+
/**
33+
* Whether to create spans for requests or not.
34+
* As of now, creates spans for incoming requests, but not outgoing requests.
35+
*
36+
* @default `true`
37+
*/
38+
spans?: boolean;
39+
3140
/**
3241
* Whether to extract the trace ID from the `sentry-trace` header for incoming requests.
3342
* By default this is done by the HttpInstrumentation, but if that is not added (e.g. because tracing is disabled, ...)
3443
* then this instrumentation can take over.
3544
*
36-
* @default `false`
45+
* @deprecated This is always true and the option will be removed in the future.
46+
*
47+
* @default `true`
3748
*/
3849
extractIncomingTraceFromHeader?: boolean;
3950

@@ -46,6 +57,20 @@ export type SentryHttpInstrumentationOptions = InstrumentationConfig & {
4657
*/
4758
propagateTraceInOutgoingRequests?: boolean;
4859

60+
/**
61+
* Whether to automatically ignore common static asset requests like favicon.ico, robots.txt, etc.
62+
* This helps reduce noise in your transactions.
63+
*
64+
* @default `true`
65+
*/
66+
ignoreStaticAssets?: boolean;
67+
68+
/**
69+
* If true, do not generate spans for incoming requests at all.
70+
* This is used by Remix to avoid generating spans for incoming requests, as it generates its own spans.
71+
*/
72+
disableIncomingRequestSpans?: boolean;
73+
4974
/**
5075
* Do not capture breadcrumbs for outgoing HTTP requests to URLs where the given callback returns `true`.
5176
* For the scope of this instrumentation, this callback only controls breadcrumb creation.
@@ -57,6 +82,14 @@ export type SentryHttpInstrumentationOptions = InstrumentationConfig & {
5782
*/
5883
ignoreOutgoingRequests?: (url: string, request: http.RequestOptions) => boolean;
5984

85+
/**
86+
* Do not capture spans for incoming HTTP requests to URLs where the given callback returns `true`.
87+
*
88+
* @param urlPath Contains the URL path and query string (if any) of the incoming request.
89+
* @param request Contains the {@type IncomingMessage} object of the incoming request.
90+
*/
91+
ignoreSpansForIncomingRequests?: (urlPath: string, request: http.IncomingMessage) => boolean;
92+
6093
/**
6194
* Do not capture the request body for incoming HTTP requests to URLs where the given callback returns `true`.
6295
* This can be useful for long running requests where the body is not needed and we want to avoid capturing it.
@@ -66,6 +99,12 @@ export type SentryHttpInstrumentationOptions = InstrumentationConfig & {
6699
*/
67100
ignoreIncomingRequestBody?: (url: string, request: http.RequestOptions) => boolean;
68101

102+
/**
103+
* A hook that can be used to mutate the span for incoming requests.
104+
* This is triggered after the span is created, but before it is recorded.
105+
*/
106+
incomingRequestSpanHook?: (span: Span, request: http.IncomingMessage, response: http.ServerResponse) => void;
107+
69108
/**
70109
* Controls the maximum size of incoming HTTP request bodies attached to events.
71110
*
@@ -90,6 +129,19 @@ export type SentryHttpInstrumentationOptions = InstrumentationConfig & {
90129
*/
91130
trackIncomingRequestsAsSessions?: boolean;
92131

132+
/**
133+
* @deprecated This is deprecated in favor of `incomingRequestSpanHook`.
134+
*/
135+
instrumentation?: {
136+
requestHook?: (span: Span, req: http.ClientRequest | http.IncomingMessage) => void;
137+
responseHook?: (span: Span, response: http.IncomingMessage | http.ServerResponse) => void;
138+
applyCustomAttributesOnSpan?: (
139+
span: Span,
140+
request: http.ClientRequest | http.IncomingMessage,
141+
response: http.IncomingMessage | http.ServerResponse,
142+
) => void;
143+
};
144+
93145
/**
94146
* Number of milliseconds until sessions tracked with `trackIncomingRequestsAsSessions` will be flushed as a session aggregate.
95147
*
@@ -128,13 +180,21 @@ export class SentryHttpInstrumentation extends InstrumentationBase<SentryHttpIns
128180
// but we only want to register them once, whichever is loaded first
129181
let hasRegisteredHandlers = false;
130182

183+
const spansEnabled = this.getConfig().spans ?? true;
184+
131185
const onHttpServerRequestStart = ((_data: unknown) => {
132186
const data = _data as { server: http.Server };
133187
instrumentServer(data.server, {
188+
// eslint-disable-next-line deprecation/deprecation
189+
instrumentation: this.getConfig().instrumentation,
134190
ignoreIncomingRequestBody: this.getConfig().ignoreIncomingRequestBody,
191+
ignoreSpansForIncomingRequests: this.getConfig().ignoreSpansForIncomingRequests,
192+
incomingRequestSpanHook: this.getConfig().incomingRequestSpanHook,
135193
maxIncomingRequestBodySize: this.getConfig().maxIncomingRequestBodySize,
136194
trackIncomingRequestsAsSessions: this.getConfig().trackIncomingRequestsAsSessions,
137195
sessionFlushingDelayMS: this.getConfig().sessionFlushingDelayMS ?? 60_000,
196+
ignoreStaticAssets: this.getConfig().ignoreStaticAssets,
197+
spans: spansEnabled && !this.getConfig().disableIncomingRequestSpans,
138198
});
139199
}) satisfies ChannelListener;
140200

0 commit comments

Comments
 (0)