Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ test.describe('Lambda layer', () => {
expect.objectContaining({
data: expect.objectContaining({
'sentry.op': 'http.client',
'sentry.origin': 'auto.http.otel.http',
'sentry.origin': 'auto.http.client',
url: 'http://example.com/',
}),
description: 'GET http://example.com/',
Expand Down Expand Up @@ -113,7 +113,7 @@ test.describe('Lambda layer', () => {
expect.objectContaining({
data: expect.objectContaining({
'sentry.op': 'http.client',
'sentry.origin': 'auto.http.otel.http',
'sentry.origin': 'auto.http.client',
url: 'http://example.com/',
}),
description: 'GET http://example.com/',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ test.describe('NPM package', () => {
expect.objectContaining({
data: expect.objectContaining({
'sentry.op': 'http.client',
'sentry.origin': 'auto.http.otel.http',
'sentry.origin': 'auto.http.client',
url: 'http://example.com/',
}),
description: 'GET http://example.com/',
Expand Down Expand Up @@ -113,7 +113,7 @@ test.describe('NPM package', () => {
expect.objectContaining({
data: expect.objectContaining({
'sentry.op': 'http.client',
'sentry.origin': 'auto.http.otel.http',
'sentry.origin': 'auto.http.client',
url: 'http://example.com/',
}),
description: 'GET http://example.com/',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ test('Should send a transaction with a fetch span', async ({ page }) => {
data: expect.objectContaining({
'http.method': 'GET',
'sentry.op': 'http.client',
'sentry.origin': 'auto.http.otel.http',
'sentry.origin': 'auto.http.client',
}),
description: 'GET https://github.com/',
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ test.skip('Should send a transaction with a http span', async ({ request }) => {
data: expect.objectContaining({
'http.method': 'GET',
'sentry.op': 'http.client',
'sentry.origin': 'auto.http.otel.http',
'sentry.origin': 'auto.http.client',
}),
description: 'GET https://example.com/',
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ test.skip('Should send a transaction with a http span', async ({ request }) => {
data: expect.objectContaining({
'http.method': 'GET',
'sentry.op': 'http.client',
'sentry.origin': 'auto.http.otel.http',
'sentry.origin': 'auto.http.client',
}),
description: 'GET https://example.com/',
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ test('Should record spans from http instrumentation', async ({ request }) => {
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
data: expect.objectContaining({
'http.flavor': '1.1',
'http.host': 'example.com:80',
'http.host': 'example.com',
'http.method': 'GET',
'http.response.status_code': 200,
'http.status_code': 200,
Expand All @@ -146,7 +146,7 @@ test('Should record spans from http instrumentation', async ({ request }) => {
'net.transport': 'ip_tcp',
'otel.kind': 'CLIENT',
'sentry.op': 'http.client',
'sentry.origin': 'auto.http.otel.http',
'sentry.origin': 'auto.http.client',
url: 'http://example.com/',
}),
description: 'GET http://example.com/',
Expand All @@ -155,6 +155,6 @@ test('Should record spans from http instrumentation', async ({ request }) => {
timestamp: expect.any(Number),
status: 'ok',
op: 'http.client',
origin: 'auto.http.otel.http',
origin: 'auto.http.client',
});
});
Original file line number Diff line number Diff line change
@@ -1,202 +1,101 @@
import { createTestServer } from '@sentry-internal/test-utils';
import { describe, expect } from 'vitest';
import { conditionalTest } from '../../../../utils';
import { createEsmAndCjsTests } from '../../../../utils/runner';

describe('outgoing http requests with tracing & spans disabled', () => {
createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => {
conditionalTest({ min: 22 })('node >=22', () => {
test('outgoing http requests are correctly instrumented with tracing & spans disabled', async () => {
expect.assertions(11);
test('outgoing http requests are correctly instrumented with tracing & spans disabled', async () => {
expect.assertions(11);

const [SERVER_URL, closeTestServer] = await createTestServer()
.get('/api/v0', headers => {
expect(headers['sentry-trace']).toEqual(expect.stringMatching(/^([a-f\d]{32})-([a-f\d]{16})$/));
expect(headers['sentry-trace']).not.toEqual('00000000000000000000000000000000-0000000000000000');
expect(headers['baggage']).toEqual(expect.any(String));
})
.get('/api/v1', headers => {
expect(headers['sentry-trace']).toEqual(expect.stringMatching(/^([a-f\d]{32})-([a-f\d]{16})$/));
expect(headers['sentry-trace']).not.toEqual('00000000000000000000000000000000-0000000000000000');
expect(headers['baggage']).toEqual(expect.any(String));
})
.get('/api/v2', headers => {
expect(headers['baggage']).toBeUndefined();
expect(headers['sentry-trace']).toBeUndefined();
})
.get('/api/v3', headers => {
expect(headers['baggage']).toBeUndefined();
expect(headers['sentry-trace']).toBeUndefined();
})
.start();
const [SERVER_URL, closeTestServer] = await createTestServer()
.get('/api/v0', headers => {
expect(headers['sentry-trace']).toEqual(expect.stringMatching(/^([a-f\d]{32})-([a-f\d]{16})$/));
expect(headers['sentry-trace']).not.toEqual('00000000000000000000000000000000-0000000000000000');
expect(headers['baggage']).toEqual(expect.any(String));
})
.get('/api/v1', headers => {
expect(headers['sentry-trace']).toEqual(expect.stringMatching(/^([a-f\d]{32})-([a-f\d]{16})$/));
expect(headers['sentry-trace']).not.toEqual('00000000000000000000000000000000-0000000000000000');
expect(headers['baggage']).toEqual(expect.any(String));
})
.get('/api/v2', headers => {
expect(headers['baggage']).toBeUndefined();
expect(headers['sentry-trace']).toBeUndefined();
})
.get('/api/v3', headers => {
expect(headers['baggage']).toBeUndefined();
expect(headers['sentry-trace']).toBeUndefined();
})
.start();

await createRunner()
.withEnv({ SERVER_URL })
.expect({
event: {
exception: {
values: [
{
type: 'Error',
value: 'foo',
},
],
},
breadcrumbs: [
{
message: 'manual breadcrumb',
timestamp: expect.any(Number),
},
{
category: 'http',
data: {
'http.method': 'GET',
url: `${SERVER_URL}/api/v0`,
status_code: 200,
ADDED_PATH: '/api/v0',
},
timestamp: expect.any(Number),
type: 'http',
},
{
category: 'http',
data: {
'http.method': 'GET',
url: `${SERVER_URL}/api/v1`,
status_code: 200,
ADDED_PATH: '/api/v1',
},
timestamp: expect.any(Number),
type: 'http',
},
await createRunner()
.withEnv({ SERVER_URL })
.expect({
event: {
exception: {
values: [
{
category: 'http',
data: {
'http.method': 'GET',
url: `${SERVER_URL}/api/v2`,
status_code: 200,
ADDED_PATH: '/api/v2',
},
timestamp: expect.any(Number),
type: 'http',
},
{
category: 'http',
data: {
'http.method': 'GET',
url: `${SERVER_URL}/api/v3`,
status_code: 200,
ADDED_PATH: '/api/v3',
},
timestamp: expect.any(Number),
type: 'http',
type: 'Error',
value: 'foo',
},
],
},
})
.start()
.completed();

closeTestServer();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Light http integration loses trace propagation on Node <22

Medium Severity

The node-core light httpIntegration only injects sentry-trace / baggage via the http.client.request.created diagnostic channel, which does not exist before Node 22.12. The fallback added for older Nodes (http.client.request.start) only attaches breadcrumb listeners — it cannot inject headers because that channel fires after headers have been sent. The corresponding integration test was un-gated from conditionalTest({ min: 22 }) and now unconditionally asserts sentry-trace and baggage are set on /api/v0 and /api/v1, which will fail when the suite runs against Node <22.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5c11ce7. Configure here.

});
});

// On older node versions, outgoing requests do not get trace-headers injected, sadly
// This is because the necessary diagnostics channel hook is not available yet
conditionalTest({ max: 21 })('node <22', () => {
Comment thread
JPeer264 marked this conversation as resolved.
test('outgoing http requests generate breadcrumbs correctly with tracing & spans disabled', async () => {
expect.assertions(9);

const [SERVER_URL, closeTestServer] = await createTestServer()
.get('/api/v0', headers => {
// This is not instrumented, sadly
expect(headers['baggage']).toBeUndefined();
expect(headers['sentry-trace']).toBeUndefined();
})
.get('/api/v1', headers => {
// This is not instrumented, sadly
expect(headers['baggage']).toBeUndefined();
expect(headers['sentry-trace']).toBeUndefined();
})
.get('/api/v2', headers => {
expect(headers['baggage']).toBeUndefined();
expect(headers['sentry-trace']).toBeUndefined();
})
.get('/api/v3', headers => {
expect(headers['baggage']).toBeUndefined();
expect(headers['sentry-trace']).toBeUndefined();
})
.start();

await createRunner()
.withEnv({ SERVER_URL })
.expect({
event: {
exception: {
values: [
{
type: 'Error',
value: 'foo',
},
],
breadcrumbs: [
{
message: 'manual breadcrumb',
timestamp: expect.any(Number),
},
breadcrumbs: [
{
message: 'manual breadcrumb',
timestamp: expect.any(Number),
},
{
category: 'http',
data: {
'http.method': 'GET',
url: `${SERVER_URL}/api/v0`,
status_code: 200,
ADDED_PATH: '/api/v0',
},
timestamp: expect.any(Number),
type: 'http',
{
category: 'http',
data: {
'http.method': 'GET',
url: `${SERVER_URL}/api/v0`,
status_code: 200,
ADDED_PATH: '/api/v0',
},
{
category: 'http',
data: {
'http.method': 'GET',
url: `${SERVER_URL}/api/v1`,
status_code: 200,
ADDED_PATH: '/api/v1',
},
timestamp: expect.any(Number),
type: 'http',
timestamp: expect.any(Number),
type: 'http',
},
{
category: 'http',
data: {
'http.method': 'GET',
url: `${SERVER_URL}/api/v1`,
status_code: 200,
ADDED_PATH: '/api/v1',
},
{
category: 'http',
data: {
'http.method': 'GET',
url: `${SERVER_URL}/api/v2`,
status_code: 200,
ADDED_PATH: '/api/v2',
},
timestamp: expect.any(Number),
type: 'http',
timestamp: expect.any(Number),
type: 'http',
},
{
category: 'http',
data: {
'http.method': 'GET',
url: `${SERVER_URL}/api/v2`,
status_code: 200,
ADDED_PATH: '/api/v2',
},
{
category: 'http',
data: {
'http.method': 'GET',
url: `${SERVER_URL}/api/v3`,
status_code: 200,
ADDED_PATH: '/api/v3',
},
timestamp: expect.any(Number),
type: 'http',
timestamp: expect.any(Number),
type: 'http',
},
{
category: 'http',
data: {
'http.method': 'GET',
url: `${SERVER_URL}/api/v3`,
status_code: 200,
ADDED_PATH: '/api/v3',
},
],
},
})
.start()
.completed();
timestamp: expect.any(Number),
type: 'http',
},
],
},
})
.start()
.completed();

closeTestServer();
});
closeTestServer();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ test('captures spans for outgoing http requests', async () => {
expect.objectContaining({
description: expect.stringMatching(/GET .*\/api\/v0/),
op: 'http.client',
origin: 'auto.http.otel.http',
origin: 'auto.http.client',
status: 'ok',
}),
expect.objectContaining({
description: expect.stringMatching(/GET .*\/api\/v1/),
op: 'http.client',
origin: 'auto.http.otel.http',
origin: 'auto.http.client',
status: 'not_found',
data: expect.objectContaining({
'http.response.status_code': 404,
Expand Down
Loading
Loading