Skip to content

Commit b26df86

Browse files
authored
feat(node-core,node): Add tracePropagation option to http and fetch integrations (#19712)
Add a `tracePropagation` option to `httpIntegration` and `nativeNodeFetchIntegration` that allows disabling Sentry's trace header injection (sentry-trace, baggage, traceparent) while still creating breadcrumbs. This is useful when `skipOpenTelemetrySetup: true` is configured and an external OTel setup handles trace propagation, avoiding duplicate headers. Closes: #19689
1 parent 7b69774 commit b26df86

File tree

19 files changed

+472
-7
lines changed

19 files changed

+472
-7
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import * as Sentry from '@sentry/node-core';
2+
import { loggingTransport } from '@sentry-internal/node-integration-tests';
3+
import { setupOtel } from '../../../../utils/setupOtel.js';
4+
5+
const client = Sentry.init({
6+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
7+
release: '1.0',
8+
integrations: [Sentry.nativeNodeFetchIntegration({ tracePropagation: false })],
9+
transport: loggingTransport,
10+
});
11+
12+
setupOtel(client);
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import * as Sentry from '@sentry/node-core';
2+
3+
async function run() {
4+
Sentry.addBreadcrumb({ message: 'manual breadcrumb' });
5+
6+
await fetch(`${process.env.SERVER_URL}/api/v0`).then(res => res.text());
7+
await fetch(`${process.env.SERVER_URL}/api/v1`).then(res => res.text());
8+
9+
Sentry.captureException(new Error('foo'));
10+
}
11+
12+
run();
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { createTestServer } from '@sentry-internal/test-utils';
2+
import { describe, expect } from 'vitest';
3+
import { createEsmAndCjsTests } from '../../../../utils/runner';
4+
5+
describe('outgoing fetch with tracePropagation disabled', () => {
6+
createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => {
7+
test('does not inject trace headers but still creates breadcrumbs', async () => {
8+
expect.assertions(5);
9+
10+
const [SERVER_URL, closeTestServer] = await createTestServer()
11+
.get('/api/v0', headers => {
12+
expect(headers['sentry-trace']).toBeUndefined();
13+
expect(headers['baggage']).toBeUndefined();
14+
})
15+
.get('/api/v1', headers => {
16+
expect(headers['sentry-trace']).toBeUndefined();
17+
expect(headers['baggage']).toBeUndefined();
18+
})
19+
.start();
20+
21+
await createRunner()
22+
.withEnv({ SERVER_URL })
23+
.expect({
24+
event: {
25+
breadcrumbs: [
26+
{
27+
message: 'manual breadcrumb',
28+
timestamp: expect.any(Number),
29+
},
30+
{
31+
category: 'http',
32+
data: {
33+
'http.method': 'GET',
34+
url: `${SERVER_URL}/api/v0`,
35+
status_code: 200,
36+
},
37+
timestamp: expect.any(Number),
38+
type: 'http',
39+
},
40+
{
41+
category: 'http',
42+
data: {
43+
'http.method': 'GET',
44+
url: `${SERVER_URL}/api/v1`,
45+
status_code: 200,
46+
},
47+
timestamp: expect.any(Number),
48+
type: 'http',
49+
},
50+
],
51+
exception: {
52+
values: [
53+
{
54+
type: 'Error',
55+
value: 'foo',
56+
},
57+
],
58+
},
59+
},
60+
})
61+
.start()
62+
.completed();
63+
closeTestServer();
64+
});
65+
});
66+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import * as Sentry from '@sentry/node-core';
2+
import { loggingTransport } from '@sentry-internal/node-integration-tests';
3+
import { setupOtel } from '../../../../utils/setupOtel.js';
4+
5+
const client = Sentry.init({
6+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
7+
release: '1.0',
8+
integrations: [Sentry.httpIntegration({ tracePropagation: false, spans: false })],
9+
transport: loggingTransport,
10+
});
11+
12+
setupOtel(client);
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import * as Sentry from '@sentry/node-core';
2+
import * as http from 'http';
3+
4+
async function run() {
5+
Sentry.addBreadcrumb({ message: 'manual breadcrumb' });
6+
7+
await makeHttpRequest(`${process.env.SERVER_URL}/api/v0`);
8+
await makeHttpRequest(`${process.env.SERVER_URL}/api/v1`);
9+
10+
Sentry.captureException(new Error('foo'));
11+
}
12+
13+
run();
14+
15+
function makeHttpRequest(url) {
16+
return new Promise(resolve => {
17+
http
18+
.request(url, httpRes => {
19+
httpRes.on('data', () => {
20+
// we don't care about data
21+
});
22+
httpRes.on('end', () => {
23+
resolve();
24+
});
25+
})
26+
.end();
27+
});
28+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { createTestServer } from '@sentry-internal/test-utils';
2+
import { describe, expect } from 'vitest';
3+
import { createEsmAndCjsTests } from '../../../../utils/runner';
4+
5+
describe('outgoing http with tracePropagation disabled', () => {
6+
createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => {
7+
test('does not inject trace headers but still creates breadcrumbs', async () => {
8+
expect.assertions(5);
9+
10+
const [SERVER_URL, closeTestServer] = await createTestServer()
11+
.get('/api/v0', headers => {
12+
expect(headers['sentry-trace']).toBeUndefined();
13+
expect(headers['baggage']).toBeUndefined();
14+
})
15+
.get('/api/v1', headers => {
16+
expect(headers['sentry-trace']).toBeUndefined();
17+
expect(headers['baggage']).toBeUndefined();
18+
})
19+
.start();
20+
21+
await createRunner()
22+
.withEnv({ SERVER_URL })
23+
.expect({
24+
event: {
25+
breadcrumbs: [
26+
{
27+
message: 'manual breadcrumb',
28+
timestamp: expect.any(Number),
29+
},
30+
{
31+
category: 'http',
32+
data: {
33+
'http.method': 'GET',
34+
url: `${SERVER_URL}/api/v0`,
35+
status_code: 200,
36+
},
37+
timestamp: expect.any(Number),
38+
type: 'http',
39+
},
40+
{
41+
category: 'http',
42+
data: {
43+
'http.method': 'GET',
44+
url: `${SERVER_URL}/api/v1`,
45+
status_code: 200,
46+
},
47+
timestamp: expect.any(Number),
48+
type: 'http',
49+
},
50+
],
51+
exception: {
52+
values: [
53+
{
54+
type: 'Error',
55+
value: 'foo',
56+
},
57+
],
58+
},
59+
},
60+
})
61+
.start()
62+
.completed();
63+
closeTestServer();
64+
});
65+
});
66+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
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+
integrations: [Sentry.nativeNodeFetchIntegration({ tracePropagation: false })],
8+
transport: loggingTransport,
9+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import * as Sentry from '@sentry/node';
2+
3+
async function run() {
4+
Sentry.addBreadcrumb({ message: 'manual breadcrumb' });
5+
6+
await fetch(`${process.env.SERVER_URL}/api/v0`).then(res => res.text());
7+
await fetch(`${process.env.SERVER_URL}/api/v1`).then(res => res.text());
8+
9+
Sentry.captureException(new Error('foo'));
10+
}
11+
12+
run();
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { createTestServer } from '@sentry-internal/test-utils';
2+
import { describe, expect } from 'vitest';
3+
import { createEsmAndCjsTests } from '../../../../utils/runner';
4+
5+
describe('outgoing fetch with tracePropagation disabled', () => {
6+
createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => {
7+
test('does not inject trace headers but still creates breadcrumbs', async () => {
8+
expect.assertions(5);
9+
10+
const [SERVER_URL, closeTestServer] = await createTestServer()
11+
.get('/api/v0', headers => {
12+
expect(headers['sentry-trace']).toBeUndefined();
13+
expect(headers['baggage']).toBeUndefined();
14+
})
15+
.get('/api/v1', headers => {
16+
expect(headers['sentry-trace']).toBeUndefined();
17+
expect(headers['baggage']).toBeUndefined();
18+
})
19+
.start();
20+
21+
await createRunner()
22+
.withEnv({ SERVER_URL })
23+
.expect({
24+
event: {
25+
breadcrumbs: [
26+
{
27+
message: 'manual breadcrumb',
28+
timestamp: expect.any(Number),
29+
},
30+
{
31+
category: 'http',
32+
data: {
33+
'http.method': 'GET',
34+
url: `${SERVER_URL}/api/v0`,
35+
status_code: 200,
36+
},
37+
timestamp: expect.any(Number),
38+
type: 'http',
39+
},
40+
{
41+
category: 'http',
42+
data: {
43+
'http.method': 'GET',
44+
url: `${SERVER_URL}/api/v1`,
45+
status_code: 200,
46+
},
47+
timestamp: expect.any(Number),
48+
type: 'http',
49+
},
50+
],
51+
exception: {
52+
values: [
53+
{
54+
type: 'Error',
55+
value: 'foo',
56+
},
57+
],
58+
},
59+
},
60+
})
61+
.start()
62+
.completed();
63+
closeTestServer();
64+
});
65+
});
66+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
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+
integrations: [Sentry.httpIntegration({ tracePropagation: false, spans: false })],
8+
transport: loggingTransport,
9+
});

0 commit comments

Comments
 (0)