Skip to content

Commit f75c3ed

Browse files
authored
feat(node): Pass requestHook and responseHook option to OTel (#17996)
This adds two new options into the `nativeNodeFetchIntegration` - the only thing it does is passing the two new options directly into the OTel instrumentation. Since this is OTel related, this is only accessible within the `node` SDK. The documentation will be then updated for the fetch integration ([it seems](https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/nodefetch/) that also the `spans` are missing) (closes #17953)
1 parent 39f85b3 commit f75c3ed

File tree

3 files changed

+87
-1
lines changed
  • dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-forward-request-hook
  • packages/node/src/integrations

3 files changed

+87
-1
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import * as Sentry from '@sentry/node';
2+
import { loggingTransport } from '@sentry-internal/node-integration-tests';
3+
4+
Sentry.init({
5+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
6+
release: '1.0',
7+
tracesSampleRate: 1.0,
8+
transport: loggingTransport,
9+
integrations: [
10+
Sentry.nativeNodeFetchIntegration({
11+
requestHook: (span, req) => {
12+
span.setAttribute('sentry.request.hook', req.path);
13+
},
14+
responseHook: (span, { response, request }) => {
15+
span.setAttribute('sentry.response.hook.path', request.path);
16+
span.setAttribute('sentry.response.hook.status_code', response.statusCode);
17+
},
18+
}),
19+
],
20+
});
21+
22+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
23+
Sentry.startSpan({ name: 'test_transaction' }, async () => {
24+
await fetch(`${process.env.SERVER_URL}/api/v0`);
25+
await fetch(`${process.env.SERVER_URL}/api/v1`);
26+
});
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { expect, test } from 'vitest';
2+
import { createRunner } from '../../../../utils/runner';
3+
import { createTestServer } from '../../../../utils/server';
4+
5+
test('adds requestHook and responseHook attributes to spans of outgoing fetch requests', async () => {
6+
expect.assertions(3);
7+
8+
const [SERVER_URL, closeTestServer] = await createTestServer()
9+
.get('/api/v0', () => {
10+
// Just ensure we're called
11+
expect(true).toBe(true);
12+
})
13+
.get(
14+
'/api/v1',
15+
() => {
16+
// Just ensure we're called
17+
expect(true).toBe(true);
18+
},
19+
404,
20+
)
21+
.start();
22+
23+
await createRunner(__dirname, 'scenario.ts')
24+
.withEnv({ SERVER_URL })
25+
.expect({
26+
transaction: {
27+
transaction: 'test_transaction',
28+
spans: [
29+
expect.objectContaining({
30+
description: expect.stringMatching(/GET .*\/api\/v0/),
31+
op: 'http.client',
32+
origin: 'auto.http.otel.node_fetch',
33+
status: 'ok',
34+
data: expect.objectContaining({
35+
'sentry.request.hook': '/api/v0',
36+
'sentry.response.hook.path': '/api/v0',
37+
'sentry.response.hook.status_code': 200,
38+
}),
39+
}),
40+
expect.objectContaining({
41+
description: expect.stringMatching(/GET .*\/api\/v1/),
42+
op: 'http.client',
43+
origin: 'auto.http.otel.node_fetch',
44+
status: 'not_found',
45+
data: expect.objectContaining({
46+
'sentry.request.hook': '/api/v1',
47+
'sentry.response.hook.path': '/api/v1',
48+
'sentry.response.hook.status_code': 404,
49+
'http.response.status_code': 404,
50+
}),
51+
}),
52+
],
53+
},
54+
})
55+
.start()
56+
.completed();
57+
closeTestServer();
58+
});

packages/node/src/integrations/node-fetch.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type { NodeClientOptions } from '../types';
88

99
const INTEGRATION_NAME = 'NodeFetch';
1010

11-
interface NodeFetchOptions {
11+
interface NodeFetchOptions extends Pick<UndiciInstrumentationConfig, 'requestHook' | 'responseHook'> {
1212
/**
1313
* Whether breadcrumbs should be recorded for requests.
1414
* Defaults to true
@@ -106,6 +106,8 @@ function getConfigWithDefaults(options: Partial<NodeFetchOptions> = {}): UndiciI
106106
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.otel.node_fetch',
107107
};
108108
},
109+
requestHook: options.requestHook,
110+
responseHook: options.responseHook,
109111
} satisfies UndiciInstrumentationConfig;
110112

111113
return instrumentationConfig;

0 commit comments

Comments
 (0)