From 81ec187a411aa05c055832b5943eb3845f9c6f31 Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Thu, 28 Aug 2025 14:52:09 +0200 Subject: [PATCH 01/24] feat(node): Add request headers as OTel span attributes --- .../nextjs-15/tests/pageload-tracing.test.ts | 28 +++ .../node-express/tests/transactions.test.ts | 31 ++++ .../nuxt-3/tests/tracing.server.test.ts | 28 +++ .../tests/performance.server.test.ts | 28 +++ .../tracing/anthropic/instrument-with-pii.mjs | 5 +- .../suites/tracing/anthropic/instrument.mjs | 5 +- .../tracing/anthropic/scenario-stream.mjs | 2 - .../suites/tracing/anthropic/scenario.mjs | 16 +- packages/astro/src/index.server.ts | 2 + packages/astro/src/server/middleware.ts | 3 + packages/astro/test/server/middleware.test.ts | 44 +++++ packages/bun/src/integrations/bunserver.ts | 3 + .../bun/test/integrations/bunserver.test.ts | 50 +++++ packages/cloudflare/src/request.ts | 4 + packages/core/src/index.ts | 1 + packages/core/src/utils/request.ts | 35 ++++ packages/core/test/lib/utils/request.test.ts | 175 ++++++++++++++++++ packages/node/src/index.ts | 2 + packages/node/src/integrations/http.ts | 14 +- packages/remix/src/server/instrumentServer.ts | 3 + .../sveltekit/src/server-common/handle.ts | 4 + 21 files changed, 462 insertions(+), 21 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/tests/pageload-tracing.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/pageload-tracing.test.ts index 3e41c04e2644..0148d5377318 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15/tests/pageload-tracing.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/pageload-tracing.test.ts @@ -22,3 +22,31 @@ test('App router transactions should be attached to the pageload request span', expect(pageloadTraceId).toBeTruthy(); expect(serverTransaction.contexts?.trace?.trace_id).toBe(pageloadTraceId); }); + +test('extracts HTTP request headers as span attributes', async ({ baseURL }) => { + const serverTransactionPromise = waitForTransaction('nextjs-15', async transactionEvent => { + return transactionEvent?.transaction === 'GET /pageload-tracing'; + }); + + await fetch(`${baseURL}/pageload-tracing`, { + headers: { + 'User-Agent': 'Custom-NextJS-Agent/15.0', + 'Content-Type': 'text/html', + 'X-NextJS-Test': 'nextjs-header-value', + Accept: 'text/html, application/xhtml+xml', + 'X-Framework': 'Next.js', + }, + }); + + const serverTransaction = await serverTransactionPromise; + + expect(serverTransaction.contexts?.trace?.data).toEqual( + expect.objectContaining({ + 'http.request.header.user_agent': ['Custom-NextJS-Agent/15.0'], + 'http.request.header.content_type': ['text/html'], + 'http.request.header.x_nextjs_test': ['nextjs-header-value'], + 'http.request.header.accept': ['text/html, application/xhtml+xml'], + 'http.request.header.x_framework': ['Next.js'], + }), + ); +}); diff --git a/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts index b47feebcd728..586af75f7284 100644 --- a/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts @@ -208,3 +208,34 @@ test('Sends an API route transaction for an errored route', async ({ baseURL }) measurements: {}, }); }); + +test('Extracts HTTP request headers as span attributes', async ({ baseURL }) => { + const transactionEventPromise = waitForTransaction('node-express', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + transactionEvent?.transaction === 'GET /test-transaction' + ); + }); + + await fetch(`${baseURL}/test-transaction`, { + headers: { + 'User-Agent': 'Custom-Agent/1.0 (Test)', + 'Content-Type': 'application/json', + 'X-Custom-Header': 'test-value', + Accept: 'application/json, text/plain', + Authorization: 'Bearer test-token-123', + }, + }); + + const transactionEvent = await transactionEventPromise; + + expect(transactionEvent.contexts?.trace?.data).toEqual( + expect.objectContaining({ + 'http.request.header.user_agent': ['Custom-Agent/1.0 (Test)'], + 'http.request.header.content_type': ['application/json'], + 'http.request.header.x_custom_header': ['test-value'], + 'http.request.header.accept': ['application/json, text/plain'], + 'http.request.header.authorization': ['Bearer test-token-123'], + }), + ); +}); diff --git a/dev-packages/e2e-tests/test-applications/nuxt-3/tests/tracing.server.test.ts b/dev-packages/e2e-tests/test-applications/nuxt-3/tests/tracing.server.test.ts index f1df13a71ab3..28ded4be9add 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-3/tests/tracing.server.test.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-3/tests/tracing.server.test.ts @@ -43,3 +43,31 @@ test('does not send transactions for build asset folder "_nuxt"', async ({ page expect(transactionEvent.transaction).toBe('GET /test-param/:param()'); }); + +test('extracts HTTP request headers as span attributes', async ({ baseURL }) => { + const transactionPromise = waitForTransaction('nuxt-3', transactionEvent => { + return transactionEvent.transaction.includes('GET /api/test-param/'); + }); + + await fetch(`${baseURL}/api/test-param/headers-test`, { + headers: { + 'User-Agent': 'Custom-Nuxt-Agent/3.0', + 'Content-Type': 'application/json', + 'X-Nuxt-Test': 'nuxt-header-value', + Accept: 'application/json, text/html', + 'X-Framework': 'Nuxt', + }, + }); + + const transaction = await transactionPromise; + + expect(transaction.contexts?.trace?.data).toEqual( + expect.objectContaining({ + 'http.request.header.user_agent': ['Custom-Nuxt-Agent/3.0'], + 'http.request.header.content_type': ['application/json'], + 'http.request.header.x_nuxt_test': ['nuxt-header-value'], + 'http.request.header.accept': ['application/json, text/html'], + 'http.request.header.x_framework': ['Nuxt'], + }), + ); +}); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2/tests/performance.server.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2/tests/performance.server.test.ts index 4cc3fb5cef9e..f86503c86ba1 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2/tests/performance.server.test.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2/tests/performance.server.test.ts @@ -32,3 +32,31 @@ test('server pageload request span has nested request span for sub request', asy ]), ); }); + +test('extracts HTTP request headers as span attributes', async ({ page, baseURL }) => { + const serverTxnEventPromise = waitForTransaction('sveltekit-2', txnEvent => { + return txnEvent?.transaction === 'GET /api/users'; + }); + + await fetch(`${baseURL}/api/users`, { + headers: { + 'User-Agent': 'Custom-SvelteKit-Agent/1.0', + 'Content-Type': 'application/json', + 'X-Test-Header': 'sveltekit-test-value', + Accept: 'application/json', + 'X-Framework': 'SvelteKit', + }, + }); + + const serverTxnEvent = await serverTxnEventPromise; + + expect(serverTxnEvent.contexts?.trace?.data).toEqual( + expect.objectContaining({ + 'http.request.header.user_agent': ['Custom-SvelteKit-Agent/1.0'], + 'http.request.header.content_type': ['application/json'], + 'http.request.header.x_test_header': ['sveltekit-test-value'], + 'http.request.header.accept': ['application/json'], + 'http.request.header.x_framework': ['SvelteKit'], + }), + ); +}); diff --git a/dev-packages/node-integration-tests/suites/tracing/anthropic/instrument-with-pii.mjs b/dev-packages/node-integration-tests/suites/tracing/anthropic/instrument-with-pii.mjs index eb8b02b1cf8b..c2776c15b001 100644 --- a/dev-packages/node-integration-tests/suites/tracing/anthropic/instrument-with-pii.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/anthropic/instrument-with-pii.mjs @@ -8,8 +8,5 @@ Sentry.init({ tracesSampleRate: 1.0, sendDefaultPii: true, transport: loggingTransport, - integrations: [ - Sentry.anthropicAIIntegration(), - nodeContextIntegration(), - ], + integrations: [Sentry.anthropicAIIntegration(), nodeContextIntegration()], }); diff --git a/dev-packages/node-integration-tests/suites/tracing/anthropic/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/anthropic/instrument.mjs index fa011052c50c..39f1506eb2c9 100644 --- a/dev-packages/node-integration-tests/suites/tracing/anthropic/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/anthropic/instrument.mjs @@ -9,8 +9,5 @@ Sentry.init({ sendDefaultPii: false, transport: loggingTransport, // Force include the integration - integrations: [ - Sentry.anthropicAIIntegration(), - nodeContextIntegration(), - ], + integrations: [Sentry.anthropicAIIntegration(), nodeContextIntegration()], }); diff --git a/dev-packages/node-integration-tests/suites/tracing/anthropic/scenario-stream.mjs b/dev-packages/node-integration-tests/suites/tracing/anthropic/scenario-stream.mjs index da70a2b12467..ac5eb6019010 100644 --- a/dev-packages/node-integration-tests/suites/tracing/anthropic/scenario-stream.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/anthropic/scenario-stream.mjs @@ -101,5 +101,3 @@ async function run() { } run(); - - diff --git a/dev-packages/node-integration-tests/suites/tracing/anthropic/scenario.mjs b/dev-packages/node-integration-tests/suites/tracing/anthropic/scenario.mjs index 425d1366879e..590796931315 100644 --- a/dev-packages/node-integration-tests/suites/tracing/anthropic/scenario.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/anthropic/scenario.mjs @@ -8,7 +8,7 @@ class MockAnthropic { // Create messages object with create and countTokens methods this.messages = { create: this._messagesCreate.bind(this), - countTokens: this._messagesCountTokens.bind(this) + countTokens: this._messagesCountTokens.bind(this), }; this.models = { @@ -56,8 +56,8 @@ class MockAnthropic { // For countTokens, just return input_tokens return { - input_tokens: 15 - } + input_tokens: 15, + }; } async _modelsRetrieve(modelId) { @@ -69,7 +69,7 @@ class MockAnthropic { id: modelId, name: modelId, created_at: 1715145600, - model: modelId, // Add model field to match the check in addResponseAttributes + model: modelId, // Add model field to match the check in addResponseAttributes }; } } @@ -86,9 +86,7 @@ async function run() { await client.messages.create({ model: 'claude-3-haiku-20240307', system: 'You are a helpful assistant.', - messages: [ - { role: 'user', content: 'What is the capital of France?' }, - ], + messages: [{ role: 'user', content: 'What is the capital of France?' }], temperature: 0.7, max_tokens: 100, }); @@ -106,9 +104,7 @@ async function run() { // Third test: count tokens with cached tokens await client.messages.countTokens({ model: 'claude-3-haiku-20240307', - messages: [ - { role: 'user', content: 'What is the capital of France?' }, - ], + messages: [{ role: 'user', content: 'What is the capital of France?' }], }); // Fourth test: models.retrieve diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts index ce9a1b1fa65a..c91d98725d9b 100644 --- a/packages/astro/src/index.server.ts +++ b/packages/astro/src/index.server.ts @@ -59,6 +59,8 @@ export { getSpanStatusFromHttpCode, getTraceData, getTraceMetaTags, + httpHeadersToSpanAttributes, + winterCGHeadersToDict, graphqlIntegration, hapiIntegration, httpIntegration, diff --git a/packages/astro/src/server/middleware.ts b/packages/astro/src/server/middleware.ts index fbf6720c23b8..d27bb19e19a9 100644 --- a/packages/astro/src/server/middleware.ts +++ b/packages/astro/src/server/middleware.ts @@ -14,10 +14,12 @@ import { getClient, getCurrentScope, getTraceMetaTags, + httpHeadersToSpanAttributes, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, setHttpStatus, startSpan, + winterCGHeadersToDict, withIsolationScope, } from '@sentry/node'; import type { APIContext, MiddlewareResponseHandler, RoutePart } from 'astro'; @@ -154,6 +156,7 @@ async function instrumentRequest( [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source, method, url: stripUrlQueryAndFragment(ctx.url.href), + ...httpHeadersToSpanAttributes(winterCGHeadersToDict(request.headers)), }; if (ctx.url.search) { diff --git a/packages/astro/test/server/middleware.test.ts b/packages/astro/test/server/middleware.test.ts index 6430a5f47eb7..fa6f880d840d 100644 --- a/packages/astro/test/server/middleware.test.ts +++ b/packages/astro/test/server/middleware.test.ts @@ -86,6 +86,50 @@ describe('sentryMiddleware', () => { expect(resultFromNext).toStrictEqual(nextResult); }); + it('includes HTTP request headers as span attributes', async () => { + const middleware = handleRequest(); + const headers = new Headers({ + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', + 'Content-Type': 'application/json', + 'X-Custom-Header': 'custom-value', + Accept: 'application/json, text/plain', + }); + + const ctx = { + request: { + method: 'POST', + url: '/api/data', + headers, + }, + url: new URL('https://myDomain.io/api/data'), + params: {}, + }; + const next = vi.fn(() => nextResult); + + // @ts-expect-error, a partial ctx object is fine here + await middleware(ctx, next); + + expect(startSpanSpy).toHaveBeenCalledWith( + { + attributes: { + 'sentry.origin': 'auto.http.astro', + method: 'POST', + url: 'https://mydomain.io/api/data', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + 'http.request.header.user_agent': ['Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'], + 'http.request.header.content_type': ['application/json'], + 'http.request.header.x_custom_header': ['custom-value'], + 'http.request.header.accept': ['application/json, text/plain'], + }, + name: 'POST /api/data', + op: 'http.server', + }, + expect.any(Function), + ); + + expect(next).toHaveBeenCalled(); + }); + it("sets source route if the url couldn't be decoded correctly", async () => { const middleware = handleRequest(); const ctx = { diff --git a/packages/bun/src/integrations/bunserver.ts b/packages/bun/src/integrations/bunserver.ts index c31aa0e84ebc..06902ae2b05a 100644 --- a/packages/bun/src/integrations/bunserver.ts +++ b/packages/bun/src/integrations/bunserver.ts @@ -3,6 +3,7 @@ import { captureException, continueTrace, defineIntegration, + httpHeadersToSpanAttributes, isURLObjectRelative, parseStringToURLObject, SEMANTIC_ATTRIBUTE_HTTP_REQUEST_METHOD, @@ -205,6 +206,8 @@ function wrapRequestHandler( routeName = route; } + Object.assign(attributes, httpHeadersToSpanAttributes(request.headers.toJSON())); + isolationScope.setSDKProcessingMetadata({ normalizedRequest: { url: request.url, diff --git a/packages/bun/test/integrations/bunserver.test.ts b/packages/bun/test/integrations/bunserver.test.ts index 77ac2440915b..3244a2a38ae6 100644 --- a/packages/bun/test/integrations/bunserver.test.ts +++ b/packages/bun/test/integrations/bunserver.test.ts @@ -128,6 +128,56 @@ describe('Bun Serve Integration', () => { expect(startSpanSpy).toHaveBeenCalledTimes(1); }); + test('includes HTTP request headers as span attributes', async () => { + const server = Bun.serve({ + async fetch(_req) { + return new Response('Headers test!'); + }, + port, + }); + + // Make request with custom headers + await fetch(`http://localhost:${port}/api/test`, { + method: 'POST', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', + 'Content-Type': 'application/json', + 'X-Custom-Header': 'custom-value', + Accept: 'application/json, text/plain', + Authorization: 'Bearer token123', + }, + body: JSON.stringify({ test: 'data' }), + }); + + await server.stop(); + + // Verify span was created with header attributes + expect(startSpanSpy).toHaveBeenCalledTimes(1); + expect(startSpanSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ + attributes: expect.objectContaining({ + 'sentry.origin': 'auto.http.bun.serve', + 'http.request.method': 'POST', + 'sentry.source': 'url', + 'url.path': '/api/test', + 'url.full': `http://localhost:${port}/api/test`, + 'url.port': port.toString(), + 'url.scheme': 'http:', + 'url.domain': 'localhost', + // HTTP headers as span attributes following OpenTelemetry semantic conventions + 'http.request.header.user_agent': ['Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'], + 'http.request.header.content_type': ['application/json'], + 'http.request.header.x_custom_header': ['custom-value'], + 'http.request.header.accept': ['application/json, text/plain'], + 'http.request.header.authorization': ['Bearer token123'], + }), + op: 'http.server', + name: 'POST /api/test', + }), + expect.any(Function), + ); + }); + test('skips span creation for OPTIONS and HEAD requests', async () => { const server = Bun.serve({ async fetch(_req) { diff --git a/packages/cloudflare/src/request.ts b/packages/cloudflare/src/request.ts index edc1bccef96f..58294ed3c8e3 100644 --- a/packages/cloudflare/src/request.ts +++ b/packages/cloudflare/src/request.ts @@ -4,10 +4,12 @@ import { continueTrace, flush, getHttpSpanDetailsFromUrlObject, + httpHeadersToSpanAttributes, parseStringToURLObject, SEMANTIC_ATTRIBUTE_SENTRY_OP, setHttpStatus, startSpan, + winterCGHeadersToDict, withIsolationScope, } from '@sentry/core'; import type { CloudflareOptions } from './client'; @@ -64,6 +66,8 @@ export function wrapRequestHandler( attributes['user_agent.original'] = userAgentHeader; } + Object.assign(attributes, httpHeadersToSpanAttributes(winterCGHeadersToDict(request.headers))); + attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] = 'http.server'; addCloudResourceContext(isolationScope); diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 6385a75687f7..c2d643b2e719 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -92,6 +92,7 @@ export { httpRequestToRequestData, extractQueryParamsFromUrl, headersToDict, + httpHeadersToSpanAttributes, } from './utils/request'; export { DEFAULT_ENVIRONMENT } from './constants'; export { addBreadcrumb } from './breadcrumbs'; diff --git a/packages/core/src/utils/request.ts b/packages/core/src/utils/request.ts index 04cd1006ba28..d42bc4f8491e 100644 --- a/packages/core/src/utils/request.ts +++ b/packages/core/src/utils/request.ts @@ -128,6 +128,41 @@ function getAbsoluteUrl({ return undefined; } +/** + * Converts HTTP request headers to OpenTelemetry span attributes following semantic conventions. + * Header names are converted to the format: http.request.header. + * where is the header name in lowercase with dashes converted to underscores. + * + * @see https://opentelemetry.io/docs/specs/semconv/registry/attributes/http/#http-request-header + */ +export function httpHeadersToSpanAttributes( + headers: Record, +): Record { + const spanAttributes: Record = {}; + + try { + Object.entries(headers).forEach(([key, value]) => { + if (value !== undefined) { + const normalizedKey = `http.request.header.${key.toLowerCase().replace(/-/g, '_')}`; + + if (Array.isArray(value)) { + const stringValues = value.filter((v): v is string => typeof v === 'string'); + + if (stringValues.length > 0) { + spanAttributes[normalizedKey] = stringValues; + } + } else if (typeof value === 'string') { + spanAttributes[normalizedKey] = [value]; + } + } + }); + } catch { + // Return empty object if there's an error + } + + return spanAttributes; +} + /** Extract the query params from an URL. */ export function extractQueryParamsFromUrl(url: string): string | undefined { // url is path and query string diff --git a/packages/core/test/lib/utils/request.test.ts b/packages/core/test/lib/utils/request.test.ts index fe90578d5392..d10f8d84ca8e 100644 --- a/packages/core/test/lib/utils/request.test.ts +++ b/packages/core/test/lib/utils/request.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest'; import { extractQueryParamsFromUrl, headersToDict, + httpHeadersToSpanAttributes, httpRequestToRequestData, winterCGHeadersToDict, winterCGRequestToRequestData, @@ -420,4 +421,178 @@ describe('request utils', () => { expect(extractQueryParamsFromUrl(url)).toEqual(expected); }); }); + + describe('httpHeadersToSpanAttributes', () => { + it('works with empty headers object', () => { + expect(httpHeadersToSpanAttributes({})).toEqual({}); + }); + + it('converts single string header values to arrays', () => { + const headers = { + 'Content-Type': 'application/json', + 'user-agent': 'test-agent', + }; + + const result = httpHeadersToSpanAttributes(headers); + + expect(result).toEqual({ + 'http.request.header.content_type': ['application/json'], + 'http.request.header.user_agent': ['test-agent'], + }); + }); + + it('handles array header values', () => { + const headers = { + 'custom-header': ['value1', 'value2'], + accept: ['application/json', 'text/html'], + }; + + const result = httpHeadersToSpanAttributes(headers); + + expect(result).toEqual({ + 'http.request.header.custom_header': ['value1', 'value2'], + 'http.request.header.accept': ['application/json', 'text/html'], + }); + }); + + it('filters out undefined values from arrays', () => { + const headers = { + 'mixed-header': ['value1', undefined, 'value2'], + } as any; + + const result = httpHeadersToSpanAttributes(headers); + + expect(result).toEqual({ + 'http.request.header.mixed_header': ['value1', 'value2'], + }); + }); + + it('ignores empty arrays after filtering undefined values', () => { + const headers = { + 'empty-after-filter': [undefined, undefined], + 'valid-header': 'valid-value', + } as any; + + const result = httpHeadersToSpanAttributes(headers); + + expect(result).toEqual({ + 'http.request.header.valid_header': ['valid-value'], + }); + }); + + it('ignores undefined header values', () => { + const headers = { + 'valid-header': 'valid-value', + 'undefined-header': undefined, + }; + + const result = httpHeadersToSpanAttributes(headers); + + expect(result).toEqual({ + 'http.request.header.valid_header': ['valid-value'], + }); + }); + + it('converts header names to lowercase and replaces dashes with underscores', () => { + const headers = { + 'Content-Type': 'application/json', + 'X-CUSTOM-HEADER': 'custom-value', + 'user-Agent': 'test-agent', + ACCEPT: 'text/html', + }; + + const result = httpHeadersToSpanAttributes(headers); + + expect(result).toEqual({ + 'http.request.header.content_type': ['application/json'], + 'http.request.header.x_custom_header': ['custom-value'], + 'http.request.header.user_agent': ['test-agent'], + 'http.request.header.accept': ['text/html'], + }); + }); + + it('handles real-world headers', () => { + const headers = { + Host: 'example.com', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', + Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'Accept-Language': 'en-US,en;q=0.5', + 'Accept-Encoding': 'gzip, deflate', + Connection: 'keep-alive', + 'Upgrade-Insecure-Requests': '1', + 'Cache-Control': 'no-cache', + 'X-Forwarded-For': '192.168.1.1', + }; + + const result = httpHeadersToSpanAttributes(headers); + + expect(result).toEqual({ + 'http.request.header.host': ['example.com'], + 'http.request.header.user_agent': ['Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'], + 'http.request.header.accept': ['text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'], + 'http.request.header.accept_language': ['en-US,en;q=0.5'], + 'http.request.header.accept_encoding': ['gzip, deflate'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.upgrade_insecure_requests': ['1'], + 'http.request.header.cache_control': ['no-cache'], + 'http.request.header.x_forwarded_for': ['192.168.1.1'], + }); + }); + + it('handles multiple values for the same header', () => { + const headers = { + Cookie: ['session=abc123', 'preferences=dark-mode'], + Accept: ['application/json', 'text/html'], + }; + + const result = httpHeadersToSpanAttributes(headers); + + expect(result).toEqual({ + 'http.request.header.cookie': ['session=abc123', 'preferences=dark-mode'], + 'http.request.header.accept': ['application/json', 'text/html'], + }); + }); + + it('returns empty object when processing invalid headers throws error', () => { + // Create a headers object that will throw an error when iterated + const headers = {}; + Object.defineProperty(headers, Symbol.iterator, { + get() { + throw new Error('Test error'); + }, + }); + + const result = httpHeadersToSpanAttributes(headers); + + expect(result).toEqual({}); + }); + + it('ignores non-string values in arrays', () => { + const headers = { + 'mixed-types': ['string-value', 123, true, null], + } as any; + + const result = httpHeadersToSpanAttributes(headers); + + expect(result).toEqual({ + 'http.request.header.mixed_types': ['string-value'], + }); + }); + + it('ignores non-string and non-array header values', () => { + const headers = { + 'string-header': 'valid-value', + 'number-header': 123, + 'boolean-header': true, + 'null-header': null, + 'object-header': { key: 'value' }, + } as any; + + const result = httpHeadersToSpanAttributes(headers); + + expect(result).toEqual({ + 'http.request.header.string_header': ['valid-value'], + }); + }); + }); }); diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index f5f3865feffa..f510ca733d19 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -92,6 +92,8 @@ export { getIsolationScope, getTraceData, getTraceMetaTags, + httpHeadersToSpanAttributes, + winterCGHeadersToDict, continueTrace, withScope, withIsolationScope, diff --git a/packages/node/src/integrations/http.ts b/packages/node/src/integrations/http.ts index 4f1eeeea9e9c..5bf4502e4a85 100644 --- a/packages/node/src/integrations/http.ts +++ b/packages/node/src/integrations/http.ts @@ -3,7 +3,13 @@ import { diag } from '@opentelemetry/api'; import type { HttpInstrumentationConfig } from '@opentelemetry/instrumentation-http'; import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'; import type { Span } from '@sentry/core'; -import { defineIntegration, getClient, hasSpansEnabled, stripUrlQueryAndFragment } from '@sentry/core'; +import { + defineIntegration, + getClient, + hasSpansEnabled, + httpHeadersToSpanAttributes, + stripUrlQueryAndFragment, +} from '@sentry/core'; import type { HTTPModuleRequestIncomingMessage, NodeClient } from '@sentry/node-core'; import { type SentryHttpInstrumentationOptions, @@ -313,6 +319,12 @@ function getConfigWithDefaults(options: Partial = {}): HttpInstrume span.setAttribute('sentry.http.prefetch', true); } + // Extract headers for incoming requests + if (!_isClientRequest(req)) { + const headerAttributes = httpHeadersToSpanAttributes(req.headers); + span.setAttributes(headerAttributes); + } + options.instrumentation?.requestHook?.(span, req); }, responseHook: (span, res) => { diff --git a/packages/remix/src/server/instrumentServer.ts b/packages/remix/src/server/instrumentServer.ts index db8322a0c828..418a79dadd2e 100644 --- a/packages/remix/src/server/instrumentServer.ts +++ b/packages/remix/src/server/instrumentServer.ts @@ -23,6 +23,7 @@ import { getRootSpan, getTraceData, hasSpansEnabled, + httpHeadersToSpanAttributes, isNodeEnv, loadModule, SEMANTIC_ATTRIBUTE_SENTRY_OP, @@ -31,6 +32,7 @@ import { setHttpStatus, spanToJSON, startSpan, + winterCGHeadersToDict, winterCGRequestToRequestData, withIsolationScope, } from '@sentry/core'; @@ -324,6 +326,7 @@ function wrapRequestHandler ServerBuild | Promise [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source, [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.server', method: request.method, + ...httpHeadersToSpanAttributes(winterCGHeadersToDict(request.headers)), }, }, async span => { diff --git a/packages/sveltekit/src/server-common/handle.ts b/packages/sveltekit/src/server-common/handle.ts index 3f5797efd211..3d9963bd1056 100644 --- a/packages/sveltekit/src/server-common/handle.ts +++ b/packages/sveltekit/src/server-common/handle.ts @@ -7,6 +7,7 @@ import { getDefaultIsolationScope, getIsolationScope, getTraceMetaTags, + httpHeadersToSpanAttributes, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, @@ -14,6 +15,7 @@ import { spanToJSON, startSpan, updateSpanName, + winterCGHeadersToDict, winterCGRequestToRequestData, withIsolationScope, } from '@sentry/core'; @@ -176,6 +178,7 @@ async function instrumentHandle( [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.sveltekit', [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: routeName ? 'route' : 'url', 'sveltekit.tracing.original_name': originalName, + ...httpHeadersToSpanAttributes(winterCGHeadersToDict(event.request.headers)), }); } @@ -201,6 +204,7 @@ async function instrumentHandle( [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.sveltekit', [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: routeId ? 'route' : 'url', 'http.method': event.request.method, + ...httpHeadersToSpanAttributes(winterCGHeadersToDict(event.request.headers)), }, name: routeName, }, From a1305794d0a8b1d3d525378e8ce15a1839415c8c Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Thu, 28 Aug 2025 16:04:58 +0200 Subject: [PATCH 02/24] fix some tests --- .../node-express/tests/transactions.test.ts | 4 ++-- .../node-fastify-5/tests/propagation.test.ts | 12 ++++++------ .../node-koa/tests/propagation.test.ts | 12 ++++++------ .../suites/tracing/httpIntegration/test.ts | 16 ++++++++++++++++ packages/cloudflare/test/request.test.ts | 1 + packages/core/src/utils/request.ts | 2 +- 6 files changed, 32 insertions(+), 15 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts index 586af75f7284..05b92a7e9593 100644 --- a/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts @@ -14,7 +14,7 @@ test('Sends an API route transaction', async ({ baseURL }) => { const transactionEvent = await pageloadTransactionEventPromise; expect(transactionEvent.contexts?.trace).toEqual({ - data: { + data: expect.objectContaining({ 'sentry.source': 'route', 'sentry.origin': 'auto.http.otel.http', 'sentry.op': 'http.server', @@ -38,7 +38,7 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-transaction', - }, + }), op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), status: 'ok', diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/propagation.test.ts index 6de3b988c3b5..86c899fdc71f 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/propagation.test.ts @@ -51,7 +51,7 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { ); expect(outboundTransaction.contexts?.trace).toEqual({ - data: { + data: expect.objectContaining({ 'sentry.source': 'route', 'sentry.origin': 'auto.http.otel.http', 'sentry.op': 'http.server', @@ -75,7 +75,7 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-outgoing-http/:id', - }, + }), op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), status: 'ok', @@ -84,7 +84,7 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { }); expect(inboundTransaction.contexts?.trace).toEqual({ - data: { + data: expect.objectContaining({ 'sentry.source': 'route', 'sentry.origin': 'auto.http.otel.http', 'sentry.op': 'http.server', @@ -106,7 +106,7 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-inbound-headers/:id', - }, + }), op: 'http.server', parent_span_id: outgoingHttpSpanId, span_id: expect.stringMatching(/[a-f0-9]{16}/), @@ -165,7 +165,7 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { ); expect(outboundTransaction.contexts?.trace).toEqual({ - data: { + data: expect.objectContaining({ 'sentry.source': 'route', 'sentry.origin': 'auto.http.otel.http', 'sentry.op': 'http.server', @@ -189,7 +189,7 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-outgoing-fetch/:id', - }, + }), op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), status: 'ok', diff --git a/dev-packages/e2e-tests/test-applications/node-koa/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/node-koa/tests/propagation.test.ts index f7e8639b7ace..4e840947fcb6 100644 --- a/dev-packages/e2e-tests/test-applications/node-koa/tests/propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-koa/tests/propagation.test.ts @@ -50,7 +50,7 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { ); expect(outboundTransaction.contexts?.trace).toEqual({ - data: { + data: expect.objectContaining({ 'sentry.source': 'route', 'sentry.origin': 'auto.http.otel.http', 'sentry.op': 'http.server', @@ -74,7 +74,7 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-outgoing-http/:id', - }, + }), op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), status: 'ok', @@ -83,7 +83,7 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { }); expect(inboundTransaction.contexts?.trace).toEqual({ - data: { + data: expect.objectContaining({ 'sentry.source': 'route', 'sentry.origin': 'auto.http.otel.http', 'sentry.op': 'http.server', @@ -105,7 +105,7 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-inbound-headers/:id', - }, + }), op: 'http.server', parent_span_id: outgoingHttpSpanId, span_id: expect.stringMatching(/[a-f0-9]{16}/), @@ -164,7 +164,7 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { ); expect(outboundTransaction.contexts?.trace).toEqual({ - data: { + data: expect.objectContaining({ 'sentry.source': 'route', 'sentry.origin': 'auto.http.otel.http', 'sentry.op': 'http.server', @@ -188,7 +188,7 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-outgoing-fetch/:id', - }, + }), op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), status: 'ok', diff --git a/dev-packages/node-integration-tests/suites/tracing/httpIntegration/test.ts b/dev-packages/node-integration-tests/suites/tracing/httpIntegration/test.ts index 397b6baa7cc7..dd1a9059b1ab 100644 --- a/dev-packages/node-integration-tests/suites/tracing/httpIntegration/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/httpIntegration/test.ts @@ -2,6 +2,18 @@ import { afterAll, describe, expect, test } from 'vitest'; import { cleanupChildProcesses, createEsmAndCjsTests, createRunner } from '../../../utils/runner'; import { createTestServer } from '../../../utils/server'; +function getCommonHttpRequestHeaders(): Record { + return { + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate'], + 'http.request.header.accept_language': ['*'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': [expect.any(String)], + 'http.request.header.sec_fetch_mode': ['cors'], + 'http.request.header.user_agent': ['node'], + }; +} + describe('httpIntegration', () => { afterAll(() => { cleanupChildProcesses(); @@ -86,6 +98,7 @@ describe('httpIntegration', () => { 'sentry.sample_rate': 1, 'sentry.source': 'route', url: `http://localhost:${port}/test`, + ...getCommonHttpRequestHeaders(), }); }, }) @@ -127,6 +140,9 @@ describe('httpIntegration', () => { 'sentry.sample_rate': 1, 'sentry.source': 'route', url: `http://localhost:${port}/test`, + 'http.request.header.content_length': ['9'], + 'http.request.header.content_type': ['text/plain;charset=UTF-8'], + ...getCommonHttpRequestHeaders(), }); }, }) diff --git a/packages/cloudflare/test/request.test.ts b/packages/cloudflare/test/request.test.ts index eb2989437396..2277632e9ab1 100644 --- a/packages/cloudflare/test/request.test.ts +++ b/packages/cloudflare/test/request.test.ts @@ -319,6 +319,7 @@ describe('withSentry', () => { 'sentry.sample_rate': 1, 'http.response.status_code': 200, 'http.request.body.size': 10, + 'http.request.header.content_length': ['10'], }, op: 'http.server', origin: 'auto.http.cloudflare', diff --git a/packages/core/src/utils/request.ts b/packages/core/src/utils/request.ts index d42bc4f8491e..f9462807e22c 100644 --- a/packages/core/src/utils/request.ts +++ b/packages/core/src/utils/request.ts @@ -129,7 +129,7 @@ function getAbsoluteUrl({ } /** - * Converts HTTP request headers to OpenTelemetry span attributes following semantic conventions. + * Converts incoming HTTP request headers to OpenTelemetry span attributes following semantic conventions. * Header names are converted to the format: http.request.header. * where is the header name in lowercase with dashes converted to underscores. * From 88c1db5ec04110a4f724f631940eabf661537513 Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Mon, 1 Sep 2025 10:19:57 +0200 Subject: [PATCH 03/24] add check for sendDefaultPii --- .../nextjs-15/tests/pageload-tracing.test.ts | 2 + .../node-express/tests/transactions.test.ts | 4 +- .../nuxt-3/tests/tracing.server.test.ts | 2 + .../tests/performance.server.test.ts | 2 + packages/astro/src/server/middleware.ts | 2 +- packages/bun/src/integrations/bunserver.ts | 5 +- .../bun/test/integrations/bunserver.test.ts | 16 +++- packages/cloudflare/src/request.ts | 3 +- packages/core/src/utils/request.ts | 12 ++- packages/core/test/lib/utils/request.test.ts | 92 +++++++++++++++++++ packages/node/src/integrations/http.ts | 4 +- packages/remix/src/server/instrumentServer.ts | 5 +- .../sveltekit/src/server-common/handle.ts | 11 ++- 13 files changed, 149 insertions(+), 11 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/tests/pageload-tracing.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/pageload-tracing.test.ts index 0148d5377318..7a92a80a84b7 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15/tests/pageload-tracing.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/pageload-tracing.test.ts @@ -35,6 +35,7 @@ test('extracts HTTP request headers as span attributes', async ({ baseURL }) => 'X-NextJS-Test': 'nextjs-header-value', Accept: 'text/html, application/xhtml+xml', 'X-Framework': 'Next.js', + 'X-Request-ID': 'nextjs-789', }, }); @@ -47,6 +48,7 @@ test('extracts HTTP request headers as span attributes', async ({ baseURL }) => 'http.request.header.x_nextjs_test': ['nextjs-header-value'], 'http.request.header.accept': ['text/html, application/xhtml+xml'], 'http.request.header.x_framework': ['Next.js'], + 'http.request.header.x_request_id': ['nextjs-789'], }), ); }); diff --git a/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts index 05b92a7e9593..8483e1e71735 100644 --- a/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts @@ -223,7 +223,7 @@ test('Extracts HTTP request headers as span attributes', async ({ baseURL }) => 'Content-Type': 'application/json', 'X-Custom-Header': 'test-value', Accept: 'application/json, text/plain', - Authorization: 'Bearer test-token-123', + 'X-Request-ID': 'req-123', }, }); @@ -235,7 +235,7 @@ test('Extracts HTTP request headers as span attributes', async ({ baseURL }) => 'http.request.header.content_type': ['application/json'], 'http.request.header.x_custom_header': ['test-value'], 'http.request.header.accept': ['application/json, text/plain'], - 'http.request.header.authorization': ['Bearer test-token-123'], + 'http.request.header.x_request_id': ['req-123'], }), ); }); diff --git a/dev-packages/e2e-tests/test-applications/nuxt-3/tests/tracing.server.test.ts b/dev-packages/e2e-tests/test-applications/nuxt-3/tests/tracing.server.test.ts index 28ded4be9add..6fb1aa6928a5 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-3/tests/tracing.server.test.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-3/tests/tracing.server.test.ts @@ -56,6 +56,7 @@ test('extracts HTTP request headers as span attributes', async ({ baseURL }) => 'X-Nuxt-Test': 'nuxt-header-value', Accept: 'application/json, text/html', 'X-Framework': 'Nuxt', + 'X-Request-ID': 'nuxt-456', }, }); @@ -68,6 +69,7 @@ test('extracts HTTP request headers as span attributes', async ({ baseURL }) => 'http.request.header.x_nuxt_test': ['nuxt-header-value'], 'http.request.header.accept': ['application/json, text/html'], 'http.request.header.x_framework': ['Nuxt'], + 'http.request.header.x_request_id': ['nuxt-456'], }), ); }); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2/tests/performance.server.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2/tests/performance.server.test.ts index f86503c86ba1..732a6b860a74 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2/tests/performance.server.test.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2/tests/performance.server.test.ts @@ -45,6 +45,7 @@ test('extracts HTTP request headers as span attributes', async ({ page, baseURL 'X-Test-Header': 'sveltekit-test-value', Accept: 'application/json', 'X-Framework': 'SvelteKit', + 'X-Request-ID': 'sveltekit-123', }, }); @@ -57,6 +58,7 @@ test('extracts HTTP request headers as span attributes', async ({ page, baseURL 'http.request.header.x_test_header': ['sveltekit-test-value'], 'http.request.header.accept': ['application/json'], 'http.request.header.x_framework': ['SvelteKit'], + 'http.request.header.x_request_id': ['sveltekit-123'], }), ); }); diff --git a/packages/astro/src/server/middleware.ts b/packages/astro/src/server/middleware.ts index d27bb19e19a9..05c543c4e5c6 100644 --- a/packages/astro/src/server/middleware.ts +++ b/packages/astro/src/server/middleware.ts @@ -156,7 +156,7 @@ async function instrumentRequest( [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source, method, url: stripUrlQueryAndFragment(ctx.url.href), - ...httpHeadersToSpanAttributes(winterCGHeadersToDict(request.headers)), + ...httpHeadersToSpanAttributes(winterCGHeadersToDict(request.headers), getClient()?.getOptions().sendDefaultPii ?? false), }; if (ctx.url.search) { diff --git a/packages/bun/src/integrations/bunserver.ts b/packages/bun/src/integrations/bunserver.ts index 06902ae2b05a..9c235b8bc97c 100644 --- a/packages/bun/src/integrations/bunserver.ts +++ b/packages/bun/src/integrations/bunserver.ts @@ -3,6 +3,7 @@ import { captureException, continueTrace, defineIntegration, + getClient, httpHeadersToSpanAttributes, isURLObjectRelative, parseStringToURLObject, @@ -206,7 +207,9 @@ function wrapRequestHandler( routeName = route; } - Object.assign(attributes, httpHeadersToSpanAttributes(request.headers.toJSON())); + const client = getClient(); + const sendDefaultPii = client?.getOptions().sendDefaultPii ?? false; + Object.assign(attributes, httpHeadersToSpanAttributes(request.headers.toJSON(), sendDefaultPii)); isolationScope.setSDKProcessingMetadata({ normalizedRequest: { diff --git a/packages/bun/test/integrations/bunserver.test.ts b/packages/bun/test/integrations/bunserver.test.ts index 3244a2a38ae6..6ef612493ad7 100644 --- a/packages/bun/test/integrations/bunserver.test.ts +++ b/packages/bun/test/integrations/bunserver.test.ts @@ -47,6 +47,11 @@ describe('Bun Serve Integration', () => { 'url.port': port.toString(), 'url.scheme': 'http:', 'url.domain': 'localhost', + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate, br, zstd'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': [expect.any(String)], + 'http.request.header.user_agent': [expect.stringContaining('Bun')], }, op: 'http.server', name: 'GET /users', @@ -81,6 +86,12 @@ describe('Bun Serve Integration', () => { 'url.port': port.toString(), 'url.scheme': 'http:', 'url.domain': 'localhost', + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate, br, zstd'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.content_length': ['0'], + 'http.request.header.host': [expect.any(String)], + 'http.request.header.user_agent': [expect.stringContaining('Bun')], }, op: 'http.server', name: 'POST /', @@ -169,7 +180,10 @@ describe('Bun Serve Integration', () => { 'http.request.header.content_type': ['application/json'], 'http.request.header.x_custom_header': ['custom-value'], 'http.request.header.accept': ['application/json, text/plain'], - 'http.request.header.authorization': ['Bearer token123'], + 'http.request.header.accept_encoding': ['gzip, deflate, br, zstd'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.content_length': ['15'], + 'http.request.header.host': [expect.any(String)], }), op: 'http.server', name: 'POST /api/test', diff --git a/packages/cloudflare/src/request.ts b/packages/cloudflare/src/request.ts index 58294ed3c8e3..45fe548696ab 100644 --- a/packages/cloudflare/src/request.ts +++ b/packages/cloudflare/src/request.ts @@ -66,7 +66,8 @@ export function wrapRequestHandler( attributes['user_agent.original'] = userAgentHeader; } - Object.assign(attributes, httpHeadersToSpanAttributes(winterCGHeadersToDict(request.headers))); + const sendDefaultPii = options.sendDefaultPii ?? false; + Object.assign(attributes, httpHeadersToSpanAttributes(winterCGHeadersToDict(request.headers), sendDefaultPii)); attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] = 'http.server'; diff --git a/packages/core/src/utils/request.ts b/packages/core/src/utils/request.ts index f9462807e22c..a566c8dec6a8 100644 --- a/packages/core/src/utils/request.ts +++ b/packages/core/src/utils/request.ts @@ -128,6 +128,9 @@ function getAbsoluteUrl({ return undefined; } +// "-user" because otherwise it would match "user-agent" +const SENSITIVE_HEADER_SNIPPETS = ['auth', 'token', 'secret', 'cookie', '-user', 'password', 'key']; + /** * Converts incoming HTTP request headers to OpenTelemetry span attributes following semantic conventions. * Header names are converted to the format: http.request.header. @@ -137,13 +140,20 @@ function getAbsoluteUrl({ */ export function httpHeadersToSpanAttributes( headers: Record, + sendDefaultPii: boolean = false, ): Record { const spanAttributes: Record = {}; try { Object.entries(headers).forEach(([key, value]) => { if (value !== undefined) { - const normalizedKey = `http.request.header.${key.toLowerCase().replace(/-/g, '_')}`; + const lowerCasedKey = key.toLowerCase(); + + if (!sendDefaultPii && SENSITIVE_HEADER_SNIPPETS.some(snippet => lowerCasedKey.includes(snippet))) { + return; + } + + const normalizedKey = `http.request.header.${lowerCasedKey.replace(/-/g, '_')}`; if (Array.isArray(value)) { const stringValues = value.filter((v): v is string => typeof v === 'string'); diff --git a/packages/core/test/lib/utils/request.test.ts b/packages/core/test/lib/utils/request.test.ts index d10f8d84ca8e..3017f55aee76 100644 --- a/packages/core/test/lib/utils/request.test.ts +++ b/packages/core/test/lib/utils/request.test.ts @@ -594,5 +594,97 @@ describe('request utils', () => { 'http.request.header.string_header': ['valid-value'], }); }); + + describe('PII filtering', () => { + it('filters out sensitive headers when sendDefaultPii is false (default)', () => { + const headers = { + 'Content-Type': 'application/json', + 'User-Agent': 'test-agent', + Authorization: 'Bearer secret-token', + Cookie: 'session=abc123', + 'X-API-Key': 'api-key-123', + 'X-Auth-Token': 'auth-token-456', + }; + + const result = httpHeadersToSpanAttributes(headers, false); + + expect(result).toEqual({ + 'http.request.header.content_type': ['application/json'], + 'http.request.header.user_agent': ['test-agent'], + // Sensitive headers should be filtered out + }); + }); + + it('includes sensitive headers when sendDefaultPii is true', () => { + const headers = { + 'Content-Type': 'application/json', + 'User-Agent': 'test-agent', + Authorization: 'Bearer secret-token', + Cookie: 'session=abc123', + 'X-API-Key': 'api-key-123', + }; + + const result = httpHeadersToSpanAttributes(headers, true); + + expect(result).toEqual({ + 'http.request.header.content_type': ['application/json'], + 'http.request.header.user_agent': ['test-agent'], + 'http.request.header.cookie': ['session=abc123'], + 'http.request.header.x_api_key': ['api-key-123'], + }); + }); + + it('filters sensitive headers case-insensitively', () => { + const headers = { + AUTHORIZATION: 'Bearer secret-token', + Cookie: 'session=abc123', + 'x-api-key': 'key-123', + 'Content-Type': 'application/json', + }; + + const result = httpHeadersToSpanAttributes(headers, false); + + expect(result).toEqual({ + 'http.request.header.content_type': ['application/json'], + }); + }); + + it('filters comprehensive list of sensitive headers', () => { + const headers = { + 'Content-Type': 'application/json', + 'User-Agent': 'test-agent', + Accept: 'application/json', + Host: 'example.com', + + // Should be filtered + Authorization: 'Bearer token', + Cookie: 'session=123', + 'Set-Cookie': 'session=456', + 'X-API-Key': 'key', + 'X-Auth-Token': 'token', + 'X-Secret': 'secret', + 'x-secret-key': 'another-secret', + 'WWW-Authenticate': 'Basic', + 'Proxy-Authorization': 'Basic auth', + 'X-Access-Token': 'access', + 'X-CSRF-Token': 'csrf', + 'X-XSRF-Token': 'xsrf', + 'X-Session-Token': 'session', + 'X-Password': 'password', + 'X-Private-Key': 'private', + 'X-Forwarded-user': 'user', + 'X-Forwarded-authorization': 'auth', + }; + + const result = httpHeadersToSpanAttributes(headers, false); + + expect(result).toEqual({ + 'http.request.header.content_type': ['application/json'], + 'http.request.header.user_agent': ['test-agent'], + 'http.request.header.accept': ['application/json'], + 'http.request.header.host': ['example.com'], + }); + }); + }); }); }); diff --git a/packages/node/src/integrations/http.ts b/packages/node/src/integrations/http.ts index 5bf4502e4a85..65d307d32734 100644 --- a/packages/node/src/integrations/http.ts +++ b/packages/node/src/integrations/http.ts @@ -321,7 +321,9 @@ function getConfigWithDefaults(options: Partial = {}): HttpInstrume // Extract headers for incoming requests if (!_isClientRequest(req)) { - const headerAttributes = httpHeadersToSpanAttributes(req.headers); + const client = getClient(); + const sendDefaultPii = client?.getOptions().sendDefaultPii ?? false; + const headerAttributes = httpHeadersToSpanAttributes(req.headers, sendDefaultPii); span.setAttributes(headerAttributes); } diff --git a/packages/remix/src/server/instrumentServer.ts b/packages/remix/src/server/instrumentServer.ts index 418a79dadd2e..109c3e0f3672 100644 --- a/packages/remix/src/server/instrumentServer.ts +++ b/packages/remix/src/server/instrumentServer.ts @@ -326,7 +326,10 @@ function wrapRequestHandler ServerBuild | Promise [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source, [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.server', method: request.method, - ...httpHeadersToSpanAttributes(winterCGHeadersToDict(request.headers)), + ...httpHeadersToSpanAttributes( + winterCGHeadersToDict(request.headers), + clientOptions.sendDefaultPii ?? false, + ), }, }, async span => { diff --git a/packages/sveltekit/src/server-common/handle.ts b/packages/sveltekit/src/server-common/handle.ts index 3d9963bd1056..26872a0f6f24 100644 --- a/packages/sveltekit/src/server-common/handle.ts +++ b/packages/sveltekit/src/server-common/handle.ts @@ -3,6 +3,7 @@ import { continueTrace, debug, flushIfServerless, + getClient, getCurrentScope, getDefaultIsolationScope, getIsolationScope, @@ -178,7 +179,10 @@ async function instrumentHandle( [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.sveltekit', [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: routeName ? 'route' : 'url', 'sveltekit.tracing.original_name': originalName, - ...httpHeadersToSpanAttributes(winterCGHeadersToDict(event.request.headers)), + ...httpHeadersToSpanAttributes( + winterCGHeadersToDict(event.request.headers), + getClient()?.getOptions().sendDefaultPii ?? false, + ), }); } @@ -204,7 +208,10 @@ async function instrumentHandle( [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.sveltekit', [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: routeId ? 'route' : 'url', 'http.method': event.request.method, - ...httpHeadersToSpanAttributes(winterCGHeadersToDict(event.request.headers)), + ...httpHeadersToSpanAttributes( + winterCGHeadersToDict(event.request.headers), + getClient()?.getOptions().sendDefaultPii ?? false, + ), }, name: routeName, }, From c79ca58d11140a2c6c1d8358739d9fb9244d494c Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Mon, 1 Sep 2025 10:33:10 +0200 Subject: [PATCH 04/24] fix prettier --- packages/astro/src/server/middleware.ts | 5 ++++- packages/core/src/utils/request.ts | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/astro/src/server/middleware.ts b/packages/astro/src/server/middleware.ts index 05c543c4e5c6..eaadf9ac852b 100644 --- a/packages/astro/src/server/middleware.ts +++ b/packages/astro/src/server/middleware.ts @@ -156,7 +156,10 @@ async function instrumentRequest( [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source, method, url: stripUrlQueryAndFragment(ctx.url.href), - ...httpHeadersToSpanAttributes(winterCGHeadersToDict(request.headers), getClient()?.getOptions().sendDefaultPii ?? false), + ...httpHeadersToSpanAttributes( + winterCGHeadersToDict(request.headers), + getClient()?.getOptions().sendDefaultPii ?? false, + ), }; if (ctx.url.search) { diff --git a/packages/core/src/utils/request.ts b/packages/core/src/utils/request.ts index a566c8dec6a8..345d0168cfb7 100644 --- a/packages/core/src/utils/request.ts +++ b/packages/core/src/utils/request.ts @@ -146,7 +146,7 @@ export function httpHeadersToSpanAttributes( try { Object.entries(headers).forEach(([key, value]) => { - if (value !== undefined) { + if (value) { const lowerCasedKey = key.toLowerCase(); if (!sendDefaultPii && SENSITIVE_HEADER_SNIPPETS.some(snippet => lowerCasedKey.includes(snippet))) { From 7f69303d3bb75a67d910fcf3514fb8bebeaf2b33 Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Mon, 1 Sep 2025 10:49:57 +0200 Subject: [PATCH 05/24] fix tests and nextjs --- .../nestjs-11/tests/transactions.test.ts | 7 +++++++ .../nestjs-8/tests/transactions.test.ts | 7 +++++++ .../nestjs-basic/tests/transactions.test.ts | 7 +++++++ .../nestjs-fastify/tests/transactions.test.ts | 7 +++++++ .../tests/transactions.test.ts | 7 +++++++ .../nestjs-with-submodules/tests/transactions.test.ts | 7 +++++++ .../node-connect/tests/transactions.test.ts | 7 +++++++ .../tests/transactions.test.ts | 7 +++++++ .../tests/transactions.test.ts | 7 +++++++ .../tests/transactions.test.ts | 7 +++++++ .../tests/transactions.test.ts | 7 +++++++ .../node-express-v5/tests/transactions.test.ts | 7 +++++++ .../node-express/tests/transactions.test.ts | 7 +++++++ .../node-fastify-3/tests/transactions.test.ts | 7 +++++++ .../node-fastify-4/tests/transactions.test.ts | 7 +++++++ .../node-fastify-5/tests/transactions.test.ts | 7 +++++++ .../node-koa/tests/transactions.test.ts | 7 +++++++ .../node-otel-sdk-node/tests/transactions.test.ts | 7 +++++++ .../node-otel/tests/transactions.test.ts | 7 +++++++ .../tsx-express/tests/transactions.test.ts | 7 +++++++ packages/nextjs/src/server/index.ts | 10 ++++++++++ 21 files changed, 150 insertions(+) diff --git a/dev-packages/e2e-tests/test-applications/nestjs-11/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-11/tests/transactions.test.ts index 1209eae1ada9..6a57fa737ca0 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-11/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-11/tests/transactions.test.ts @@ -38,6 +38,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-transaction', + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate'], + 'http.request.header.accept_language': ['*'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': [expect.any(String)], + 'http.request.header.sec_fetch_mode': ['cors'], + 'http.request.header.user_agent': ['node'], }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/nestjs-8/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-8/tests/transactions.test.ts index be0e03cdbc97..9d32675d1b31 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-8/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-8/tests/transactions.test.ts @@ -38,6 +38,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-transaction', + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate'], + 'http.request.header.accept_language': ['*'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': [expect.any(String)], + 'http.request.header.sec_fetch_mode': ['cors'], + 'http.request.header.user_agent': ['node'], }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/transactions.test.ts index c37eb8da7cc1..f32be11f0eaf 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/transactions.test.ts @@ -38,6 +38,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-transaction', + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate'], + 'http.request.header.accept_language': ['*'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': [expect.any(String)], + 'http.request.header.sec_fetch_mode': ['cors'], + 'http.request.header.user_agent': ['node'], }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts index ac4c8bdea83e..a1c0660d799c 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts @@ -38,6 +38,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-transaction', + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate'], + 'http.request.header.accept_language': ['*'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': [expect.any(String)], + 'http.request.header.sec_fetch_mode': ['cors'], + 'http.request.header.user_agent': ['node'], }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/tests/transactions.test.ts index 5fb9c98ffc66..587cb9534c1f 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/tests/transactions.test.ts @@ -38,6 +38,13 @@ test('Sends an API route transaction from module', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/example-module/transaction', + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate'], + 'http.request.header.accept_language': ['*'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': [expect.any(String)], + 'http.request.header.sec_fetch_mode': ['cors'], + 'http.request.header.user_agent': ['node'], }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/transactions.test.ts index b95aa1fe4406..9fdc4aba5e8b 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/transactions.test.ts @@ -38,6 +38,13 @@ test('Sends an API route transaction from module', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/example-module/transaction', + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate'], + 'http.request.header.accept_language': ['*'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': [expect.any(String)], + 'http.request.header.sec_fetch_mode': ['cors'], + 'http.request.header.user_agent': ['node'], }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-connect/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-connect/tests/transactions.test.ts index ec02acca77d6..e7ed0723256e 100644 --- a/dev-packages/e2e-tests/test-applications/node-connect/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-connect/tests/transactions.test.ts @@ -39,6 +39,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-transaction', + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate'], + 'http.request.header.accept_language': ['*'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': [expect.any(String)], + 'http.request.header.sec_fetch_mode': ['cors'], + 'http.request.header.user_agent': ['node'], }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1-sdk-node/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1-sdk-node/tests/transactions.test.ts index 6141261d8954..fd0bc7d8dda7 100644 --- a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1-sdk-node/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1-sdk-node/tests/transactions.test.ts @@ -49,6 +49,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'net.peer.port': expect.any(Number), 'http.status_code': 200, 'http.status_text': 'OK', + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate'], + 'http.request.header.accept_language': ['*'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': [expect.any(String)], + 'http.request.header.sec_fetch_mode': ['cors'], + 'http.request.header.user_agent': ['node'], }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1/tests/transactions.test.ts index 1628a9a03ada..c98c21f74594 100644 --- a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1/tests/transactions.test.ts @@ -37,6 +37,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'net.peer.port': expect.any(Number), 'http.status_code': 200, 'http.status_text': 'OK', + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate'], + 'http.request.header.accept_language': ['*'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': [expect.any(String)], + 'http.request.header.sec_fetch_mode': ['cors'], + 'http.request.header.user_agent': ['node'], }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-sdk-node/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-sdk-node/tests/transactions.test.ts index 08c8f80cd9f0..740acee0d959 100644 --- a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-sdk-node/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-sdk-node/tests/transactions.test.ts @@ -49,6 +49,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'net.peer.port': expect.any(Number), 'http.status_code': 200, 'http.status_text': 'OK', + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate'], + 'http.request.header.accept_language': ['*'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': [expect.any(String)], + 'http.request.header.sec_fetch_mode': ['cors'], + 'http.request.header.user_agent': ['node'], }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2/tests/transactions.test.ts index f3b1b680f2e9..853512b1d2ff 100644 --- a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2/tests/transactions.test.ts @@ -37,6 +37,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'net.peer.port': expect.any(Number), 'http.status_code': 200, 'http.status_text': 'OK', + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate'], + 'http.request.header.accept_language': ['*'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': [expect.any(String)], + 'http.request.header.sec_fetch_mode': ['cors'], + 'http.request.header.user_agent': ['node'], }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-express-v5/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-express-v5/tests/transactions.test.ts index 86fdffd3b452..1a2273b265af 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-v5/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express-v5/tests/transactions.test.ts @@ -38,6 +38,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-transaction', + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate'], + 'http.request.header.accept_language': ['*'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': [expect.any(String)], + 'http.request.header.sec_fetch_mode': ['cors'], + 'http.request.header.user_agent': ['node'], }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts index 8483e1e71735..8007b87e2640 100644 --- a/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts @@ -38,6 +38,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-transaction', + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate'], + 'http.request.header.accept_language': ['*'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': [expect.any(String)], + 'http.request.header.sec_fetch_mode': ['cors'], + 'http.request.header.user_agent': ['node'], }), op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/transactions.test.ts index d4c10751f4a5..5049950eefef 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/transactions.test.ts @@ -38,6 +38,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-transaction', + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate'], + 'http.request.header.accept_language': ['*'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': [expect.any(String)], + 'http.request.header.sec_fetch_mode': ['cors'], + 'http.request.header.user_agent': ['node'], }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-4/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify-4/tests/transactions.test.ts index 1f049b802bca..3cccd7ec9f1b 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-4/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-fastify-4/tests/transactions.test.ts @@ -38,6 +38,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-transaction', + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate'], + 'http.request.header.accept_language': ['*'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': [expect.any(String)], + 'http.request.header.sec_fetch_mode': ['cors'], + 'http.request.header.user_agent': ['node'], }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/transactions.test.ts index e148c8158cd8..0d3e0f8830f6 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/transactions.test.ts @@ -38,6 +38,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-transaction', + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate'], + 'http.request.header.accept_language': ['*'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': [expect.any(String)], + 'http.request.header.sec_fetch_mode': ['cors'], + 'http.request.header.user_agent': ['node'], }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-koa/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-koa/tests/transactions.test.ts index 966dbc5937e3..05eecec24578 100644 --- a/dev-packages/e2e-tests/test-applications/node-koa/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-koa/tests/transactions.test.ts @@ -38,6 +38,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-transaction', + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate'], + 'http.request.header.accept_language': ['*'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': [expect.any(String)], + 'http.request.header.sec_fetch_mode': ['cors'], + 'http.request.header.user_agent': ['node'], }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/tests/transactions.test.ts index 3e12007c0d75..82d19e92330e 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/tests/transactions.test.ts @@ -50,6 +50,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-transaction', + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate'], + 'http.request.header.accept_language': ['*'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': [expect.any(String)], + 'http.request.header.sec_fetch_mode': ['cors'], + 'http.request.header.user_agent': ['node'], }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-otel/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-otel/tests/transactions.test.ts index c6abde474439..38e37100241e 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-otel/tests/transactions.test.ts @@ -50,6 +50,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-transaction', + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate'], + 'http.request.header.accept_language': ['*'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': [expect.any(String)], + 'http.request.header.sec_fetch_mode': ['cors'], + 'http.request.header.user_agent': ['node'], }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/tsx-express/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/tsx-express/tests/transactions.test.ts index 2a0ca499d02c..9dff721875ab 100644 --- a/dev-packages/e2e-tests/test-applications/tsx-express/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/tsx-express/tests/transactions.test.ts @@ -38,6 +38,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-transaction', + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate'], + 'http.request.header.accept_language': ['*'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': [expect.any(String)], + 'http.request.header.sec_fetch_mode': ['cors'], + 'http.request.header.user_agent': ['node'], }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/packages/nextjs/src/server/index.ts b/packages/nextjs/src/server/index.ts index 82d475a719c6..feb5af4f3dfa 100644 --- a/packages/nextjs/src/server/index.ts +++ b/packages/nextjs/src/server/index.ts @@ -18,6 +18,7 @@ import { getIsolationScope, getRootSpan, GLOBAL_OBJ, + httpHeadersToSpanAttributes, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, @@ -200,6 +201,15 @@ export function init(options: NodeOptions): NodeClient | undefined { } setCapturedScopesOnSpan(span, scope, isolationScope); + + const sendDefaultPii = opts.sendDefaultPii ?? false; + const sdkProcessingMetadata = isolationScope.getScopeData().sdkProcessingMetadata; + const normalizedRequest = sdkProcessingMetadata?.normalizedRequest; + + if (normalizedRequest?.headers) { + const headerAttributes = httpHeadersToSpanAttributes(normalizedRequest.headers, sendDefaultPii); + span.setAttributes(headerAttributes); + } } }); From bbcec8761cb4b4abb5ba7badf8f517e559166175 Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Mon, 1 Sep 2025 11:04:49 +0200 Subject: [PATCH 06/24] fix tests --- .../nestjs-distributed-tracing/tests/propagation.test.ts | 7 +++++++ .../node-fastify-3/tests/propagation.test.ts | 7 +++++++ .../node-fastify-4/tests/propagation.test.ts | 7 +++++++ .../node-fastify-5/tests/propagation.test.ts | 7 +++++++ .../test-applications/node-hapi/tests/transactions.test.ts | 7 +++++++ .../node-otel-custom-sampler/tests/sampling.test.ts | 7 +++++++ packages/aws-serverless/src/index.ts | 2 ++ packages/bun/src/index.ts | 2 ++ packages/core/test/lib/utils/request.test.ts | 5 +++-- packages/google-cloud-serverless/src/index.ts | 2 ++ 10 files changed, 51 insertions(+), 2 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tests/propagation.test.ts index 78dfe680a453..02e90f70276c 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tests/propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tests/propagation.test.ts @@ -75,6 +75,13 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-outgoing-http/:id', + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate'], + 'http.request.header.accept_language': ['*'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': ['localhost:3030'], + 'http.request.header.sec_fetch_mode': ['cors'], + 'http.request.header.user_agent': ['node'], }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/propagation.test.ts index ee097817bafb..cbb314821fda 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/propagation.test.ts @@ -75,6 +75,13 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-outgoing-http/:id', + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate'], + 'http.request.header.accept_language': ['*'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': ['localhost:3030'], + 'http.request.header.sec_fetch_mode': ['cors'], + 'http.request.header.user_agent': ['node'], }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-4/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify-4/tests/propagation.test.ts index 3746687b92c1..15a6fc4ed503 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-4/tests/propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-fastify-4/tests/propagation.test.ts @@ -75,6 +75,13 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-outgoing-http/:id', + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate'], + 'http.request.header.accept_language': ['*'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': ['localhost:3030'], + 'http.request.header.sec_fetch_mode': ['cors'], + 'http.request.header.user_agent': ['node'], }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/propagation.test.ts index 86c899fdc71f..a514533f882a 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/propagation.test.ts @@ -75,6 +75,13 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-outgoing-http/:id', + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate'], + 'http.request.header.accept_language': ['*'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': ['localhost:3030'], + 'http.request.header.sec_fetch_mode': ['cors'], + 'http.request.header.user_agent': ['node'], }), op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-hapi/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-hapi/tests/transactions.test.ts index 3f332992d0e7..2284fa997f0b 100644 --- a/dev-packages/e2e-tests/test-applications/node-hapi/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-hapi/tests/transactions.test.ts @@ -37,6 +37,13 @@ test('Sends successful transaction', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-success', + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate'], + 'http.request.header.accept_language': ['*'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': ['localhost:3030'], + 'http.request.header.sec_fetch_mode': ['cors'], + 'http.request.header.user_agent': ['node'], }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/tests/sampling.test.ts b/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/tests/sampling.test.ts index 5ca9077634d2..ecfb3f042894 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/tests/sampling.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/tests/sampling.test.ts @@ -36,6 +36,13 @@ test('Sends a sampled API route transaction', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/task', + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate'], + 'http.request.header.accept_language': ['*'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': ['localhost:3030'], + 'http.request.header.sec_fetch_mode': ['cors'], + 'http.request.header.user_agent': ['node'], }, origin: 'auto.http.otel.http', op: 'http.server', diff --git a/packages/aws-serverless/src/index.ts b/packages/aws-serverless/src/index.ts index 7d7455d496bb..a041e0a7231f 100644 --- a/packages/aws-serverless/src/index.ts +++ b/packages/aws-serverless/src/index.ts @@ -42,6 +42,8 @@ export { close, getSentryRelease, createGetModuleFromFilename, + httpHeadersToSpanAttributes, + winterCGHeadersToDict, // eslint-disable-next-line deprecation/deprecation anrIntegration, // eslint-disable-next-line deprecation/deprecation diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index ec092bcdbbba..e0ce86b1bd23 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -62,6 +62,8 @@ export { close, getSentryRelease, createGetModuleFromFilename, + httpHeadersToSpanAttributes, + winterCGHeadersToDict, // eslint-disable-next-line deprecation/deprecation anrIntegration, // eslint-disable-next-line deprecation/deprecation diff --git a/packages/core/test/lib/utils/request.test.ts b/packages/core/test/lib/utils/request.test.ts index 3017f55aee76..83efe4e7f4a5 100644 --- a/packages/core/test/lib/utils/request.test.ts +++ b/packages/core/test/lib/utils/request.test.ts @@ -541,14 +541,14 @@ describe('request utils', () => { it('handles multiple values for the same header', () => { const headers = { - Cookie: ['session=abc123', 'preferences=dark-mode'], + 'x-random-header': ['test=abc123', 'preferences=dark-mode', 'number=three'], Accept: ['application/json', 'text/html'], }; const result = httpHeadersToSpanAttributes(headers); expect(result).toEqual({ - 'http.request.header.cookie': ['session=abc123', 'preferences=dark-mode'], + 'http.request.header.x_random_header': ['test=abc123', 'preferences=dark-mode', 'number=three'], 'http.request.header.accept': ['application/json', 'text/html'], }); }); @@ -629,6 +629,7 @@ describe('request utils', () => { expect(result).toEqual({ 'http.request.header.content_type': ['application/json'], 'http.request.header.user_agent': ['test-agent'], + 'http.request.header.authorization': ['Bearer secret-token'], 'http.request.header.cookie': ['session=abc123'], 'http.request.header.x_api_key': ['api-key-123'], }); diff --git a/packages/google-cloud-serverless/src/index.ts b/packages/google-cloud-serverless/src/index.ts index 0b76f7776772..83292ab11e46 100644 --- a/packages/google-cloud-serverless/src/index.ts +++ b/packages/google-cloud-serverless/src/index.ts @@ -42,6 +42,8 @@ export { close, getSentryRelease, createGetModuleFromFilename, + httpHeadersToSpanAttributes, + winterCGHeadersToDict, // eslint-disable-next-line deprecation/deprecation anrIntegration, // eslint-disable-next-line deprecation/deprecation From 1db7f939b12598bf68753bac17cbfaf29f7cf946 Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Mon, 1 Sep 2025 12:14:54 +0200 Subject: [PATCH 07/24] add headers in nextjs and node-core --- .../wrapApiHandlerWithSentry.ts | 2 ++ .../src/common/utils/headersToAttributes.ts | 30 +++++++++++++++++++ .../wrapGenerationFunctionWithSentry.ts | 6 ++++ .../src/common/wrapMiddlewareWithSentry.ts | 6 ++++ .../src/common/wrapRouteHandlerWithSentry.ts | 6 ++++ .../common/wrapServerComponentWithSentry.ts | 6 ++++ .../src/edge/wrapApiHandlerWithSentry.ts | 7 +++++ packages/nextjs/src/server/index.ts | 9 ------ .../integrations/http/incoming-requests.ts | 8 ++++- packages/opentelemetry/src/spanProcessor.ts | 15 ++++++++++ 10 files changed, 85 insertions(+), 10 deletions(-) create mode 100644 packages/nextjs/src/common/utils/headersToAttributes.ts diff --git a/packages/nextjs/src/common/pages-router-instrumentation/wrapApiHandlerWithSentry.ts b/packages/nextjs/src/common/pages-router-instrumentation/wrapApiHandlerWithSentry.ts index ba50778d30ad..9c82bf88cb88 100644 --- a/packages/nextjs/src/common/pages-router-instrumentation/wrapApiHandlerWithSentry.ts +++ b/packages/nextjs/src/common/pages-router-instrumentation/wrapApiHandlerWithSentry.ts @@ -15,6 +15,7 @@ import { } from '@sentry/core'; import type { NextApiRequest } from 'next'; import type { AugmentedNextApiResponse, NextApiHandler } from '../types'; +import { addHeadersAsAttributes } from '../utils/headersToAttributes'; import { flushSafelyWithTimeout } from '../utils/responseEnd'; import { dropNextjsRootContext, escapeNextjsTracing } from '../utils/tracingUtils'; @@ -87,6 +88,7 @@ export function wrapApiHandlerWithSentry(apiHandler: NextApiHandler, parameteriz attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.nextjs', + ...addHeadersAsAttributes(normalizedRequest.headers || {}), }, }, async span => { diff --git a/packages/nextjs/src/common/utils/headersToAttributes.ts b/packages/nextjs/src/common/utils/headersToAttributes.ts new file mode 100644 index 000000000000..ea63e04e786c --- /dev/null +++ b/packages/nextjs/src/common/utils/headersToAttributes.ts @@ -0,0 +1,30 @@ +import type { Span, WebFetchHeaders } from '@sentry/core'; +import { getClient, httpHeadersToSpanAttributes, winterCGHeadersToDict } from '@sentry/core'; + +/** + * Extracts HTTP request headers as span attributes and optionally applies them to a span. + */ +export function addHeadersAsAttributes( + headers: WebFetchHeaders | Headers | Record | undefined, + span?: Span, +): Record { + if (!headers) { + return {}; + } + + const client = getClient(); + const sendDefaultPii = client?.getOptions().sendDefaultPii ?? false; + + const headersDict: Record = + headers instanceof Headers || (typeof headers === 'object' && 'get' in headers) + ? winterCGHeadersToDict(headers as Headers) + : headers; + + const headerAttributes = httpHeadersToSpanAttributes(headersDict, sendDefaultPii); + + if (span) { + span.setAttributes(headerAttributes); + } + + return headerAttributes; +} diff --git a/packages/nextjs/src/common/wrapGenerationFunctionWithSentry.ts b/packages/nextjs/src/common/wrapGenerationFunctionWithSentry.ts index 2067ebccc245..201e20067694 100644 --- a/packages/nextjs/src/common/wrapGenerationFunctionWithSentry.ts +++ b/packages/nextjs/src/common/wrapGenerationFunctionWithSentry.ts @@ -22,6 +22,7 @@ import { import type { GenerationFunctionContext } from '../common/types'; import { isNotFoundNavigationError, isRedirectNavigationError } from './nextNavigationErrorUtils'; import { TRANSACTION_ATTR_SENTRY_TRACE_BACKFILL } from './span-attributes-with-logic-attached'; +import { addHeadersAsAttributes } from './utils/headersToAttributes'; import { commonObjectToIsolationScope, commonObjectToPropagationContext } from './utils/tracingUtils'; import { getSanitizedRequestUrl } from './utils/urls'; import { maybeExtractSynchronousParamsAndSearchParams } from './utils/wrapperUtils'; @@ -63,6 +64,11 @@ export function wrapGenerationFunctionWithSentry a const headersDict = headers ? winterCGHeadersToDict(headers) : undefined; + if (activeSpan) { + const rootSpan = getRootSpan(activeSpan); + addHeadersAsAttributes(headers, rootSpan); + } + let data: Record | undefined = undefined; if (getClient()?.getOptions().sendDefaultPii) { const props: unknown = args[0]; diff --git a/packages/nextjs/src/common/wrapMiddlewareWithSentry.ts b/packages/nextjs/src/common/wrapMiddlewareWithSentry.ts index 3a9ca786d697..950c2fc74a8e 100644 --- a/packages/nextjs/src/common/wrapMiddlewareWithSentry.ts +++ b/packages/nextjs/src/common/wrapMiddlewareWithSentry.ts @@ -13,6 +13,7 @@ import { winterCGRequestToRequestData, withIsolationScope, } from '@sentry/core'; +import { addHeadersAsAttributes } from '../common/utils/headersToAttributes'; import { flushSafelyWithTimeout } from '../common/utils/responseEnd'; import type { EdgeRouteHandler } from '../edge/types'; @@ -59,6 +60,7 @@ export function wrapMiddlewareWithSentry( let spanName: string; let spanSource: TransactionSource; + let headerAttributes: Record = {}; if (req instanceof Request) { isolationScope.setSDKProcessingMetadata({ @@ -66,6 +68,8 @@ export function wrapMiddlewareWithSentry( }); spanName = `middleware ${req.method} ${new URL(req.url).pathname}`; spanSource = 'url'; + + headerAttributes = addHeadersAsAttributes(req.headers); } else { spanName = 'middleware'; spanSource = 'component'; @@ -84,6 +88,7 @@ export function wrapMiddlewareWithSentry( const rootSpan = getRootSpan(activeSpan); if (rootSpan) { setCapturedScopesOnSpan(rootSpan, currentScope, isolationScope); + rootSpan.setAttributes(headerAttributes); } } @@ -94,6 +99,7 @@ export function wrapMiddlewareWithSentry( attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: spanSource, [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs.wrapMiddlewareWithSentry', + ...headerAttributes, }, }, () => { diff --git a/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts b/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts index e1a2238b05a1..1d258a8ae6f6 100644 --- a/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts +++ b/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts @@ -19,6 +19,7 @@ import { } from '@sentry/core'; import { isNotFoundNavigationError, isRedirectNavigationError } from './nextNavigationErrorUtils'; import type { RouteHandlerContext } from './types'; +import { addHeadersAsAttributes } from './utils/headersToAttributes'; import { flushSafelyWithTimeout } from './utils/responseEnd'; import { commonObjectToIsolationScope } from './utils/tracingUtils'; @@ -39,6 +40,10 @@ export function wrapRouteHandlerWithSentry any>( const activeSpan = getActiveSpan(); const rootSpan = activeSpan ? getRootSpan(activeSpan) : undefined; + if (rootSpan && process.env.NEXT_RUNTIME !== 'edge') { + addHeadersAsAttributes(headers, rootSpan); + } + let edgeRuntimeIsolationScopeOverride: Scope | undefined; if (rootSpan && process.env.NEXT_RUNTIME === 'edge') { const isolationScope = commonObjectToIsolationScope(headers); @@ -50,6 +55,7 @@ export function wrapRouteHandlerWithSentry any>( rootSpan.updateName(`${method} ${parameterizedRoute}`); rootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); rootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'http.server'); + addHeadersAsAttributes(headers, rootSpan); } return withIsolationScope( diff --git a/packages/nextjs/src/common/wrapServerComponentWithSentry.ts b/packages/nextjs/src/common/wrapServerComponentWithSentry.ts index 9dd097cb75ae..0bddaeadff9c 100644 --- a/packages/nextjs/src/common/wrapServerComponentWithSentry.ts +++ b/packages/nextjs/src/common/wrapServerComponentWithSentry.ts @@ -24,6 +24,7 @@ import { isNotFoundNavigationError, isRedirectNavigationError } from '../common/ import type { ServerComponentContext } from '../common/types'; import { flushSafelyWithTimeout } from '../common/utils/responseEnd'; import { TRANSACTION_ATTR_SENTRY_TRACE_BACKFILL } from './span-attributes-with-logic-attached'; +import { addHeadersAsAttributes } from './utils/headersToAttributes'; import { commonObjectToIsolationScope, commonObjectToPropagationContext } from './utils/tracingUtils'; import { getSanitizedRequestUrl } from './utils/urls'; import { maybeExtractSynchronousParamsAndSearchParams } from './utils/wrapperUtils'; @@ -61,6 +62,11 @@ export function wrapServerComponentWithSentry any> const headersDict = context.headers ? winterCGHeadersToDict(context.headers) : undefined; + if (activeSpan) { + const rootSpan = getRootSpan(activeSpan); + addHeadersAsAttributes(context.headers, rootSpan); + } + let params: Record | undefined = undefined; if (getClient()?.getOptions().sendDefaultPii) { diff --git a/packages/nextjs/src/edge/wrapApiHandlerWithSentry.ts b/packages/nextjs/src/edge/wrapApiHandlerWithSentry.ts index 466eb19eb1d1..378925f74d0c 100644 --- a/packages/nextjs/src/edge/wrapApiHandlerWithSentry.ts +++ b/packages/nextjs/src/edge/wrapApiHandlerWithSentry.ts @@ -13,6 +13,7 @@ import { winterCGRequestToRequestData, withIsolationScope, } from '@sentry/core'; +import { addHeadersAsAttributes } from '../common/utils/headersToAttributes'; import { flushSafelyWithTimeout } from '../common/utils/responseEnd'; import type { EdgeRouteHandler } from './types'; @@ -31,11 +32,15 @@ export function wrapApiHandlerWithSentry( const req: unknown = args[0]; const currentScope = getCurrentScope(); + let headerAttributes: Record = {}; + if (req instanceof Request) { isolationScope.setSDKProcessingMetadata({ normalizedRequest: winterCGRequestToRequestData(req), }); currentScope.setTransactionName(`${req.method} ${parameterizedRoute}`); + + headerAttributes = addHeadersAsAttributes(req.headers); } else { currentScope.setTransactionName(`handler (${parameterizedRoute})`); } @@ -58,6 +63,7 @@ export function wrapApiHandlerWithSentry( rootSpan.setAttributes({ [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.server', [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + ...headerAttributes, }); setCapturedScopesOnSpan(rootSpan, currentScope, isolationScope); } @@ -74,6 +80,7 @@ export function wrapApiHandlerWithSentry( attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs.wrapApiHandlerWithSentry', + ...headerAttributes, }, }, () => { diff --git a/packages/nextjs/src/server/index.ts b/packages/nextjs/src/server/index.ts index feb5af4f3dfa..ad762e6b6afb 100644 --- a/packages/nextjs/src/server/index.ts +++ b/packages/nextjs/src/server/index.ts @@ -201,15 +201,6 @@ export function init(options: NodeOptions): NodeClient | undefined { } setCapturedScopesOnSpan(span, scope, isolationScope); - - const sendDefaultPii = opts.sendDefaultPii ?? false; - const sdkProcessingMetadata = isolationScope.getScopeData().sdkProcessingMetadata; - const normalizedRequest = sdkProcessingMetadata?.normalizedRequest; - - if (normalizedRequest?.headers) { - const headerAttributes = httpHeadersToSpanAttributes(normalizedRequest.headers, sendDefaultPii); - span.setAttributes(headerAttributes); - } } }); diff --git a/packages/node-core/src/integrations/http/incoming-requests.ts b/packages/node-core/src/integrations/http/incoming-requests.ts index 2d18d1c064c4..9de162a3dbff 100644 --- a/packages/node-core/src/integrations/http/incoming-requests.ts +++ b/packages/node-core/src/integrations/http/incoming-requests.ts @@ -7,6 +7,7 @@ import { getClient, getCurrentScope, getIsolationScope, + httpHeadersToSpanAttributes, httpRequestToRequestData, stripUrlQueryAndFragment, withIsolationScope, @@ -70,8 +71,13 @@ export function instrumentServer( patchRequestToCaptureBody(request, isolationScope, maxIncomingRequestBodySize); } + // Extract HTTP request headers as span attributes + const client = getClient(); + const sendDefaultPii = client?.getOptions().sendDefaultPii ?? false; + const httpHeaderAttributes = httpHeadersToSpanAttributes(normalizedRequest.headers || {}, sendDefaultPii); + // Update the isolation scope, isolate this request - isolationScope.setSDKProcessingMetadata({ normalizedRequest, ipAddress }); + isolationScope.setSDKProcessingMetadata({ normalizedRequest, ipAddress, httpHeaderAttributes }); // attempt to update the scope's `transactionName` based on the request URL // Ideally, framework instrumentations coming after the HttpInstrumentation diff --git a/packages/opentelemetry/src/spanProcessor.ts b/packages/opentelemetry/src/spanProcessor.ts index 3430456caaee..c871b0db08ab 100644 --- a/packages/opentelemetry/src/spanProcessor.ts +++ b/packages/opentelemetry/src/spanProcessor.ts @@ -9,6 +9,7 @@ import { logSpanEnd, logSpanStart, setCapturedScopesOnSpan, + spanToJSON, } from '@sentry/core'; import { SEMANTIC_ATTRIBUTE_SENTRY_PARENT_IS_REMOTE } from './semanticAttributes'; import { SentrySpanExporter } from './spanExporter'; @@ -47,6 +48,20 @@ function onSpanStart(span: Span, parentContext: Context): void { logSpanStart(span); + // Add HTTP request headers as span attributes for HTTP server spans + if (scopes?.isolationScope) { + const spanData = spanToJSON(span); + // Check if this is an HTTP server span + if (spanData.op === 'http.server' || (spanData.data && 'http.method' in spanData.data)) { + const sdkProcessingMetadata = scopes.isolationScope.getScopeData().sdkProcessingMetadata; + const httpHeaderAttributes = sdkProcessingMetadata?.httpHeaderAttributes as Record; + + if (httpHeaderAttributes && typeof httpHeaderAttributes === 'object') { + span.setAttributes(httpHeaderAttributes); + } + } + } + const client = getClient(); client?.emit('spanStart', span); } From 9ea2643fc33ac33777909197833d7f7a68358ff4 Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Mon, 1 Sep 2025 13:27:43 +0200 Subject: [PATCH 08/24] fix CI problems/tests --- .../nestjs-fastify/tests/transactions.test.ts | 7 +++++++ .../tests/sampling.test.ts | 7 +++++++ .../tests/sampling.test.ts | 7 +++++++ .../node-fastify-3/tests/propagation.test.ts | 11 +++++++++++ .../node-fastify-4/tests/propagation.test.ts | 11 +++++++++++ .../node-hapi/tests/transactions.test.ts | 7 +++++++ packages/astro/test/server/middleware.test.ts | 2 +- packages/nextjs/src/server/index.ts | 1 - 8 files changed, 51 insertions(+), 2 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts index a1c0660d799c..b0372ba2be21 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts @@ -110,6 +110,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'nestjs.controller': 'AppController', 'nestjs.callback': 'testTransaction', url: '/test-transaction', + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate'], + 'http.request.header.accept_language': ['*'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': [expect.any(String)], + 'http.request.header.sec_fetch_mode': ['cors'], + 'http.request.header.user_agent': ['node'], }, description: 'GET /test-transaction', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1-custom-sampler/tests/sampling.test.ts b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1-custom-sampler/tests/sampling.test.ts index 60e2424552cd..9e48289ab21e 100644 --- a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1-custom-sampler/tests/sampling.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1-custom-sampler/tests/sampling.test.ts @@ -35,6 +35,13 @@ test('Sends a sampled API route transaction', async ({ baseURL }) => { 'net.peer.port': expect.any(Number), 'http.status_code': 200, 'http.status_text': 'OK', + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate'], + 'http.request.header.accept_language': ['*'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': ['localhost:3030'], + 'http.request.header.sec_fetch_mode': ['cors'], + 'http.request.header.user_agent': ['node'], }, origin: 'manual', op: 'http.server', diff --git a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-custom-sampler/tests/sampling.test.ts b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-custom-sampler/tests/sampling.test.ts index 134f9f22b429..352573152d51 100644 --- a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-custom-sampler/tests/sampling.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-custom-sampler/tests/sampling.test.ts @@ -35,6 +35,13 @@ test('Sends a sampled API route transaction', async ({ baseURL }) => { 'net.peer.port': expect.any(Number), 'http.status_code': 200, 'http.status_text': 'OK', + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate'], + 'http.request.header.accept_language': ['*'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': ['localhost:3030'], + 'http.request.header.sec_fetch_mode': ['cors'], + 'http.request.header.user_agent': ['node'], }, origin: 'manual', op: 'http.server', diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/propagation.test.ts index cbb314821fda..d66858f51418 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/propagation.test.ts @@ -113,6 +113,10 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-inbound-headers/:id', + 'http.request.header.baggage': [expect.any(String)], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': [expect.any(String)], + 'http.request.header.sentry_trace': [expect.stringContaining(traceId || '')], }, op: 'http.server', parent_span_id: outgoingHttpSpanId, @@ -196,6 +200,13 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-outgoing-fetch/:id', + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate'], + 'http.request.header.accept_language': ['*'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': [expect.any(String)], + 'http.request.header.sec_fetch_mode': ['cors'], + 'http.request.header.user_agent': ['node'], }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-4/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify-4/tests/propagation.test.ts index 15a6fc4ed503..dac9e8cdae4e 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-4/tests/propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-fastify-4/tests/propagation.test.ts @@ -113,6 +113,10 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-inbound-headers/:id', + 'http.request.header.baggage': [expect.any(String)], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': [expect.any(String)], + 'http.request.header.sentry_trace': [expect.stringContaining(traceId || '')], }, op: 'http.server', parent_span_id: outgoingHttpSpanId, @@ -196,6 +200,13 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-outgoing-fetch/:id', + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate'], + 'http.request.header.accept_language': ['*'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': [expect.any(String)], + 'http.request.header.sec_fetch_mode': ['cors'], + 'http.request.header.user_agent': ['node'], }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-hapi/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-hapi/tests/transactions.test.ts index 2284fa997f0b..db28d95ed74d 100644 --- a/dev-packages/e2e-tests/test-applications/node-hapi/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-hapi/tests/transactions.test.ts @@ -72,6 +72,13 @@ test('Sends successful transaction', async ({ baseURL }) => { 'http.route': '/test-success', 'sentry.op': 'router.hapi', 'sentry.origin': 'auto.http.otel.hapi', + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate'], + 'http.request.header.accept_language': ['*'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': [expect.any(String)], + 'http.request.header.sec_fetch_mode': ['cors'], + 'http.request.header.user_agent': ['node'], }, description: 'GET /test-success', op: 'router.hapi', diff --git a/packages/astro/test/server/middleware.test.ts b/packages/astro/test/server/middleware.test.ts index fa6f880d840d..e8635ec7a79d 100644 --- a/packages/astro/test/server/middleware.test.ts +++ b/packages/astro/test/server/middleware.test.ts @@ -32,7 +32,7 @@ describe('sentryMiddleware', () => { } as any; }); vi.spyOn(SentryNode, 'getActiveSpan').mockImplementation(getSpanMock); - vi.spyOn(SentryNode, 'getClient').mockImplementation(() => ({}) as Client); + vi.spyOn(SentryNode, 'getClient').mockImplementation(() => ({ getOptions: () => ({}) }) as Client); vi.spyOn(SentryNode, 'getTraceMetaTags').mockImplementation( () => ` diff --git a/packages/nextjs/src/server/index.ts b/packages/nextjs/src/server/index.ts index ad762e6b6afb..82d475a719c6 100644 --- a/packages/nextjs/src/server/index.ts +++ b/packages/nextjs/src/server/index.ts @@ -18,7 +18,6 @@ import { getIsolationScope, getRootSpan, GLOBAL_OBJ, - httpHeadersToSpanAttributes, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, From 07e834dc0783f389e2c9ce891cd8a05bc1cd4734 Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Mon, 1 Sep 2025 13:43:31 +0200 Subject: [PATCH 09/24] mock setAttributes --- packages/nextjs/test/config/wrappers.test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/nextjs/test/config/wrappers.test.ts b/packages/nextjs/test/config/wrappers.test.ts index c96184df51cf..c61c92026f60 100644 --- a/packages/nextjs/test/config/wrappers.test.ts +++ b/packages/nextjs/test/config/wrappers.test.ts @@ -53,11 +53,14 @@ describe('data-fetching function wrappers should not create manual spans', () => test('wrapped function sets route backfill attribute when called within an active span', async () => { const mockSetAttribute = vi.fn(); + const mockSetAttributes = vi.fn(); const mockGetActiveSpan = vi.spyOn(SentryCore, 'getActiveSpan').mockReturnValue({ setAttribute: mockSetAttribute, + setAttributes: mockSetAttributes, } as any); const mockGetRootSpan = vi.spyOn(SentryCore, 'getRootSpan').mockReturnValue({ setAttribute: mockSetAttribute, + setAttributes: mockSetAttributes, } as any); const origFunction = vi.fn(async () => ({ props: {} })); @@ -72,11 +75,14 @@ describe('data-fetching function wrappers should not create manual spans', () => test('wrapped function does not set route backfill attribute for /_error route', async () => { const mockSetAttribute = vi.fn(); + const mockSetAttributes = vi.fn(); const mockGetActiveSpan = vi.spyOn(SentryCore, 'getActiveSpan').mockReturnValue({ setAttribute: mockSetAttribute, + setAttributes: mockSetAttributes, } as any); const mockGetRootSpan = vi.spyOn(SentryCore, 'getRootSpan').mockReturnValue({ setAttribute: mockSetAttribute, + setAttributes: mockSetAttributes, } as any); const origFunction = vi.fn(async () => ({ props: {} })); From f58ad683d62b0f5d1fd3ecacdfd19e5d52177727 Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Tue, 2 Sep 2025 15:46:45 +0200 Subject: [PATCH 10/24] fix tests --- .../tests/propagation.test.ts | 21 +++++++++- .../tests/sampling.test.ts | 2 +- .../tests/sampling.test.ts | 2 +- .../node-express/tests/transactions.test.ts | 4 +- .../node-fastify-3/tests/propagation.test.ts | 14 +++++-- .../node-fastify-4/tests/propagation.test.ts | 14 +++++-- .../node-fastify-5/tests/propagation.test.ts | 24 +++++++---- .../node-hapi/tests/transactions.test.ts | 2 +- .../node-koa/tests/propagation.test.ts | 40 +++++++++++++++---- .../tests/sampling.test.ts | 2 +- packages/core/src/utils/request.ts | 8 +--- packages/core/test/lib/utils/request.test.ts | 39 +++++++++++++----- .../integrations/http/incoming-requests.ts | 6 ++- packages/opentelemetry/src/spanProcessor.ts | 5 ++- 14 files changed, 134 insertions(+), 49 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tests/propagation.test.ts index 02e90f70276c..414945f6d68c 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tests/propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tests/propagation.test.ts @@ -32,6 +32,10 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { const outgoingHttpSpanId = outgoingHttpSpan?.span_id; + const outgoingHttpSpanData = outgoingHttpSpan?.data || {}; + // Outgoing span (`http.client`) does not include headers as attributes + expect(Object.keys(outgoingHttpSpanData).some(key => key.startsWith('http.request.header.'))).toBe(false); + expect(traceId).toEqual(expect.any(String)); // data is passed through from the inbound request, to verify we have the correct headers set @@ -79,7 +83,7 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'http.request.header.accept_encoding': ['gzip, deflate'], 'http.request.header.accept_language': ['*'], 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': ['localhost:3030'], + 'http.request.header.host': [expect.any(String)], 'http.request.header.sec_fetch_mode': ['cors'], 'http.request.header.user_agent': ['node'], }, @@ -113,6 +117,10 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-inbound-headers/:id', + 'http.request.header.baggage': [expect.any(String)], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': [expect.any(String)], + 'http.request.header.sentry_trace': [expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/)], }, op: 'http.server', parent_span_id: outgoingHttpSpanId, @@ -153,6 +161,10 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { const outgoingHttpSpanId = outgoingHttpSpan?.span_id; + const outgoingHttpSpanData = outgoingHttpSpan?.data || {}; + // Outgoing span (`http.client`) does not include headers as attributes + expect(Object.keys(outgoingHttpSpanData).some(key => key.startsWith('http.request.header.'))).toBe(false); + expect(traceId).toEqual(expect.any(String)); // data is passed through from the inbound request, to verify we have the correct headers set @@ -196,6 +208,13 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-outgoing-fetch/:id', + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate'], + 'http.request.header.accept_language': ['*'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': [expect.any(String)], + 'http.request.header.sec_fetch_mode': ['cors'], + 'http.request.header.user_agent': ['node'], }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1-custom-sampler/tests/sampling.test.ts b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1-custom-sampler/tests/sampling.test.ts index 9e48289ab21e..27ca47b4f62a 100644 --- a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1-custom-sampler/tests/sampling.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1-custom-sampler/tests/sampling.test.ts @@ -39,7 +39,7 @@ test('Sends a sampled API route transaction', async ({ baseURL }) => { 'http.request.header.accept_encoding': ['gzip, deflate'], 'http.request.header.accept_language': ['*'], 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': ['localhost:3030'], + 'http.request.header.host': [expect.any(String)], 'http.request.header.sec_fetch_mode': ['cors'], 'http.request.header.user_agent': ['node'], }, diff --git a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-custom-sampler/tests/sampling.test.ts b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-custom-sampler/tests/sampling.test.ts index 352573152d51..707bb6be0cfa 100644 --- a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-custom-sampler/tests/sampling.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-custom-sampler/tests/sampling.test.ts @@ -39,7 +39,7 @@ test('Sends a sampled API route transaction', async ({ baseURL }) => { 'http.request.header.accept_encoding': ['gzip, deflate'], 'http.request.header.accept_language': ['*'], 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': ['localhost:3030'], + 'http.request.header.host': [expect.any(String)], 'http.request.header.sec_fetch_mode': ['cors'], 'http.request.header.user_agent': ['node'], }, diff --git a/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts index 8007b87e2640..b4a95acbb5dd 100644 --- a/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts @@ -14,7 +14,7 @@ test('Sends an API route transaction', async ({ baseURL }) => { const transactionEvent = await pageloadTransactionEventPromise; expect(transactionEvent.contexts?.trace).toEqual({ - data: expect.objectContaining({ + data: { 'sentry.source': 'route', 'sentry.origin': 'auto.http.otel.http', 'sentry.op': 'http.server', @@ -45,7 +45,7 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.request.header.host': [expect.any(String)], 'http.request.header.sec_fetch_mode': ['cors'], 'http.request.header.user_agent': ['node'], - }), + }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), status: 'ok', diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/propagation.test.ts index d66858f51418..729897e806dc 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/propagation.test.ts @@ -32,6 +32,10 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { const outgoingHttpSpanId = outgoingHttpSpan?.span_id; + const outgoingHttpSpanData = outgoingHttpSpan?.data || {}; + // Outgoing span (`http.client`) does not include headers as attributes + expect(Object.keys(outgoingHttpSpanData).some(key => key.startsWith('http.request.header.'))).toBe(false); + expect(traceId).toEqual(expect.any(String)); // data is passed through from the inbound request, to verify we have the correct headers set @@ -79,7 +83,7 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'http.request.header.accept_encoding': ['gzip, deflate'], 'http.request.header.accept_language': ['*'], 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': ['localhost:3030'], + 'http.request.header.host': [expect.any(String)], 'http.request.header.sec_fetch_mode': ['cors'], 'http.request.header.user_agent': ['node'], }, @@ -157,6 +161,10 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { const outgoingHttpSpanId = outgoingHttpSpan?.span_id; + const outgoingHttpSpanData = outgoingHttpSpan?.data || {}; + // Outgoing span (`http.client`) does not include headers as attributes + expect(Object.keys(outgoingHttpSpanData).some(key => key.startsWith('http.request.header.'))).toBe(false); + expect(traceId).toEqual(expect.any(String)); // data is passed through from the inbound request, to verify we have the correct headers set @@ -216,7 +224,7 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { }); expect(inboundTransaction.contexts?.trace).toEqual({ - data: expect.objectContaining({ + data: { 'sentry.source': 'route', 'sentry.origin': 'auto.http.otel.http', 'sentry.op': 'http.server', @@ -238,7 +246,7 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-inbound-headers/:id', - }), + }, op: 'http.server', parent_span_id: outgoingHttpSpanId, span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-4/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify-4/tests/propagation.test.ts index dac9e8cdae4e..858ec2ba42eb 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-4/tests/propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-fastify-4/tests/propagation.test.ts @@ -32,6 +32,10 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { const outgoingHttpSpanId = outgoingHttpSpan?.span_id; + const outgoingHttpSpanData = outgoingHttpSpan?.data || {}; + // Outgoing span (`http.client`) does not include headers as attributes + expect(Object.keys(outgoingHttpSpanData).some(key => key.startsWith('http.request.header.'))).toBe(false); + expect(traceId).toEqual(expect.any(String)); // data is passed through from the inbound request, to verify we have the correct headers set @@ -79,7 +83,7 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'http.request.header.accept_encoding': ['gzip, deflate'], 'http.request.header.accept_language': ['*'], 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': ['localhost:3030'], + 'http.request.header.host': [expect.any(String)], 'http.request.header.sec_fetch_mode': ['cors'], 'http.request.header.user_agent': ['node'], }, @@ -157,6 +161,10 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { const outgoingHttpSpanId = outgoingHttpSpan?.span_id; + const outgoingHttpSpanData = outgoingHttpSpan?.data || {}; + // Outgoing span (`http.client`) does not include headers as attributes + expect(Object.keys(outgoingHttpSpanData).some(key => key.startsWith('http.request.header.'))).toBe(false); + expect(traceId).toEqual(expect.any(String)); // data is passed through from the inbound request, to verify we have the correct headers set @@ -216,7 +224,7 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { }); expect(inboundTransaction.contexts?.trace).toEqual({ - data: expect.objectContaining({ + data: { 'sentry.source': 'route', 'sentry.origin': 'auto.http.otel.http', 'sentry.op': 'http.server', @@ -238,7 +246,7 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-inbound-headers/:id', - }), + }, op: 'http.server', parent_span_id: outgoingHttpSpanId, span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/propagation.test.ts index a514533f882a..a00f4b463bec 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/propagation.test.ts @@ -32,6 +32,10 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { const outgoingHttpSpanId = outgoingHttpSpan?.span_id; + const outgoingHttpSpanData = outgoingHttpSpan?.data || {}; + // Outgoing span (`http.client`) does not include headers as attributes + expect(Object.keys(outgoingHttpSpanData).some(key => key.startsWith('http.request.header.'))).toBe(false); + expect(traceId).toEqual(expect.any(String)); // data is passed through from the inbound request, to verify we have the correct headers set @@ -51,7 +55,7 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { ); expect(outboundTransaction.contexts?.trace).toEqual({ - data: expect.objectContaining({ + data: { 'sentry.source': 'route', 'sentry.origin': 'auto.http.otel.http', 'sentry.op': 'http.server', @@ -82,7 +86,7 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'http.request.header.host': ['localhost:3030'], 'http.request.header.sec_fetch_mode': ['cors'], 'http.request.header.user_agent': ['node'], - }), + }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), status: 'ok', @@ -91,7 +95,7 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { }); expect(inboundTransaction.contexts?.trace).toEqual({ - data: expect.objectContaining({ + data: { 'sentry.source': 'route', 'sentry.origin': 'auto.http.otel.http', 'sentry.op': 'http.server', @@ -113,7 +117,7 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-inbound-headers/:id', - }), + }, op: 'http.server', parent_span_id: outgoingHttpSpanId, span_id: expect.stringMatching(/[a-f0-9]{16}/), @@ -153,6 +157,10 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { const outgoingHttpSpanId = outgoingHttpSpan?.span_id; + const outgoingHttpSpanData = outgoingHttpSpan?.data || {}; + // Outgoing span (`http.client`) does not include headers as attributes + expect(Object.keys(outgoingHttpSpanData).some(key => key.startsWith('http.request.header.'))).toBe(false); + expect(traceId).toEqual(expect.any(String)); // data is passed through from the inbound request, to verify we have the correct headers set @@ -172,7 +180,7 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { ); expect(outboundTransaction.contexts?.trace).toEqual({ - data: expect.objectContaining({ + data: { 'sentry.source': 'route', 'sentry.origin': 'auto.http.otel.http', 'sentry.op': 'http.server', @@ -196,7 +204,7 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-outgoing-fetch/:id', - }), + }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), status: 'ok', @@ -205,7 +213,7 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { }); expect(inboundTransaction.contexts?.trace).toEqual({ - data: expect.objectContaining({ + data: { 'sentry.source': 'route', 'sentry.origin': 'auto.http.otel.http', 'sentry.op': 'http.server', @@ -227,7 +235,7 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-inbound-headers/:id', - }), + }, op: 'http.server', parent_span_id: outgoingHttpSpanId, span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-hapi/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-hapi/tests/transactions.test.ts index db28d95ed74d..3dff10238c15 100644 --- a/dev-packages/e2e-tests/test-applications/node-hapi/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-hapi/tests/transactions.test.ts @@ -41,7 +41,7 @@ test('Sends successful transaction', async ({ baseURL }) => { 'http.request.header.accept_encoding': ['gzip, deflate'], 'http.request.header.accept_language': ['*'], 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': ['localhost:3030'], + 'http.request.header.host': [expect.any(String)], 'http.request.header.sec_fetch_mode': ['cors'], 'http.request.header.user_agent': ['node'], }, diff --git a/dev-packages/e2e-tests/test-applications/node-koa/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/node-koa/tests/propagation.test.ts index 4e840947fcb6..cf50b13c781e 100644 --- a/dev-packages/e2e-tests/test-applications/node-koa/tests/propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-koa/tests/propagation.test.ts @@ -31,6 +31,12 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { const outgoingHttpSpanId = outgoingHttpSpan?.span_id; + const outgoingHttpSpanData = outgoingHttpSpan?.data || {}; + + // fixme: this is included here. If the span attributes are not added in node-core incoming-requests.ts, this works + // Outgoing span (`http.client`) does not include headers as attributes + // expect(Object.keys(outgoingHttpSpanData).some(key => key.startsWith('http.request.header.'))).toBe(false); + expect(traceId).toEqual(expect.any(String)); // data is passed through from the inbound request, to verify we have the correct headers set @@ -50,7 +56,7 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { ); expect(outboundTransaction.contexts?.trace).toEqual({ - data: expect.objectContaining({ + data: { 'sentry.source': 'route', 'sentry.origin': 'auto.http.otel.http', 'sentry.op': 'http.server', @@ -74,7 +80,14 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-outgoing-http/:id', - }), + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate'], + 'http.request.header.accept_language': ['*'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': [expect.any(String)], + 'http.request.header.sec_fetch_mode': ['cors'], + 'http.request.header.user_agent': ['node'], + }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), status: 'ok', @@ -83,7 +96,7 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { }); expect(inboundTransaction.contexts?.trace).toEqual({ - data: expect.objectContaining({ + data: { 'sentry.source': 'route', 'sentry.origin': 'auto.http.otel.http', 'sentry.op': 'http.server', @@ -105,7 +118,7 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-inbound-headers/:id', - }), + }, op: 'http.server', parent_span_id: outgoingHttpSpanId, span_id: expect.stringMatching(/[a-f0-9]{16}/), @@ -145,6 +158,10 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { const outgoingHttpSpanId = outgoingHttpSpan?.span_id; + const outgoingHttpSpanData = outgoingHttpSpan?.data || {}; + // Outgoing span (`http.client`) does not include headers as attributes + expect(Object.keys(outgoingHttpSpanData).some(key => key.startsWith('http.request.header.'))).toBe(false); + expect(traceId).toEqual(expect.any(String)); // data is passed through from the inbound request, to verify we have the correct headers set @@ -164,7 +181,7 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { ); expect(outboundTransaction.contexts?.trace).toEqual({ - data: expect.objectContaining({ + data: { 'sentry.source': 'route', 'sentry.origin': 'auto.http.otel.http', 'sentry.op': 'http.server', @@ -188,7 +205,14 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-outgoing-fetch/:id', - }), + 'http.request.header.accept': ['*/*'], + 'http.request.header.accept_encoding': ['gzip, deflate'], + 'http.request.header.accept_language': ['*'], + 'http.request.header.connection': ['keep-alive'], + 'http.request.header.host': ['localhost:3030'], + 'http.request.header.sec_fetch_mode': ['cors'], + 'http.request.header.user_agent': ['node'], + }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), status: 'ok', @@ -197,7 +221,7 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { }); expect(inboundTransaction.contexts?.trace).toEqual({ - data: expect.objectContaining({ + data: { 'sentry.source': 'route', 'sentry.origin': 'auto.http.otel.http', 'sentry.op': 'http.server', @@ -219,7 +243,7 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-inbound-headers/:id', - }), + }, op: 'http.server', parent_span_id: outgoingHttpSpanId, span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/tests/sampling.test.ts b/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/tests/sampling.test.ts index ecfb3f042894..66f2b1cf3c15 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/tests/sampling.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/tests/sampling.test.ts @@ -40,7 +40,7 @@ test('Sends a sampled API route transaction', async ({ baseURL }) => { 'http.request.header.accept_encoding': ['gzip, deflate'], 'http.request.header.accept_language': ['*'], 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': ['localhost:3030'], + 'http.request.header.host': [expect.any(String)], 'http.request.header.sec_fetch_mode': ['cors'], 'http.request.header.user_agent': ['node'], }, diff --git a/packages/core/src/utils/request.ts b/packages/core/src/utils/request.ts index 345d0168cfb7..c339a215a3d8 100644 --- a/packages/core/src/utils/request.ts +++ b/packages/core/src/utils/request.ts @@ -146,7 +146,7 @@ export function httpHeadersToSpanAttributes( try { Object.entries(headers).forEach(([key, value]) => { - if (value) { + if (value !== undefined) { const lowerCasedKey = key.toLowerCase(); if (!sendDefaultPii && SENSITIVE_HEADER_SNIPPETS.some(snippet => lowerCasedKey.includes(snippet))) { @@ -156,11 +156,7 @@ export function httpHeadersToSpanAttributes( const normalizedKey = `http.request.header.${lowerCasedKey.replace(/-/g, '_')}`; if (Array.isArray(value)) { - const stringValues = value.filter((v): v is string => typeof v === 'string'); - - if (stringValues.length > 0) { - spanAttributes[normalizedKey] = stringValues; - } + spanAttributes[normalizedKey] = value.map(v => (v !== null && v !== undefined ? String(v) : v)); } else if (typeof value === 'string') { spanAttributes[normalizedKey] = [value]; } diff --git a/packages/core/test/lib/utils/request.test.ts b/packages/core/test/lib/utils/request.test.ts index 83efe4e7f4a5..03a9c6e9d9db 100644 --- a/packages/core/test/lib/utils/request.test.ts +++ b/packages/core/test/lib/utils/request.test.ts @@ -455,23 +455,25 @@ describe('request utils', () => { }); }); - it('filters out undefined values from arrays', () => { + it('keeps undefined values', () => { const headers = { - 'mixed-header': ['value1', undefined, 'value2'], + 'undefined-values': [undefined, undefined], + 'valid-header': 'valid-value', } as any; const result = httpHeadersToSpanAttributes(headers); expect(result).toEqual({ - 'http.request.header.mixed_header': ['value1', 'value2'], + 'http.request.header.valid_header': ['valid-value'], + 'http.request.header.undefined_values': [undefined, undefined], }); }); - it('ignores empty arrays after filtering undefined values', () => { + it('ignores undefined header values', () => { const headers = { - 'empty-after-filter': [undefined, undefined], 'valid-header': 'valid-value', - } as any; + 'undefined-header': undefined, + }; const result = httpHeadersToSpanAttributes(headers); @@ -480,15 +482,16 @@ describe('request utils', () => { }); }); - it('ignores undefined header values', () => { + it('adds empty array headers', () => { const headers = { + 'empty-header': [], 'valid-header': 'valid-value', - 'undefined-header': undefined, - }; + } as any; const result = httpHeadersToSpanAttributes(headers); expect(result).toEqual({ + 'http.request.header.empty_header': [], 'http.request.header.valid_header': ['valid-value'], }); }); @@ -553,6 +556,20 @@ describe('request utils', () => { }); }); + it('handles headers with empty string values', () => { + const headers = { + 'empty-header': '', + 'valid-header': 'valid-value', + }; + + const result = httpHeadersToSpanAttributes(headers); + + expect(result).toEqual({ + 'http.request.header.empty_header': [''], + 'http.request.header.valid_header': ['valid-value'], + }); + }); + it('returns empty object when processing invalid headers throws error', () => { // Create a headers object that will throw an error when iterated const headers = {}; @@ -567,7 +584,7 @@ describe('request utils', () => { expect(result).toEqual({}); }); - it('ignores non-string values in arrays', () => { + it('stringifies non-string values (except null` in arrays', () => { const headers = { 'mixed-types': ['string-value', 123, true, null], } as any; @@ -575,7 +592,7 @@ describe('request utils', () => { const result = httpHeadersToSpanAttributes(headers); expect(result).toEqual({ - 'http.request.header.mixed_types': ['string-value'], + 'http.request.header.mixed_types': ['string-value', '123', 'true', null], }); }); diff --git a/packages/node-core/src/integrations/http/incoming-requests.ts b/packages/node-core/src/integrations/http/incoming-requests.ts index 9de162a3dbff..8b9e33968932 100644 --- a/packages/node-core/src/integrations/http/incoming-requests.ts +++ b/packages/node-core/src/integrations/http/incoming-requests.ts @@ -74,7 +74,11 @@ export function instrumentServer( // Extract HTTP request headers as span attributes const client = getClient(); const sendDefaultPii = client?.getOptions().sendDefaultPii ?? false; - const httpHeaderAttributes = httpHeadersToSpanAttributes(normalizedRequest.headers || {}, sendDefaultPii); + const httpHeaderAttributes: Record = { + // fixme: adding the attributes here will add them to spans in node-core OTel setups (e.g. E2E test node-core-express-otel-v1) + // However, adding this here will also add the span attributes to http.client spans in koa + ...httpHeadersToSpanAttributes(normalizedRequest.headers || {}, sendDefaultPii), + }; // Update the isolation scope, isolate this request isolationScope.setSDKProcessingMetadata({ normalizedRequest, ipAddress, httpHeaderAttributes }); diff --git a/packages/opentelemetry/src/spanProcessor.ts b/packages/opentelemetry/src/spanProcessor.ts index c871b0db08ab..a7610a6bf76a 100644 --- a/packages/opentelemetry/src/spanProcessor.ts +++ b/packages/opentelemetry/src/spanProcessor.ts @@ -48,14 +48,15 @@ function onSpanStart(span: Span, parentContext: Context): void { logSpanStart(span); - // Add HTTP request headers as span attributes for HTTP server spans if (scopes?.isolationScope) { const spanData = spanToJSON(span); - // Check if this is an HTTP server span + + // Add HTTP request headers as span attributes for HTTP server spans if (spanData.op === 'http.server' || (spanData.data && 'http.method' in spanData.data)) { const sdkProcessingMetadata = scopes.isolationScope.getScopeData().sdkProcessingMetadata; const httpHeaderAttributes = sdkProcessingMetadata?.httpHeaderAttributes as Record; + // Those httpHeaderAttributes are set in @sentry/node-core for the incoming request if (httpHeaderAttributes && typeof httpHeaderAttributes === 'object') { span.setAttributes(httpHeaderAttributes); } From 67b4255afad49065e8e2ecc2264f11d4f595f89e Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Tue, 2 Sep 2025 16:18:06 +0200 Subject: [PATCH 11/24] change all to string, not arrays --- .../nestjs-11/tests/transactions.test.ts | 14 +-- .../nestjs-8/tests/transactions.test.ts | 14 +-- .../nestjs-basic/tests/transactions.test.ts | 14 +-- .../tests/propagation.test.ts | 36 ++++---- .../nestjs-fastify/tests/transactions.test.ts | 28 +++--- .../tests/transactions.test.ts | 14 +-- .../tests/transactions.test.ts | 14 +-- .../nextjs-15/tests/pageload-tracing.test.ts | 12 +-- .../node-connect/tests/transactions.test.ts | 14 +-- .../tests/sampling.test.ts | 14 +-- .../tests/transactions.test.ts | 14 +-- .../tests/transactions.test.ts | 14 +-- .../tests/sampling.test.ts | 14 +-- .../tests/transactions.test.ts | 14 +-- .../tests/transactions.test.ts | 14 +-- .../tests/transactions.test.ts | 14 +-- .../node-express/tests/transactions.test.ts | 24 ++--- .../node-fastify-3/tests/propagation.test.ts | 36 ++++---- .../node-fastify-3/tests/transactions.test.ts | 14 +-- .../node-fastify-4/tests/propagation.test.ts | 36 ++++---- .../node-fastify-4/tests/transactions.test.ts | 14 +-- .../node-fastify-5/tests/propagation.test.ts | 14 +-- .../node-fastify-5/tests/transactions.test.ts | 14 +-- .../node-hapi/tests/transactions.test.ts | 28 +++--- .../node-koa/tests/propagation.test.ts | 28 +++--- .../node-koa/tests/transactions.test.ts | 14 +-- .../tests/sampling.test.ts | 14 +-- .../tests/transactions.test.ts | 14 +-- .../node-otel/tests/transactions.test.ts | 14 +-- .../nuxt-3/tests/tracing.server.test.ts | 12 +-- .../tests/performance.server.test.ts | 12 +-- .../tsx-express/tests/transactions.test.ts | 14 +-- .../suites/tracing/httpIntegration/test.ts | 18 ++-- packages/astro/test/server/middleware.test.ts | 8 +- .../bun/test/integrations/bunserver.test.ts | 38 ++++---- packages/cloudflare/test/request.test.ts | 2 +- packages/core/src/utils/request.ts | 8 +- packages/core/test/lib/utils/request.test.ts | 92 +++++++++---------- .../src/common/utils/headersToAttributes.ts | 2 +- .../src/common/wrapMiddlewareWithSentry.ts | 2 +- .../src/edge/wrapApiHandlerWithSentry.ts | 2 +- .../integrations/http/incoming-requests.ts | 2 +- 42 files changed, 367 insertions(+), 367 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nestjs-11/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-11/tests/transactions.test.ts index 6a57fa737ca0..2f314a10817d 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-11/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-11/tests/transactions.test.ts @@ -38,13 +38,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-transaction', - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate'], - 'http.request.header.accept_language': ['*'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.sec_fetch_mode': ['cors'], - 'http.request.header.user_agent': ['node'], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': 'node', }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/nestjs-8/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-8/tests/transactions.test.ts index 9d32675d1b31..a7d5ed887049 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-8/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-8/tests/transactions.test.ts @@ -38,13 +38,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-transaction', - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate'], - 'http.request.header.accept_language': ['*'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.sec_fetch_mode': ['cors'], - 'http.request.header.user_agent': ['node'], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': 'node', }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/transactions.test.ts index f32be11f0eaf..143ebcd9a9f0 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/transactions.test.ts @@ -38,13 +38,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-transaction', - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate'], - 'http.request.header.accept_language': ['*'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.sec_fetch_mode': ['cors'], - 'http.request.header.user_agent': ['node'], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': 'node', }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tests/propagation.test.ts index 414945f6d68c..0a23c1766b38 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tests/propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tests/propagation.test.ts @@ -79,13 +79,13 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-outgoing-http/:id', - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate'], - 'http.request.header.accept_language': ['*'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.sec_fetch_mode': ['cors'], - 'http.request.header.user_agent': ['node'], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': 'node', }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), @@ -117,10 +117,10 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-inbound-headers/:id', - 'http.request.header.baggage': [expect.any(String)], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.sentry_trace': [expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/)], + 'http.request.header.baggage': expect.any(String), + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sentry_trace': expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/), }, op: 'http.server', parent_span_id: outgoingHttpSpanId, @@ -208,13 +208,13 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-outgoing-fetch/:id', - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate'], - 'http.request.header.accept_language': ['*'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.sec_fetch_mode': ['cors'], - 'http.request.header.user_agent': ['node'], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': 'node', }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts index b0372ba2be21..c7cd61374c6c 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts @@ -38,13 +38,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-transaction', - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate'], - 'http.request.header.accept_language': ['*'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.sec_fetch_mode': ['cors'], - 'http.request.header.user_agent': ['node'], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': 'node', }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), @@ -110,13 +110,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'nestjs.controller': 'AppController', 'nestjs.callback': 'testTransaction', url: '/test-transaction', - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate'], - 'http.request.header.accept_language': ['*'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.sec_fetch_mode': ['cors'], - 'http.request.header.user_agent': ['node'], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': 'node', }, description: 'GET /test-transaction', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/tests/transactions.test.ts index 587cb9534c1f..77cb616450f9 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/tests/transactions.test.ts @@ -38,13 +38,13 @@ test('Sends an API route transaction from module', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/example-module/transaction', - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate'], - 'http.request.header.accept_language': ['*'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.sec_fetch_mode': ['cors'], - 'http.request.header.user_agent': ['node'], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': 'node', }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/transactions.test.ts index 9fdc4aba5e8b..63976a559898 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/transactions.test.ts @@ -38,13 +38,13 @@ test('Sends an API route transaction from module', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/example-module/transaction', - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate'], - 'http.request.header.accept_language': ['*'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.sec_fetch_mode': ['cors'], - 'http.request.header.user_agent': ['node'], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': 'node', }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/tests/pageload-tracing.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/pageload-tracing.test.ts index 7a92a80a84b7..c9a9fc44bdba 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15/tests/pageload-tracing.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/pageload-tracing.test.ts @@ -43,12 +43,12 @@ test('extracts HTTP request headers as span attributes', async ({ baseURL }) => expect(serverTransaction.contexts?.trace?.data).toEqual( expect.objectContaining({ - 'http.request.header.user_agent': ['Custom-NextJS-Agent/15.0'], - 'http.request.header.content_type': ['text/html'], - 'http.request.header.x_nextjs_test': ['nextjs-header-value'], - 'http.request.header.accept': ['text/html, application/xhtml+xml'], - 'http.request.header.x_framework': ['Next.js'], - 'http.request.header.x_request_id': ['nextjs-789'], + 'http.request.header.user_agent': 'Custom-NextJS-Agent/15.0', + 'http.request.header.content_type': 'text/html', + 'http.request.header.x_nextjs_test': 'nextjs-header-value', + 'http.request.header.accept': 'text/html, application/xhtml+xml', + 'http.request.header.x_framework': 'Next.js', + 'http.request.header.x_request_id': 'nextjs-789', }), ); }); diff --git a/dev-packages/e2e-tests/test-applications/node-connect/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-connect/tests/transactions.test.ts index e7ed0723256e..9b06ad052f58 100644 --- a/dev-packages/e2e-tests/test-applications/node-connect/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-connect/tests/transactions.test.ts @@ -39,13 +39,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-transaction', - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate'], - 'http.request.header.accept_language': ['*'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.sec_fetch_mode': ['cors'], - 'http.request.header.user_agent': ['node'], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': 'node', }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1-custom-sampler/tests/sampling.test.ts b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1-custom-sampler/tests/sampling.test.ts index 27ca47b4f62a..b4ba3b219546 100644 --- a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1-custom-sampler/tests/sampling.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1-custom-sampler/tests/sampling.test.ts @@ -35,13 +35,13 @@ test('Sends a sampled API route transaction', async ({ baseURL }) => { 'net.peer.port': expect.any(Number), 'http.status_code': 200, 'http.status_text': 'OK', - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate'], - 'http.request.header.accept_language': ['*'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.sec_fetch_mode': ['cors'], - 'http.request.header.user_agent': ['node'], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': 'node', }, origin: 'manual', op: 'http.server', diff --git a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1-sdk-node/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1-sdk-node/tests/transactions.test.ts index fd0bc7d8dda7..64baf74627d6 100644 --- a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1-sdk-node/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1-sdk-node/tests/transactions.test.ts @@ -49,13 +49,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'net.peer.port': expect.any(Number), 'http.status_code': 200, 'http.status_text': 'OK', - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate'], - 'http.request.header.accept_language': ['*'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.sec_fetch_mode': ['cors'], - 'http.request.header.user_agent': ['node'], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': 'node', }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1/tests/transactions.test.ts index c98c21f74594..e43d9d2eab51 100644 --- a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1/tests/transactions.test.ts @@ -37,13 +37,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'net.peer.port': expect.any(Number), 'http.status_code': 200, 'http.status_text': 'OK', - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate'], - 'http.request.header.accept_language': ['*'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.sec_fetch_mode': ['cors'], - 'http.request.header.user_agent': ['node'], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': 'node', }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-custom-sampler/tests/sampling.test.ts b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-custom-sampler/tests/sampling.test.ts index 707bb6be0cfa..5c54309923ae 100644 --- a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-custom-sampler/tests/sampling.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-custom-sampler/tests/sampling.test.ts @@ -35,13 +35,13 @@ test('Sends a sampled API route transaction', async ({ baseURL }) => { 'net.peer.port': expect.any(Number), 'http.status_code': 200, 'http.status_text': 'OK', - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate'], - 'http.request.header.accept_language': ['*'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.sec_fetch_mode': ['cors'], - 'http.request.header.user_agent': ['node'], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': 'node', }, origin: 'manual', op: 'http.server', diff --git a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-sdk-node/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-sdk-node/tests/transactions.test.ts index 740acee0d959..9e532cb2b78a 100644 --- a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-sdk-node/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-sdk-node/tests/transactions.test.ts @@ -49,13 +49,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'net.peer.port': expect.any(Number), 'http.status_code': 200, 'http.status_text': 'OK', - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate'], - 'http.request.header.accept_language': ['*'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.sec_fetch_mode': ['cors'], - 'http.request.header.user_agent': ['node'], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': 'node', }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2/tests/transactions.test.ts index 853512b1d2ff..e2f0c0f51a61 100644 --- a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2/tests/transactions.test.ts @@ -37,13 +37,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'net.peer.port': expect.any(Number), 'http.status_code': 200, 'http.status_text': 'OK', - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate'], - 'http.request.header.accept_language': ['*'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.sec_fetch_mode': ['cors'], - 'http.request.header.user_agent': ['node'], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': 'node', }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-express-v5/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-express-v5/tests/transactions.test.ts index 1a2273b265af..048f70a1aba8 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-v5/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express-v5/tests/transactions.test.ts @@ -38,13 +38,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-transaction', - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate'], - 'http.request.header.accept_language': ['*'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.sec_fetch_mode': ['cors'], - 'http.request.header.user_agent': ['node'], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': 'node', }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts index b4a95acbb5dd..1ffd9f2e498d 100644 --- a/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts @@ -38,13 +38,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-transaction', - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate'], - 'http.request.header.accept_language': ['*'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.sec_fetch_mode': ['cors'], - 'http.request.header.user_agent': ['node'], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': 'node', }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), @@ -238,11 +238,11 @@ test('Extracts HTTP request headers as span attributes', async ({ baseURL }) => expect(transactionEvent.contexts?.trace?.data).toEqual( expect.objectContaining({ - 'http.request.header.user_agent': ['Custom-Agent/1.0 (Test)'], - 'http.request.header.content_type': ['application/json'], - 'http.request.header.x_custom_header': ['test-value'], - 'http.request.header.accept': ['application/json, text/plain'], - 'http.request.header.x_request_id': ['req-123'], + 'http.request.header.user_agent': 'Custom-Agent/1.0 (Test)', + 'http.request.header.content_type': 'application/json', + 'http.request.header.x_custom_header': 'test-value', + 'http.request.header.accept': 'application/json, text/plain', + 'http.request.header.x_request_id': 'req-123', }), ); }); diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/propagation.test.ts index 729897e806dc..df28721f7ee7 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/propagation.test.ts @@ -79,13 +79,13 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-outgoing-http/:id', - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate'], - 'http.request.header.accept_language': ['*'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.sec_fetch_mode': ['cors'], - 'http.request.header.user_agent': ['node'], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': 'node', }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), @@ -117,10 +117,10 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-inbound-headers/:id', - 'http.request.header.baggage': [expect.any(String)], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.sentry_trace': [expect.stringContaining(traceId || '')], + 'http.request.header.baggage': expect.any(String), + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sentry_trace': expect.stringContaining(traceId || ''), }, op: 'http.server', parent_span_id: outgoingHttpSpanId, @@ -208,13 +208,13 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-outgoing-fetch/:id', - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate'], - 'http.request.header.accept_language': ['*'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.sec_fetch_mode': ['cors'], - 'http.request.header.user_agent': ['node'], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': 'node', }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/transactions.test.ts index 5049950eefef..4bf9b00f127d 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/transactions.test.ts @@ -38,13 +38,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-transaction', - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate'], - 'http.request.header.accept_language': ['*'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.sec_fetch_mode': ['cors'], - 'http.request.header.user_agent': ['node'], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': 'node', }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-4/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify-4/tests/propagation.test.ts index 858ec2ba42eb..75bbf8f252bf 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-4/tests/propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-fastify-4/tests/propagation.test.ts @@ -79,13 +79,13 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-outgoing-http/:id', - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate'], - 'http.request.header.accept_language': ['*'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.sec_fetch_mode': ['cors'], - 'http.request.header.user_agent': ['node'], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': 'node', }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), @@ -117,10 +117,10 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-inbound-headers/:id', - 'http.request.header.baggage': [expect.any(String)], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.sentry_trace': [expect.stringContaining(traceId || '')], + 'http.request.header.baggage': expect.any(String), + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sentry_trace': expect.stringContaining(traceId || ''), }, op: 'http.server', parent_span_id: outgoingHttpSpanId, @@ -208,13 +208,13 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-outgoing-fetch/:id', - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate'], - 'http.request.header.accept_language': ['*'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.sec_fetch_mode': ['cors'], - 'http.request.header.user_agent': ['node'], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': 'node', }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-4/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify-4/tests/transactions.test.ts index 3cccd7ec9f1b..eadf89abe7ae 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-4/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-fastify-4/tests/transactions.test.ts @@ -38,13 +38,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-transaction', - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate'], - 'http.request.header.accept_language': ['*'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.sec_fetch_mode': ['cors'], - 'http.request.header.user_agent': ['node'], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': 'node', }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/propagation.test.ts index a00f4b463bec..beb4bc9abeb0 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/propagation.test.ts @@ -79,13 +79,13 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-outgoing-http/:id', - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate'], - 'http.request.header.accept_language': ['*'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': ['localhost:3030'], - 'http.request.header.sec_fetch_mode': ['cors'], - 'http.request.header.user_agent': ['node'], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': 'localhost:3030', + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': 'node', }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/transactions.test.ts index 0d3e0f8830f6..3a00e0616f57 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/transactions.test.ts @@ -38,13 +38,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-transaction', - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate'], - 'http.request.header.accept_language': ['*'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.sec_fetch_mode': ['cors'], - 'http.request.header.user_agent': ['node'], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': 'node', }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-hapi/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-hapi/tests/transactions.test.ts index 3dff10238c15..a644e69a27d1 100644 --- a/dev-packages/e2e-tests/test-applications/node-hapi/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-hapi/tests/transactions.test.ts @@ -37,13 +37,13 @@ test('Sends successful transaction', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-success', - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate'], - 'http.request.header.accept_language': ['*'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.sec_fetch_mode': ['cors'], - 'http.request.header.user_agent': ['node'], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': 'node', }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), @@ -72,13 +72,13 @@ test('Sends successful transaction', async ({ baseURL }) => { 'http.route': '/test-success', 'sentry.op': 'router.hapi', 'sentry.origin': 'auto.http.otel.hapi', - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate'], - 'http.request.header.accept_language': ['*'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.sec_fetch_mode': ['cors'], - 'http.request.header.user_agent': ['node'], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': 'node', }, description: 'GET /test-success', op: 'router.hapi', diff --git a/dev-packages/e2e-tests/test-applications/node-koa/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/node-koa/tests/propagation.test.ts index cf50b13c781e..c008397c1ed7 100644 --- a/dev-packages/e2e-tests/test-applications/node-koa/tests/propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-koa/tests/propagation.test.ts @@ -80,13 +80,13 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-outgoing-http/:id', - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate'], - 'http.request.header.accept_language': ['*'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.sec_fetch_mode': ['cors'], - 'http.request.header.user_agent': ['node'], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': 'node', }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), @@ -205,13 +205,13 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-outgoing-fetch/:id', - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate'], - 'http.request.header.accept_language': ['*'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': ['localhost:3030'], - 'http.request.header.sec_fetch_mode': ['cors'], - 'http.request.header.user_agent': ['node'], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': 'localhost:3030', + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': 'node', }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-koa/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-koa/tests/transactions.test.ts index 05eecec24578..53803a8882e6 100644 --- a/dev-packages/e2e-tests/test-applications/node-koa/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-koa/tests/transactions.test.ts @@ -38,13 +38,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-transaction', - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate'], - 'http.request.header.accept_language': ['*'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.sec_fetch_mode': ['cors'], - 'http.request.header.user_agent': ['node'], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': 'node', }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/tests/sampling.test.ts b/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/tests/sampling.test.ts index 66f2b1cf3c15..84d783b1d567 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/tests/sampling.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/tests/sampling.test.ts @@ -36,13 +36,13 @@ test('Sends a sampled API route transaction', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/task', - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate'], - 'http.request.header.accept_language': ['*'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.sec_fetch_mode': ['cors'], - 'http.request.header.user_agent': ['node'], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': 'node', }, origin: 'auto.http.otel.http', op: 'http.server', diff --git a/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/tests/transactions.test.ts index 82d19e92330e..a586146eece5 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/tests/transactions.test.ts @@ -50,13 +50,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-transaction', - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate'], - 'http.request.header.accept_language': ['*'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.sec_fetch_mode': ['cors'], - 'http.request.header.user_agent': ['node'], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': 'node', }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-otel/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-otel/tests/transactions.test.ts index 38e37100241e..1f79fd438719 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-otel/tests/transactions.test.ts @@ -50,13 +50,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-transaction', - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate'], - 'http.request.header.accept_language': ['*'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.sec_fetch_mode': ['cors'], - 'http.request.header.user_agent': ['node'], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': 'node', }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/nuxt-3/tests/tracing.server.test.ts b/dev-packages/e2e-tests/test-applications/nuxt-3/tests/tracing.server.test.ts index 6fb1aa6928a5..e7b6a5ddfc09 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-3/tests/tracing.server.test.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-3/tests/tracing.server.test.ts @@ -64,12 +64,12 @@ test('extracts HTTP request headers as span attributes', async ({ baseURL }) => expect(transaction.contexts?.trace?.data).toEqual( expect.objectContaining({ - 'http.request.header.user_agent': ['Custom-Nuxt-Agent/3.0'], - 'http.request.header.content_type': ['application/json'], - 'http.request.header.x_nuxt_test': ['nuxt-header-value'], - 'http.request.header.accept': ['application/json, text/html'], - 'http.request.header.x_framework': ['Nuxt'], - 'http.request.header.x_request_id': ['nuxt-456'], + 'http.request.header.user_agent': 'Custom-Nuxt-Agent/3.0', + 'http.request.header.content_type': 'application/json', + 'http.request.header.x_nuxt_test': 'nuxt-header-value', + 'http.request.header.accept': 'application/json, text/html', + 'http.request.header.x_framework': 'Nuxt', + 'http.request.header.x_request_id': 'nuxt-456', }), ); }); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2/tests/performance.server.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2/tests/performance.server.test.ts index 732a6b860a74..9fd87b052374 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2/tests/performance.server.test.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2/tests/performance.server.test.ts @@ -53,12 +53,12 @@ test('extracts HTTP request headers as span attributes', async ({ page, baseURL expect(serverTxnEvent.contexts?.trace?.data).toEqual( expect.objectContaining({ - 'http.request.header.user_agent': ['Custom-SvelteKit-Agent/1.0'], - 'http.request.header.content_type': ['application/json'], - 'http.request.header.x_test_header': ['sveltekit-test-value'], - 'http.request.header.accept': ['application/json'], - 'http.request.header.x_framework': ['SvelteKit'], - 'http.request.header.x_request_id': ['sveltekit-123'], + 'http.request.header.user_agent': 'Custom-SvelteKit-Agent/1.0', + 'http.request.header.content_type': 'application/json', + 'http.request.header.x_test_header': 'sveltekit-test-value', + 'http.request.header.accept': 'application/json', + 'http.request.header.x_framework': 'SvelteKit', + 'http.request.header.x_request_id': 'sveltekit-123', }), ); }); diff --git a/dev-packages/e2e-tests/test-applications/tsx-express/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/tsx-express/tests/transactions.test.ts index 9dff721875ab..fca8f1b85528 100644 --- a/dev-packages/e2e-tests/test-applications/tsx-express/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/tsx-express/tests/transactions.test.ts @@ -38,13 +38,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-transaction', - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate'], - 'http.request.header.accept_language': ['*'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.sec_fetch_mode': ['cors'], - 'http.request.header.user_agent': ['node'], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': 'node', }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/node-integration-tests/suites/tracing/httpIntegration/test.ts b/dev-packages/node-integration-tests/suites/tracing/httpIntegration/test.ts index dd1a9059b1ab..b6329aacfde4 100644 --- a/dev-packages/node-integration-tests/suites/tracing/httpIntegration/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/httpIntegration/test.ts @@ -4,13 +4,13 @@ import { createTestServer } from '../../../utils/server'; function getCommonHttpRequestHeaders(): Record { return { - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate'], - 'http.request.header.accept_language': ['*'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.sec_fetch_mode': ['cors'], - 'http.request.header.user_agent': ['node'], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': 'node', }; } @@ -140,8 +140,8 @@ describe('httpIntegration', () => { 'sentry.sample_rate': 1, 'sentry.source': 'route', url: `http://localhost:${port}/test`, - 'http.request.header.content_length': ['9'], - 'http.request.header.content_type': ['text/plain;charset=UTF-8'], + 'http.request.header.content_length': '9', + 'http.request.header.content_type': 'text/plain;charset=UTF-8', ...getCommonHttpRequestHeaders(), }); }, diff --git a/packages/astro/test/server/middleware.test.ts b/packages/astro/test/server/middleware.test.ts index e8635ec7a79d..04b6eb417821 100644 --- a/packages/astro/test/server/middleware.test.ts +++ b/packages/astro/test/server/middleware.test.ts @@ -116,10 +116,10 @@ describe('sentryMiddleware', () => { method: 'POST', url: 'https://mydomain.io/api/data', [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - 'http.request.header.user_agent': ['Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'], - 'http.request.header.content_type': ['application/json'], - 'http.request.header.x_custom_header': ['custom-value'], - 'http.request.header.accept': ['application/json, text/plain'], + 'http.request.header.user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', + 'http.request.header.content_type': 'application/json', + 'http.request.header.x_custom_header': 'custom-value', + 'http.request.header.accept': 'application/json, text/plain', }, name: 'POST /api/data', op: 'http.server', diff --git a/packages/bun/test/integrations/bunserver.test.ts b/packages/bun/test/integrations/bunserver.test.ts index 6ef612493ad7..9792c59c2691 100644 --- a/packages/bun/test/integrations/bunserver.test.ts +++ b/packages/bun/test/integrations/bunserver.test.ts @@ -47,11 +47,11 @@ describe('Bun Serve Integration', () => { 'url.port': port.toString(), 'url.scheme': 'http:', 'url.domain': 'localhost', - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate, br, zstd'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.user_agent': [expect.stringContaining('Bun')], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate, br, zstd', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.user_agent': expect.stringContaining('Bun'), }, op: 'http.server', name: 'GET /users', @@ -86,12 +86,12 @@ describe('Bun Serve Integration', () => { 'url.port': port.toString(), 'url.scheme': 'http:', 'url.domain': 'localhost', - 'http.request.header.accept': ['*/*'], - 'http.request.header.accept_encoding': ['gzip, deflate, br, zstd'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.content_length': ['0'], - 'http.request.header.host': [expect.any(String)], - 'http.request.header.user_agent': [expect.stringContaining('Bun')], + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate, br, zstd', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.content_length': '0', + 'http.request.header.host': expect.any(String), + 'http.request.header.user_agent': expect.stringContaining('Bun'), }, op: 'http.server', name: 'POST /', @@ -176,14 +176,14 @@ describe('Bun Serve Integration', () => { 'url.scheme': 'http:', 'url.domain': 'localhost', // HTTP headers as span attributes following OpenTelemetry semantic conventions - 'http.request.header.user_agent': ['Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'], - 'http.request.header.content_type': ['application/json'], - 'http.request.header.x_custom_header': ['custom-value'], - 'http.request.header.accept': ['application/json, text/plain'], - 'http.request.header.accept_encoding': ['gzip, deflate, br, zstd'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.content_length': ['15'], - 'http.request.header.host': [expect.any(String)], + 'http.request.header.user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', + 'http.request.header.content_type': 'application/json', + 'http.request.header.x_custom_header': 'custom-value', + 'http.request.header.accept': 'application/json, text/plain', + 'http.request.header.accept_encoding': 'gzip, deflate, br, zstd', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.content_length': '15', + 'http.request.header.host': expect.any(String), }), op: 'http.server', name: 'POST /api/test', diff --git a/packages/cloudflare/test/request.test.ts b/packages/cloudflare/test/request.test.ts index 2277632e9ab1..ad323e3c5b5a 100644 --- a/packages/cloudflare/test/request.test.ts +++ b/packages/cloudflare/test/request.test.ts @@ -319,7 +319,7 @@ describe('withSentry', () => { 'sentry.sample_rate': 1, 'http.response.status_code': 200, 'http.request.body.size': 10, - 'http.request.header.content_length': ['10'], + 'http.request.header.content_length': '10', }, op: 'http.server', origin: 'auto.http.cloudflare', diff --git a/packages/core/src/utils/request.ts b/packages/core/src/utils/request.ts index c339a215a3d8..ffd60f3e8486 100644 --- a/packages/core/src/utils/request.ts +++ b/packages/core/src/utils/request.ts @@ -141,8 +141,8 @@ const SENSITIVE_HEADER_SNIPPETS = ['auth', 'token', 'secret', 'cookie', '-user', export function httpHeadersToSpanAttributes( headers: Record, sendDefaultPii: boolean = false, -): Record { - const spanAttributes: Record = {}; +): Record { + const spanAttributes: Record = {}; try { Object.entries(headers).forEach(([key, value]) => { @@ -156,9 +156,9 @@ export function httpHeadersToSpanAttributes( const normalizedKey = `http.request.header.${lowerCasedKey.replace(/-/g, '_')}`; if (Array.isArray(value)) { - spanAttributes[normalizedKey] = value.map(v => (v !== null && v !== undefined ? String(v) : v)); + spanAttributes[normalizedKey] = value.map(v => (v !== null && v !== undefined ? String(v) : v)).join(';'); } else if (typeof value === 'string') { - spanAttributes[normalizedKey] = [value]; + spanAttributes[normalizedKey] = value; } } }); diff --git a/packages/core/test/lib/utils/request.test.ts b/packages/core/test/lib/utils/request.test.ts index 03a9c6e9d9db..b37ee860f43f 100644 --- a/packages/core/test/lib/utils/request.test.ts +++ b/packages/core/test/lib/utils/request.test.ts @@ -427,7 +427,7 @@ describe('request utils', () => { expect(httpHeadersToSpanAttributes({})).toEqual({}); }); - it('converts single string header values to arrays', () => { + it('converts single string header values to strings', () => { const headers = { 'Content-Type': 'application/json', 'user-agent': 'test-agent', @@ -436,12 +436,12 @@ describe('request utils', () => { const result = httpHeadersToSpanAttributes(headers); expect(result).toEqual({ - 'http.request.header.content_type': ['application/json'], - 'http.request.header.user_agent': ['test-agent'], + 'http.request.header.content_type': 'application/json', + 'http.request.header.user_agent': 'test-agent', }); }); - it('handles array header values', () => { + it('handles array header values by joining with semicolons', () => { const headers = { 'custom-header': ['value1', 'value2'], accept: ['application/json', 'text/html'], @@ -450,12 +450,12 @@ describe('request utils', () => { const result = httpHeadersToSpanAttributes(headers); expect(result).toEqual({ - 'http.request.header.custom_header': ['value1', 'value2'], - 'http.request.header.accept': ['application/json', 'text/html'], + 'http.request.header.custom_header': 'value1;value2', + 'http.request.header.accept': 'application/json;text/html', }); }); - it('keeps undefined values', () => { + it('filters undefined values in arrays when joining', () => { const headers = { 'undefined-values': [undefined, undefined], 'valid-header': 'valid-value', @@ -464,8 +464,8 @@ describe('request utils', () => { const result = httpHeadersToSpanAttributes(headers); expect(result).toEqual({ - 'http.request.header.valid_header': ['valid-value'], - 'http.request.header.undefined_values': [undefined, undefined], + 'http.request.header.valid_header': 'valid-value', + 'http.request.header.undefined_values': ';', }); }); @@ -478,11 +478,11 @@ describe('request utils', () => { const result = httpHeadersToSpanAttributes(headers); expect(result).toEqual({ - 'http.request.header.valid_header': ['valid-value'], + 'http.request.header.valid_header': 'valid-value', }); }); - it('adds empty array headers', () => { + it('adds empty array headers as empty string', () => { const headers = { 'empty-header': [], 'valid-header': 'valid-value', @@ -491,8 +491,8 @@ describe('request utils', () => { const result = httpHeadersToSpanAttributes(headers); expect(result).toEqual({ - 'http.request.header.empty_header': [], - 'http.request.header.valid_header': ['valid-value'], + 'http.request.header.empty_header': '', + 'http.request.header.valid_header': 'valid-value', }); }); @@ -507,10 +507,10 @@ describe('request utils', () => { const result = httpHeadersToSpanAttributes(headers); expect(result).toEqual({ - 'http.request.header.content_type': ['application/json'], - 'http.request.header.x_custom_header': ['custom-value'], - 'http.request.header.user_agent': ['test-agent'], - 'http.request.header.accept': ['text/html'], + 'http.request.header.content_type': 'application/json', + 'http.request.header.x_custom_header': 'custom-value', + 'http.request.header.user_agent': 'test-agent', + 'http.request.header.accept': 'text/html', }); }); @@ -530,19 +530,19 @@ describe('request utils', () => { const result = httpHeadersToSpanAttributes(headers); expect(result).toEqual({ - 'http.request.header.host': ['example.com'], - 'http.request.header.user_agent': ['Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'], - 'http.request.header.accept': ['text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'], - 'http.request.header.accept_language': ['en-US,en;q=0.5'], - 'http.request.header.accept_encoding': ['gzip, deflate'], - 'http.request.header.connection': ['keep-alive'], - 'http.request.header.upgrade_insecure_requests': ['1'], - 'http.request.header.cache_control': ['no-cache'], - 'http.request.header.x_forwarded_for': ['192.168.1.1'], + 'http.request.header.host': 'example.com', + 'http.request.header.user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', + 'http.request.header.accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'http.request.header.accept_language': 'en-US,en;q=0.5', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.upgrade_insecure_requests': '1', + 'http.request.header.cache_control': 'no-cache', + 'http.request.header.x_forwarded_for': '192.168.1.1', }); }); - it('handles multiple values for the same header', () => { + it('handles multiple values for the same header by joining with semicolons', () => { const headers = { 'x-random-header': ['test=abc123', 'preferences=dark-mode', 'number=three'], Accept: ['application/json', 'text/html'], @@ -551,8 +551,8 @@ describe('request utils', () => { const result = httpHeadersToSpanAttributes(headers); expect(result).toEqual({ - 'http.request.header.x_random_header': ['test=abc123', 'preferences=dark-mode', 'number=three'], - 'http.request.header.accept': ['application/json', 'text/html'], + 'http.request.header.x_random_header': 'test=abc123;preferences=dark-mode;number=three', + 'http.request.header.accept': 'application/json;text/html', }); }); @@ -565,8 +565,8 @@ describe('request utils', () => { const result = httpHeadersToSpanAttributes(headers); expect(result).toEqual({ - 'http.request.header.empty_header': [''], - 'http.request.header.valid_header': ['valid-value'], + 'http.request.header.empty_header': '', + 'http.request.header.valid_header': 'valid-value', }); }); @@ -584,7 +584,7 @@ describe('request utils', () => { expect(result).toEqual({}); }); - it('stringifies non-string values (except null` in arrays', () => { + it('stringifies non-string values (except null) in arrays and joins them', () => { const headers = { 'mixed-types': ['string-value', 123, true, null], } as any; @@ -592,7 +592,7 @@ describe('request utils', () => { const result = httpHeadersToSpanAttributes(headers); expect(result).toEqual({ - 'http.request.header.mixed_types': ['string-value', '123', 'true', null], + 'http.request.header.mixed_types': 'string-value;123;true;', }); }); @@ -608,7 +608,7 @@ describe('request utils', () => { const result = httpHeadersToSpanAttributes(headers); expect(result).toEqual({ - 'http.request.header.string_header': ['valid-value'], + 'http.request.header.string_header': 'valid-value', }); }); @@ -626,8 +626,8 @@ describe('request utils', () => { const result = httpHeadersToSpanAttributes(headers, false); expect(result).toEqual({ - 'http.request.header.content_type': ['application/json'], - 'http.request.header.user_agent': ['test-agent'], + 'http.request.header.content_type': 'application/json', + 'http.request.header.user_agent': 'test-agent', // Sensitive headers should be filtered out }); }); @@ -644,11 +644,11 @@ describe('request utils', () => { const result = httpHeadersToSpanAttributes(headers, true); expect(result).toEqual({ - 'http.request.header.content_type': ['application/json'], - 'http.request.header.user_agent': ['test-agent'], - 'http.request.header.authorization': ['Bearer secret-token'], - 'http.request.header.cookie': ['session=abc123'], - 'http.request.header.x_api_key': ['api-key-123'], + 'http.request.header.content_type': 'application/json', + 'http.request.header.user_agent': 'test-agent', + 'http.request.header.authorization': 'Bearer secret-token', + 'http.request.header.cookie': 'session=abc123', + 'http.request.header.x_api_key': 'api-key-123', }); }); @@ -663,7 +663,7 @@ describe('request utils', () => { const result = httpHeadersToSpanAttributes(headers, false); expect(result).toEqual({ - 'http.request.header.content_type': ['application/json'], + 'http.request.header.content_type': 'application/json', }); }); @@ -697,10 +697,10 @@ describe('request utils', () => { const result = httpHeadersToSpanAttributes(headers, false); expect(result).toEqual({ - 'http.request.header.content_type': ['application/json'], - 'http.request.header.user_agent': ['test-agent'], - 'http.request.header.accept': ['application/json'], - 'http.request.header.host': ['example.com'], + 'http.request.header.content_type': 'application/json', + 'http.request.header.user_agent': 'test-agent', + 'http.request.header.accept': 'application/json', + 'http.request.header.host': 'example.com', }); }); }); diff --git a/packages/nextjs/src/common/utils/headersToAttributes.ts b/packages/nextjs/src/common/utils/headersToAttributes.ts index ea63e04e786c..4e8cdb3fe7c9 100644 --- a/packages/nextjs/src/common/utils/headersToAttributes.ts +++ b/packages/nextjs/src/common/utils/headersToAttributes.ts @@ -7,7 +7,7 @@ import { getClient, httpHeadersToSpanAttributes, winterCGHeadersToDict } from '@ export function addHeadersAsAttributes( headers: WebFetchHeaders | Headers | Record | undefined, span?: Span, -): Record { +): Record { if (!headers) { return {}; } diff --git a/packages/nextjs/src/common/wrapMiddlewareWithSentry.ts b/packages/nextjs/src/common/wrapMiddlewareWithSentry.ts index 950c2fc74a8e..f82b1a31f7a1 100644 --- a/packages/nextjs/src/common/wrapMiddlewareWithSentry.ts +++ b/packages/nextjs/src/common/wrapMiddlewareWithSentry.ts @@ -60,7 +60,7 @@ export function wrapMiddlewareWithSentry( let spanName: string; let spanSource: TransactionSource; - let headerAttributes: Record = {}; + let headerAttributes: Record = {}; if (req instanceof Request) { isolationScope.setSDKProcessingMetadata({ diff --git a/packages/nextjs/src/edge/wrapApiHandlerWithSentry.ts b/packages/nextjs/src/edge/wrapApiHandlerWithSentry.ts index 378925f74d0c..cd5649d0b1b4 100644 --- a/packages/nextjs/src/edge/wrapApiHandlerWithSentry.ts +++ b/packages/nextjs/src/edge/wrapApiHandlerWithSentry.ts @@ -32,7 +32,7 @@ export function wrapApiHandlerWithSentry( const req: unknown = args[0]; const currentScope = getCurrentScope(); - let headerAttributes: Record = {}; + let headerAttributes: Record = {}; if (req instanceof Request) { isolationScope.setSDKProcessingMetadata({ diff --git a/packages/node-core/src/integrations/http/incoming-requests.ts b/packages/node-core/src/integrations/http/incoming-requests.ts index 8b9e33968932..18ac6434d1f5 100644 --- a/packages/node-core/src/integrations/http/incoming-requests.ts +++ b/packages/node-core/src/integrations/http/incoming-requests.ts @@ -74,7 +74,7 @@ export function instrumentServer( // Extract HTTP request headers as span attributes const client = getClient(); const sendDefaultPii = client?.getOptions().sendDefaultPii ?? false; - const httpHeaderAttributes: Record = { + const httpHeaderAttributes: Record = { // fixme: adding the attributes here will add them to spans in node-core OTel setups (e.g. E2E test node-core-express-otel-v1) // However, adding this here will also add the span attributes to http.client spans in koa ...httpHeadersToSpanAttributes(normalizedRequest.headers || {}, sendDefaultPii), From 31e2899c8a95cc86c47f5a1df56e6aebaf23854c Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Tue, 2 Sep 2025 17:30:23 +0200 Subject: [PATCH 12/24] fix tests --- .../node-fastify-3/tests/propagation.test.ts | 10 ++++++++++ .../node-fastify-5/tests/propagation.test.ts | 20 ++++++++++++++++++- .../node-koa/tests/propagation.test.ts | 2 +- .../integrations/http/incoming-requests.ts | 4 ++-- 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/propagation.test.ts index df28721f7ee7..b03dc03fde00 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/propagation.test.ts @@ -245,7 +245,17 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { 'net.peer.port': expect.any(Number), 'http.status_code': 200, 'http.status_text': 'OK', + 'http.user_agent': 'node', 'http.route': '/test-inbound-headers/:id', + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.baggage': expect.any(String), + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.sentry_trace': expect.any(String), + 'http.request.header.user_agent': 'node', }, op: 'http.server', parent_span_id: outgoingHttpSpanId, diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/propagation.test.ts index beb4bc9abeb0..d9bd4de278a6 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/propagation.test.ts @@ -32,9 +32,10 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { const outgoingHttpSpanId = outgoingHttpSpan?.span_id; + // fixme: http headers are included here. If the span attributes are not added in node-core incoming-requests.ts, this works const outgoingHttpSpanData = outgoingHttpSpan?.data || {}; // Outgoing span (`http.client`) does not include headers as attributes - expect(Object.keys(outgoingHttpSpanData).some(key => key.startsWith('http.request.header.'))).toBe(false); + // expect(Object.keys(outgoingHttpSpanData).some(key => key.startsWith('http.request.header.'))).toBe(false); expect(traceId).toEqual(expect.any(String)); @@ -204,6 +205,13 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-outgoing-fetch/:id', + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': 'localhost:3030', + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': 'node', }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), @@ -234,7 +242,17 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { 'net.peer.port': expect.any(Number), 'http.status_code': 200, 'http.status_text': 'OK', + 'http.user_agent': 'node', 'http.route': '/test-inbound-headers/:id', + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.baggage': expect.any(String), + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': 'localhost:3030', + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.sentry_trace': 'ba8f28eba0f9a99418a09805e96fe8a2-9ce9349e6f1021dc-1', + 'http.request.header.user_agent': 'node', }, op: 'http.server', parent_span_id: outgoingHttpSpanId, diff --git a/dev-packages/e2e-tests/test-applications/node-koa/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/node-koa/tests/propagation.test.ts index c008397c1ed7..662a4bc5d8bf 100644 --- a/dev-packages/e2e-tests/test-applications/node-koa/tests/propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-koa/tests/propagation.test.ts @@ -33,7 +33,7 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { const outgoingHttpSpanData = outgoingHttpSpan?.data || {}; - // fixme: this is included here. If the span attributes are not added in node-core incoming-requests.ts, this works + // fixme: http headers are included here. If the span attributes are not added in node-core incoming-requests.ts, this works // Outgoing span (`http.client`) does not include headers as attributes // expect(Object.keys(outgoingHttpSpanData).some(key => key.startsWith('http.request.header.'))).toBe(false); diff --git a/packages/node-core/src/integrations/http/incoming-requests.ts b/packages/node-core/src/integrations/http/incoming-requests.ts index 18ac6434d1f5..990fbe275f55 100644 --- a/packages/node-core/src/integrations/http/incoming-requests.ts +++ b/packages/node-core/src/integrations/http/incoming-requests.ts @@ -75,8 +75,8 @@ export function instrumentServer( const client = getClient(); const sendDefaultPii = client?.getOptions().sendDefaultPii ?? false; const httpHeaderAttributes: Record = { - // fixme: adding the attributes here will add them to spans in node-core OTel setups (e.g. E2E test node-core-express-otel-v1) - // However, adding this here will also add the span attributes to http.client spans in koa + // Adding the attributes here will add them to spans in node-core OTel setups (e.g. E2E test node-core-express-otel-v1) + // fixme: However, adding this here will also add the span attributes to http.client spans in koa and fastify-5 ...httpHeadersToSpanAttributes(normalizedRequest.headers || {}, sendDefaultPii), }; From 3e9f94bf5f55f045411da8c0502b0a5988753eb7 Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Tue, 2 Sep 2025 17:31:30 +0200 Subject: [PATCH 13/24] fix type assertion --- packages/opentelemetry/src/spanProcessor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opentelemetry/src/spanProcessor.ts b/packages/opentelemetry/src/spanProcessor.ts index a7610a6bf76a..2143d3e09f92 100644 --- a/packages/opentelemetry/src/spanProcessor.ts +++ b/packages/opentelemetry/src/spanProcessor.ts @@ -54,7 +54,7 @@ function onSpanStart(span: Span, parentContext: Context): void { // Add HTTP request headers as span attributes for HTTP server spans if (spanData.op === 'http.server' || (spanData.data && 'http.method' in spanData.data)) { const sdkProcessingMetadata = scopes.isolationScope.getScopeData().sdkProcessingMetadata; - const httpHeaderAttributes = sdkProcessingMetadata?.httpHeaderAttributes as Record; + const httpHeaderAttributes = sdkProcessingMetadata?.httpHeaderAttributes as Record; // Those httpHeaderAttributes are set in @sentry/node-core for the incoming request if (httpHeaderAttributes && typeof httpHeaderAttributes === 'object') { From 54917339ce344856582631a119b5181267f4d7d1 Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Mon, 8 Sep 2025 11:25:14 +0200 Subject: [PATCH 14/24] add attributes to incoming-request --- .../tests/transactions.test.ts | 7 -- .../tests/transactions.test.ts | 7 -- .../tests/transactions.test.ts | 7 -- .../tests/transactions.test.ts | 7 -- .../node-fastify-5/tests/propagation.test.ts | 3 +- .../node-hapi/tests/transactions.test.ts | 9 +-- .../node-koa/tests/propagation.test.ts | 18 ++++- .../integrations/http/incoming-requests.ts | 15 +--- packages/node/src/integrations/http.ts | 17 ++-- packages/opentelemetry/src/spanProcessor.ts | 16 ---- yarn.lock | 81 ++++++++++++++++++- 11 files changed, 109 insertions(+), 78 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1-sdk-node/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1-sdk-node/tests/transactions.test.ts index 64baf74627d6..6141261d8954 100644 --- a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1-sdk-node/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1-sdk-node/tests/transactions.test.ts @@ -49,13 +49,6 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'net.peer.port': expect.any(Number), 'http.status_code': 200, 'http.status_text': 'OK', - 'http.request.header.accept': '*/*', - 'http.request.header.accept_encoding': 'gzip, deflate', - 'http.request.header.accept_language': '*', - 'http.request.header.connection': 'keep-alive', - 'http.request.header.host': expect.any(String), - 'http.request.header.sec_fetch_mode': 'cors', - 'http.request.header.user_agent': 'node', }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1/tests/transactions.test.ts index e43d9d2eab51..1628a9a03ada 100644 --- a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1/tests/transactions.test.ts @@ -37,13 +37,6 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'net.peer.port': expect.any(Number), 'http.status_code': 200, 'http.status_text': 'OK', - 'http.request.header.accept': '*/*', - 'http.request.header.accept_encoding': 'gzip, deflate', - 'http.request.header.accept_language': '*', - 'http.request.header.connection': 'keep-alive', - 'http.request.header.host': expect.any(String), - 'http.request.header.sec_fetch_mode': 'cors', - 'http.request.header.user_agent': 'node', }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-sdk-node/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-sdk-node/tests/transactions.test.ts index 9e532cb2b78a..08c8f80cd9f0 100644 --- a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-sdk-node/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-sdk-node/tests/transactions.test.ts @@ -49,13 +49,6 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'net.peer.port': expect.any(Number), 'http.status_code': 200, 'http.status_text': 'OK', - 'http.request.header.accept': '*/*', - 'http.request.header.accept_encoding': 'gzip, deflate', - 'http.request.header.accept_language': '*', - 'http.request.header.connection': 'keep-alive', - 'http.request.header.host': expect.any(String), - 'http.request.header.sec_fetch_mode': 'cors', - 'http.request.header.user_agent': 'node', }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2/tests/transactions.test.ts index e2f0c0f51a61..f3b1b680f2e9 100644 --- a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2/tests/transactions.test.ts @@ -37,13 +37,6 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'net.peer.port': expect.any(Number), 'http.status_code': 200, 'http.status_text': 'OK', - 'http.request.header.accept': '*/*', - 'http.request.header.accept_encoding': 'gzip, deflate', - 'http.request.header.accept_language': '*', - 'http.request.header.connection': 'keep-alive', - 'http.request.header.host': expect.any(String), - 'http.request.header.sec_fetch_mode': 'cors', - 'http.request.header.user_agent': 'node', }, op: 'http.server', span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/propagation.test.ts index d9bd4de278a6..88e93970a170 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/propagation.test.ts @@ -32,10 +32,9 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { const outgoingHttpSpanId = outgoingHttpSpan?.span_id; - // fixme: http headers are included here. If the span attributes are not added in node-core incoming-requests.ts, this works const outgoingHttpSpanData = outgoingHttpSpan?.data || {}; // Outgoing span (`http.client`) does not include headers as attributes - // expect(Object.keys(outgoingHttpSpanData).some(key => key.startsWith('http.request.header.'))).toBe(false); + expect(Object.keys(outgoingHttpSpanData).some(key => key.startsWith('http.request.header.'))).toBe(false); expect(traceId).toEqual(expect.any(String)); diff --git a/dev-packages/e2e-tests/test-applications/node-hapi/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-hapi/tests/transactions.test.ts index a644e69a27d1..077c692d24f0 100644 --- a/dev-packages/e2e-tests/test-applications/node-hapi/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-hapi/tests/transactions.test.ts @@ -64,6 +64,8 @@ test('Sends successful transaction', async ({ baseURL }) => { const spans = transactionEvent.spans || []; + expect(Object.keys(spans).some(key => key.startsWith('http.request.header.'))).toBe(false); + expect(spans).toEqual([ { data: { @@ -72,13 +74,6 @@ test('Sends successful transaction', async ({ baseURL }) => { 'http.route': '/test-success', 'sentry.op': 'router.hapi', 'sentry.origin': 'auto.http.otel.hapi', - 'http.request.header.accept': '*/*', - 'http.request.header.accept_encoding': 'gzip, deflate', - 'http.request.header.accept_language': '*', - 'http.request.header.connection': 'keep-alive', - 'http.request.header.host': expect.any(String), - 'http.request.header.sec_fetch_mode': 'cors', - 'http.request.header.user_agent': 'node', }, description: 'GET /test-success', op: 'router.hapi', diff --git a/dev-packages/e2e-tests/test-applications/node-koa/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/node-koa/tests/propagation.test.ts index 662a4bc5d8bf..592c5a4717f4 100644 --- a/dev-packages/e2e-tests/test-applications/node-koa/tests/propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-koa/tests/propagation.test.ts @@ -32,10 +32,8 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { const outgoingHttpSpanId = outgoingHttpSpan?.span_id; const outgoingHttpSpanData = outgoingHttpSpan?.data || {}; - - // fixme: http headers are included here. If the span attributes are not added in node-core incoming-requests.ts, this works // Outgoing span (`http.client`) does not include headers as attributes - // expect(Object.keys(outgoingHttpSpanData).some(key => key.startsWith('http.request.header.'))).toBe(false); + expect(Object.keys(outgoingHttpSpanData).some(key => key.startsWith('http.request.header.'))).toBe(false); expect(traceId).toEqual(expect.any(String)); @@ -118,6 +116,10 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-inbound-headers/:id', + 'http.request.header.baggage': expect.stringContaining(traceId!), // we already check if traceId is defined + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sentry_trace': expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/), }, op: 'http.server', parent_span_id: outgoingHttpSpanId, @@ -243,6 +245,16 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-inbound-headers/:id', + 'http.user_agent': 'node', + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.baggage': expect.stringContaining(traceId!), // we already check if traceId is defined + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.sentry_trace': expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/), + 'http.request.header.user_agent': 'node', }, op: 'http.server', parent_span_id: outgoingHttpSpanId, diff --git a/packages/node-core/src/integrations/http/incoming-requests.ts b/packages/node-core/src/integrations/http/incoming-requests.ts index 647e10e544b7..57588d0ac16e 100644 --- a/packages/node-core/src/integrations/http/incoming-requests.ts +++ b/packages/node-core/src/integrations/http/incoming-requests.ts @@ -133,18 +133,8 @@ export function instrumentServer( patchRequestToCaptureBody(request, isolationScope, maxIncomingRequestBodySize); } - // todo - // Extract HTTP request headers as span attributes - const client = getClient(); - const sendDefaultPii = client?.getOptions().sendDefaultPii ?? false; - const httpHeaderAttributes: Record = { - // Adding the attributes here will add them to spans in node-core OTel setups (e.g. E2E test node-core-express-otel-v1) - // fixme: However, adding this here will also add the span attributes to http.client spans in koa and fastify-5 - ...httpHeadersToSpanAttributes(normalizedRequest.headers || {}, sendDefaultPii), - }; - // Update the isolation scope, isolate this request - isolationScope.setSDKProcessingMetadata({ normalizedRequest, ipAddress, httpHeaderAttributes }); + isolationScope.setSDKProcessingMetadata({ normalizedRequest, ipAddress }); // attempt to update the scope's `transactionName` based on the request URL // Ideally, framework instrumentations coming after the HttpInstrumentation @@ -202,6 +192,8 @@ export function instrumentServer( const tracer = client.tracer; const scheme = fullUrl.startsWith('https') ? 'https' : 'http'; + const shouldSendDefaultPii = client?.getOptions().sendDefaultPii ?? false; + // We use the plain tracer.startSpan here so we can pass the span kind const span = tracer.startSpan(bestEffortTransactionName, { kind: SpanKind.SERVER, @@ -222,6 +214,7 @@ export function instrumentServer( 'http.flavor': httpVersion, 'net.transport': httpVersion?.toUpperCase() === 'QUIC' ? 'ip_udp' : 'ip_tcp', ...getRequestContentLengthAttribute(request), + ...httpHeadersToSpanAttributes(normalizedRequest.headers || {}, shouldSendDefaultPii), }, }); diff --git a/packages/node/src/integrations/http.ts b/packages/node/src/integrations/http.ts index d98ef1a5fa12..0ab3c1ec2ce3 100644 --- a/packages/node/src/integrations/http.ts +++ b/packages/node/src/integrations/http.ts @@ -3,7 +3,13 @@ import { diag } from '@opentelemetry/api'; import type { HttpInstrumentationConfig } from '@opentelemetry/instrumentation-http'; import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'; import type { Span } from '@sentry/core'; -import { defineIntegration, getClient, hasSpansEnabled, httpHeadersToSpanAttributes, stripUrlQueryAndFragment } from '@sentry/core'; +import { + defineIntegration, + getClient, + hasSpansEnabled, + httpHeadersToSpanAttributes, + stripUrlQueryAndFragment, +} from '@sentry/core'; import type { HTTPModuleRequestIncomingMessage, NodeClient } from '@sentry/node-core'; import { type SentryHttpInstrumentationOptions, @@ -266,15 +272,6 @@ function getConfigWithDefaults(options: Partial = {}): HttpInstrume requestHook: (span, req) => { addOriginToSpan(span, 'auto.http.otel.http'); - // todo - // Extract headers for incoming requests - if (!_isClientRequest(req)) { - const client = getClient(); - const sendDefaultPii = client?.getOptions().sendDefaultPii ?? false; - const headerAttributes = httpHeadersToSpanAttributes(req.headers, sendDefaultPii); - span.setAttributes(headerAttributes); - } - options.instrumentation?.requestHook?.(span, req); }, responseHook: (span, res) => { diff --git a/packages/opentelemetry/src/spanProcessor.ts b/packages/opentelemetry/src/spanProcessor.ts index 2143d3e09f92..3430456caaee 100644 --- a/packages/opentelemetry/src/spanProcessor.ts +++ b/packages/opentelemetry/src/spanProcessor.ts @@ -9,7 +9,6 @@ import { logSpanEnd, logSpanStart, setCapturedScopesOnSpan, - spanToJSON, } from '@sentry/core'; import { SEMANTIC_ATTRIBUTE_SENTRY_PARENT_IS_REMOTE } from './semanticAttributes'; import { SentrySpanExporter } from './spanExporter'; @@ -48,21 +47,6 @@ function onSpanStart(span: Span, parentContext: Context): void { logSpanStart(span); - if (scopes?.isolationScope) { - const spanData = spanToJSON(span); - - // Add HTTP request headers as span attributes for HTTP server spans - if (spanData.op === 'http.server' || (spanData.data && 'http.method' in spanData.data)) { - const sdkProcessingMetadata = scopes.isolationScope.getScopeData().sdkProcessingMetadata; - const httpHeaderAttributes = sdkProcessingMetadata?.httpHeaderAttributes as Record; - - // Those httpHeaderAttributes are set in @sentry/node-core for the incoming request - if (httpHeaderAttributes && typeof httpHeaderAttributes === 'object') { - span.setAttributes(httpHeaderAttributes); - } - } - } - const client = getClient(); client?.emit('spanStart', span); } diff --git a/yarn.lock b/yarn.lock index d22046031f12..f214bcffe365 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1583,6 +1583,13 @@ dependencies: "@babel/types" "^7.27.7" +"@babel/parser@^7.28.3": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.4.tgz#da25d4643532890932cc03f7705fe19637e03fa8" + integrity sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg== + dependencies: + "@babel/types" "^7.28.4" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.24.4": version "7.24.4" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.4.tgz#6125f0158543fb4edf1c22f322f3db67f21cb3e1" @@ -2652,6 +2659,14 @@ "@babel/helper-string-parser" "^7.27.1" "@babel/helper-validator-identifier" "^7.27.1" +"@babel/types@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.4.tgz#0a4e618f4c60a7cd6c11cb2d48060e4dbe38ac3a" + integrity sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + "@bcoe/v8-coverage@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz#bbe12dca5b4ef983a0d0af4b07b9bc90ea0ababa" @@ -4827,6 +4842,11 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== +"@jridgewell/sourcemap-codec@^1.5.5": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== + "@jridgewell/trace-mapping@0.3.9": version "0.3.9" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" @@ -9370,6 +9390,17 @@ estree-walker "^2.0.2" source-map-js "^1.2.1" +"@vue/compiler-core@3.5.21": + version "3.5.21" + resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.5.21.tgz#5915b19273f0492336f0beb227aba86813e2c8a8" + integrity sha512-8i+LZ0vf6ZgII5Z9XmUvrCyEzocvWT+TeR2VBUVlzIH6Tyv57E20mPZ1bCS+tbejgUgmjrEh7q/0F0bibskAmw== + dependencies: + "@babel/parser" "^7.28.3" + "@vue/shared" "3.5.21" + entities "^4.5.0" + estree-walker "^2.0.2" + source-map-js "^1.2.1" + "@vue/compiler-core@3.5.9": version "3.5.9" resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.5.9.tgz#d51fbfe6c18479b27fe6b1723344ba0832e4aacb" @@ -9397,6 +9428,14 @@ "@vue/compiler-core" "3.5.17" "@vue/shared" "3.5.17" +"@vue/compiler-dom@3.5.21": + version "3.5.21" + resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.21.tgz#26126447fe1e1d16c8cbac45b26e66b3f7175f65" + integrity sha512-jNtbu/u97wiyEBJlJ9kmdw7tAr5Vy0Aj5CgQmo+6pxWNQhXZDPsRr1UWPN4v3Zf82s2H3kF51IbzZ4jMWAgPlQ== + dependencies: + "@vue/compiler-core" "3.5.21" + "@vue/shared" "3.5.21" + "@vue/compiler-dom@3.5.9": version "3.5.9" resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.9.tgz#6fa2b7e536ae4c416fc2d60b7e9e33b3410eac7a" @@ -9451,6 +9490,21 @@ postcss "^8.5.6" source-map-js "^1.2.1" +"@vue/compiler-sfc@^3.5.13": + version "3.5.21" + resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.5.21.tgz#e48189ef3ffe334c864c2625389ebe3bb4fa41eb" + integrity sha512-SXlyk6I5eUGBd2v8Ie7tF6ADHE9kCR6mBEuPyH1nUZ0h6Xx6nZI29i12sJKQmzbDyr2tUHMhhTt51Z6blbkTTQ== + dependencies: + "@babel/parser" "^7.28.3" + "@vue/compiler-core" "3.5.21" + "@vue/compiler-dom" "3.5.21" + "@vue/compiler-ssr" "3.5.21" + "@vue/shared" "3.5.21" + estree-walker "^2.0.2" + magic-string "^0.30.18" + postcss "^8.5.6" + source-map-js "^1.2.1" + "@vue/compiler-ssr@3.2.45": version "3.2.45" resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.45.tgz#bd20604b6e64ea15344d5b6278c4141191c983b2" @@ -9467,6 +9521,14 @@ "@vue/compiler-dom" "3.5.17" "@vue/shared" "3.5.17" +"@vue/compiler-ssr@3.5.21": + version "3.5.21" + resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.5.21.tgz#f351c27aa5c075faa609596b2269c53df0df3aa1" + integrity sha512-vKQ5olH5edFZdf5ZrlEgSO1j1DMA4u23TVK5XR1uMhvwnYvVdDF0nHXJUblL/GvzlShQbjhZZ2uvYmDlAbgo9w== + dependencies: + "@vue/compiler-dom" "3.5.21" + "@vue/shared" "3.5.21" + "@vue/compiler-ssr@3.5.9": version "3.5.9" resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.5.9.tgz#e30f8e866589392421abcbfc0e0241470f3ca9a6" @@ -9611,6 +9673,11 @@ resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.17.tgz#e8b3a41f0be76499882a89e8ed40d86a70fa4b70" integrity sha512-CabR+UN630VnsJO/jHWYBC1YVXyMq94KKp6iF5MQgZJs5I8cmjw6oVMO1oDbtBkENSHSSn/UadWlW/OAgdmKrg== +"@vue/shared@3.5.21": + version "3.5.21" + resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.21.tgz#505edb122629d1979f70a2a65ca0bd4050dc2e54" + integrity sha512-+2k1EQpnYuVuu3N7atWyG3/xoFWIVJZq4Mz8XNOdScFI0etES75fbny/oU4lKWk/577P1zmg0ioYvpGEDZ3DLw== + "@vue/shared@3.5.9": version "3.5.9" resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.9.tgz#713257216ea2cbf4e200cb9ae395c34ae2349385" @@ -21233,6 +21300,13 @@ magic-string@^0.30.0, magic-string@^0.30.10, magic-string@^0.30.11, magic-string dependencies: "@jridgewell/sourcemap-codec" "^1.5.0" +magic-string@^0.30.18: + version "0.30.18" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.18.tgz#905bfbbc6aa5692703a93db26a9edcaa0007d2bb" + integrity sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.5" + magicast@^0.2.10: version "0.2.11" resolved "https://registry.yarnpkg.com/magicast/-/magicast-0.2.11.tgz#d5d9339ec59e5322cf331460d8e3db2f6585f5d5" @@ -23004,6 +23078,11 @@ node-cron@^3.0.3: dependencies: uuid "8.3.2" +node-domexception@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" + integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== + node-fetch-native@^1.4.0, node-fetch-native@^1.6.3, node-fetch-native@^1.6.4, node-fetch-native@^1.6.6: version "1.6.6" resolved "https://registry.yarnpkg.com/node-fetch-native/-/node-fetch-native-1.6.6.tgz#ae1d0e537af35c2c0b0de81cbff37eedd410aa37" @@ -31109,7 +31188,7 @@ web-namespaces@^2.0.0: resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-2.0.1.tgz#1010ff7c650eccb2592cebeeaf9a1b253fd40692" integrity sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ== -web-streams-polyfill@^3.1.1: +web-streams-polyfill@^3.0.3, web-streams-polyfill@^3.1.1: version "3.3.3" resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz#2073b91a2fdb1fbfbd401e7de0ac9f8214cecb4b" integrity sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw== From d78fa05921ec71eb87b6a97d5ca0ba642800c28a Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Mon, 8 Sep 2025 11:34:45 +0200 Subject: [PATCH 15/24] fix problems --- .../node-fastify-3/tests/propagation.test.ts | 4 ++-- .../node-fastify-4/tests/propagation.test.ts | 2 +- .../node-fastify-5/tests/propagation.test.ts | 4 ++-- packages/astro/src/server/middleware.ts | 15 ++++++++------- packages/node/src/integrations/http.ts | 8 +------- 5 files changed, 14 insertions(+), 19 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/propagation.test.ts index b03dc03fde00..1cdfd67a4851 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/propagation.test.ts @@ -120,7 +120,7 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'http.request.header.baggage': expect.any(String), 'http.request.header.connection': 'keep-alive', 'http.request.header.host': expect.any(String), - 'http.request.header.sentry_trace': expect.stringContaining(traceId || ''), + 'http.request.header.sentry_trace': expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/), }, op: 'http.server', parent_span_id: outgoingHttpSpanId, @@ -254,7 +254,7 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { 'http.request.header.connection': 'keep-alive', 'http.request.header.host': expect.any(String), 'http.request.header.sec_fetch_mode': 'cors', - 'http.request.header.sentry_trace': expect.any(String), + 'http.request.header.sentry_trace': expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/), 'http.request.header.user_agent': 'node', }, op: 'http.server', diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-4/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify-4/tests/propagation.test.ts index 75bbf8f252bf..2d92b571f203 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-4/tests/propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-fastify-4/tests/propagation.test.ts @@ -120,7 +120,7 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'http.request.header.baggage': expect.any(String), 'http.request.header.connection': 'keep-alive', 'http.request.header.host': expect.any(String), - 'http.request.header.sentry_trace': expect.stringContaining(traceId || ''), + 'http.request.header.sentry_trace': expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/), }, op: 'http.server', parent_span_id: outgoingHttpSpanId, diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/propagation.test.ts index 88e93970a170..6605cf0c55be 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/propagation.test.ts @@ -248,9 +248,9 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { 'http.request.header.accept_language': '*', 'http.request.header.baggage': expect.any(String), 'http.request.header.connection': 'keep-alive', - 'http.request.header.host': 'localhost:3030', + 'http.request.header.host': expect.any(String), 'http.request.header.sec_fetch_mode': 'cors', - 'http.request.header.sentry_trace': 'ba8f28eba0f9a99418a09805e96fe8a2-9ce9349e6f1021dc-1', + 'http.request.header.sentry_trace': expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/), 'http.request.header.user_agent': 'node', }, op: 'http.server', diff --git a/packages/astro/src/server/middleware.ts b/packages/astro/src/server/middleware.ts index 0491cd4252d8..61f7913cf1b1 100644 --- a/packages/astro/src/server/middleware.ts +++ b/packages/astro/src/server/middleware.ts @@ -219,13 +219,14 @@ async function instrumentRequestStartHttpServerSpan( [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.astro', [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source, [SEMANTIC_ATTRIBUTE_HTTP_REQUEST_METHOD]: method, - // This is here for backwards compatibility, we used to set this here beforemethod, - url: stripUrlQueryAndFragment(ctx.url.href), - ...httpHeadersToSpanAttributes( - winterCGHeadersToDict(request.headers), - getClient()?.getOptions().sendDefaultPii ?? false, - ), - }; + // This is here for backwards compatibility, we used to set this here before + method, + url: stripUrlQueryAndFragment(ctx.url.href), + ...httpHeadersToSpanAttributes( + winterCGHeadersToDict(request.headers), + getClient()?.getOptions().sendDefaultPii ?? false, + ), + }; if (parametrizedRoute) { attributes['http.route'] = parametrizedRoute; diff --git a/packages/node/src/integrations/http.ts b/packages/node/src/integrations/http.ts index 0ab3c1ec2ce3..60999b108872 100644 --- a/packages/node/src/integrations/http.ts +++ b/packages/node/src/integrations/http.ts @@ -3,13 +3,7 @@ import { diag } from '@opentelemetry/api'; import type { HttpInstrumentationConfig } from '@opentelemetry/instrumentation-http'; import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'; import type { Span } from '@sentry/core'; -import { - defineIntegration, - getClient, - hasSpansEnabled, - httpHeadersToSpanAttributes, - stripUrlQueryAndFragment, -} from '@sentry/core'; +import { defineIntegration, getClient, hasSpansEnabled } from '@sentry/core'; import type { HTTPModuleRequestIncomingMessage, NodeClient } from '@sentry/node-core'; import { type SentryHttpInstrumentationOptions, From 8c9478e74b02426e2b46f9dc75361cf2f2b4f65d Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Mon, 8 Sep 2025 17:00:41 +0200 Subject: [PATCH 16/24] fix tests --- .../astro-4/tests/tracing.dynamic.test.ts | 25 +++++++++++ .../astro-5/tests/tracing.dynamic.test.ts | 25 +++++++++++ .../tests/tracing.serverIslands.test.ts | 5 +++ .../nestjs-fastify/tests/transactions.test.ts | 7 --- .../tests/sampling.test.ts | 7 --- .../tests/sampling.test.ts | 7 --- .../node-hapi/tests/transactions.test.ts | 4 +- packages/astro/test/server/middleware.test.ts | 44 ------------------- 8 files changed, 58 insertions(+), 66 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/astro-4/tests/tracing.dynamic.test.ts b/dev-packages/e2e-tests/test-applications/astro-4/tests/tracing.dynamic.test.ts index 07e0467382da..4d3d93fdf81e 100644 --- a/dev-packages/e2e-tests/test-applications/astro-4/tests/tracing.dynamic.test.ts +++ b/dev-packages/e2e-tests/test-applications/astro-4/tests/tracing.dynamic.test.ts @@ -78,6 +78,11 @@ test.describe('tracing in dynamically rendered (ssr) routes', () => { 'sentry.sample_rate': 1, 'sentry.source': 'route', url: expect.stringContaining('/test-ssr'), + 'http.request.header.accept': expect.any(String), + 'http.request.header.accept_encoding': 'gzip, deflate, br, zstd', + 'http.request.header.accept_language': 'en-US', + 'http.request.header.sec_fetch_mode': 'navigate', + 'http.request.header.user_agent': expect.any(String), }, op: 'http.server', origin: 'auto.http.astro', @@ -223,6 +228,11 @@ test.describe('nested SSR routes (client, server, server request)', () => { 'sentry.origin': 'auto.http.astro', 'sentry.source': 'route', url: expect.stringContaining('/user-page/myUsername123'), + 'http.request.header.accept': expect.any(String), + 'http.request.header.accept_encoding': 'gzip, deflate, br, zstd', + 'http.request.header.accept_language': 'en-US', + 'http.request.header.sec_fetch_mode': 'navigate', + 'http.request.header.user_agent': expect.any(String), }, }, }, @@ -256,6 +266,11 @@ test.describe('nested SSR routes (client, server, server request)', () => { 'sentry.origin': 'auto.http.astro', 'sentry.source': 'route', url: expect.stringContaining('/api/user/myUsername123.json'), + 'http.request.header.accept': expect.any(String), + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': expect.any(String), }, }, }, @@ -308,6 +323,11 @@ test.describe('nested SSR routes (client, server, server request)', () => { 'sentry.origin': 'auto.http.astro', 'sentry.source': 'route', url: expect.stringContaining('/catchAll/hell0/whatever-do'), + 'http.request.header.accept': expect.any(String), + 'http.request.header.accept_encoding': 'gzip, deflate, br, zstd', + 'http.request.header.accept_language': 'en-US', + 'http.request.header.sec_fetch_mode': 'navigate', + 'http.request.header.user_agent': expect.any(String), }, }, }, @@ -360,6 +380,11 @@ test.describe('parametrized vs static paths', () => { 'sentry.origin': 'auto.http.astro', 'sentry.source': 'route', url: expect.stringContaining('/user-page/settings'), + 'http.request.header.accept': expect.any(String), + 'http.request.header.accept_encoding': 'gzip, deflate, br, zstd', + 'http.request.header.accept_language': 'en-US', + 'http.request.header.sec_fetch_mode': 'navigate', + 'http.request.header.user_agent': expect.any(String), }, }, }, diff --git a/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.dynamic.test.ts b/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.dynamic.test.ts index 9151c13907af..0c69d1f59698 100644 --- a/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.dynamic.test.ts +++ b/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.dynamic.test.ts @@ -79,6 +79,11 @@ test.describe('tracing in dynamically rendered (ssr) routes', () => { 'sentry.sample_rate': 1, 'sentry.source': 'route', url: expect.stringContaining('/test-ssr'), + 'http.request.header.accept': expect.any(String), + 'http.request.header.accept_encoding': 'gzip, deflate, br, zstd', + 'http.request.header.accept_language': 'en-US', + 'http.request.header.sec_fetch_mode': 'navigate', + 'http.request.header.user_agent': expect.any(String), }, op: 'http.server', origin: 'auto.http.astro', @@ -226,6 +231,11 @@ test.describe('nested SSR routes (client, server, server request)', () => { 'sentry.origin': 'auto.http.astro', 'sentry.source': 'route', url: expect.stringContaining('/user-page/myUsername123'), + 'http.request.header.accept': expect.any(String), + 'http.request.header.accept_encoding': 'gzip, deflate, br, zstd', + 'http.request.header.accept_language': 'en-US', + 'http.request.header.sec_fetch_mode': 'navigate', + 'http.request.header.user_agent': expect.any(String), }, }, }, @@ -259,6 +269,11 @@ test.describe('nested SSR routes (client, server, server request)', () => { 'sentry.origin': 'auto.http.astro', 'sentry.source': 'route', url: expect.stringContaining('/api/user/myUsername123.json'), + 'http.request.header.accept': expect.any(String), + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': expect.any(String), }, }, }, @@ -311,6 +326,11 @@ test.describe('nested SSR routes (client, server, server request)', () => { 'sentry.origin': 'auto.http.astro', 'sentry.source': 'route', url: expect.stringContaining('/catchAll/hell0/whatever-do'), + 'http.request.header.accept': expect.any(String), + 'http.request.header.accept_encoding': 'gzip, deflate, br, zstd', + 'http.request.header.accept_language': 'en-US', + 'http.request.header.sec_fetch_mode': 'navigate', + 'http.request.header.user_agent': expect.any(String), }, }, }, @@ -363,6 +383,11 @@ test.describe('parametrized vs static paths', () => { 'sentry.origin': 'auto.http.astro', 'sentry.source': 'route', url: expect.stringContaining('/user-page/settings'), + 'http.request.header.accept': expect.any(String), + 'http.request.header.accept_encoding': 'gzip, deflate, br, zstd', + 'http.request.header.accept_language': 'en-US', + 'http.request.header.sec_fetch_mode': 'navigate', + 'http.request.header.user_agent': expect.any(String), }, }, }, diff --git a/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.serverIslands.test.ts b/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.serverIslands.test.ts index c7496d4e6247..354655a4ac7e 100644 --- a/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.serverIslands.test.ts +++ b/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.serverIslands.test.ts @@ -70,6 +70,11 @@ test.describe('tracing in static routes with server islands', () => { 'sentry.op': 'http.server', 'sentry.origin': 'auto.http.astro', 'sentry.source': 'route', + 'http.request.header.accept': expect.any(String), + 'http.request.header.accept_encoding': 'gzip, deflate, br, zstd', + 'http.request.header.accept_language': 'en-US', + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.user_agent': expect.any(String), }), op: 'http.server', origin: 'auto.http.astro', diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts index c7cd61374c6c..9730bfd6fa68 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts @@ -110,13 +110,6 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'nestjs.controller': 'AppController', 'nestjs.callback': 'testTransaction', url: '/test-transaction', - 'http.request.header.accept': '*/*', - 'http.request.header.accept_encoding': 'gzip, deflate', - 'http.request.header.accept_language': '*', - 'http.request.header.connection': 'keep-alive', - 'http.request.header.host': expect.any(String), - 'http.request.header.sec_fetch_mode': 'cors', - 'http.request.header.user_agent': 'node', }, description: 'GET /test-transaction', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1-custom-sampler/tests/sampling.test.ts b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1-custom-sampler/tests/sampling.test.ts index b4ba3b219546..60e2424552cd 100644 --- a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1-custom-sampler/tests/sampling.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1-custom-sampler/tests/sampling.test.ts @@ -35,13 +35,6 @@ test('Sends a sampled API route transaction', async ({ baseURL }) => { 'net.peer.port': expect.any(Number), 'http.status_code': 200, 'http.status_text': 'OK', - 'http.request.header.accept': '*/*', - 'http.request.header.accept_encoding': 'gzip, deflate', - 'http.request.header.accept_language': '*', - 'http.request.header.connection': 'keep-alive', - 'http.request.header.host': expect.any(String), - 'http.request.header.sec_fetch_mode': 'cors', - 'http.request.header.user_agent': 'node', }, origin: 'manual', op: 'http.server', diff --git a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-custom-sampler/tests/sampling.test.ts b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-custom-sampler/tests/sampling.test.ts index 5c54309923ae..134f9f22b429 100644 --- a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-custom-sampler/tests/sampling.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-custom-sampler/tests/sampling.test.ts @@ -35,13 +35,6 @@ test('Sends a sampled API route transaction', async ({ baseURL }) => { 'net.peer.port': expect.any(Number), 'http.status_code': 200, 'http.status_text': 'OK', - 'http.request.header.accept': '*/*', - 'http.request.header.accept_encoding': 'gzip, deflate', - 'http.request.header.accept_language': '*', - 'http.request.header.connection': 'keep-alive', - 'http.request.header.host': expect.any(String), - 'http.request.header.sec_fetch_mode': 'cors', - 'http.request.header.user_agent': 'node', }, origin: 'manual', op: 'http.server', diff --git a/dev-packages/e2e-tests/test-applications/node-hapi/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-hapi/tests/transactions.test.ts index 077c692d24f0..bd6540b088d3 100644 --- a/dev-packages/e2e-tests/test-applications/node-hapi/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-hapi/tests/transactions.test.ts @@ -64,7 +64,9 @@ test('Sends successful transaction', async ({ baseURL }) => { const spans = transactionEvent.spans || []; - expect(Object.keys(spans).some(key => key.startsWith('http.request.header.'))).toBe(false); + spans.forEach(span => { + expect(Object.keys(span.data).some(key => key.startsWith('http.request.header.'))).toBe(false); + }); expect(spans).toEqual([ { diff --git a/packages/astro/test/server/middleware.test.ts b/packages/astro/test/server/middleware.test.ts index e5e57afc2fd7..03933582c846 100644 --- a/packages/astro/test/server/middleware.test.ts +++ b/packages/astro/test/server/middleware.test.ts @@ -114,50 +114,6 @@ describe('sentryMiddleware', () => { expect(resultFromNext).toStrictEqual(nextResult); }); - it('includes HTTP request headers as span attributes', async () => { - const middleware = handleRequest(); - const headers = new Headers({ - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', - 'Content-Type': 'application/json', - 'X-Custom-Header': 'custom-value', - Accept: 'application/json, text/plain', - }); - - const ctx = { - request: { - method: 'POST', - url: '/api/data', - headers, - }, - url: new URL('https://myDomain.io/api/data'), - params: {}, - }; - const next = vi.fn(() => nextResult); - - // @ts-expect-error, a partial ctx object is fine here - await middleware(ctx, next); - - expect(startSpanSpy).toHaveBeenCalledWith( - { - attributes: { - 'sentry.origin': 'auto.http.astro', - method: 'POST', - url: 'https://mydomain.io/api/data', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - 'http.request.header.user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', - 'http.request.header.content_type': 'application/json', - 'http.request.header.x_custom_header': 'custom-value', - 'http.request.header.accept': 'application/json, text/plain', - }, - name: 'POST /api/data', - op: 'http.server', - }, - expect.any(Function), - ); - - expect(next).toHaveBeenCalled(); - }); - it("sets source route if the url couldn't be decoded correctly", async () => { const middleware = handleRequest(); const ctx = { From 217b75141be090e1fa50237123f1caa72f59d680 Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Mon, 8 Sep 2025 17:36:49 +0200 Subject: [PATCH 17/24] fix fastify tests --- .../nextjs-15/tests/pageload-tracing.test.ts | 3 +++ .../node-fastify-4/tests/propagation.test.ts | 10 ++++++++++ .../node-fastify-5/tests/propagation.test.ts | 6 +++++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/tests/pageload-tracing.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/pageload-tracing.test.ts index c9a9fc44bdba..f81214b4b18f 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15/tests/pageload-tracing.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/pageload-tracing.test.ts @@ -41,6 +41,9 @@ test('extracts HTTP request headers as span attributes', async ({ baseURL }) => const serverTransaction = await serverTransactionPromise; + console.log('serverTransaction', serverTransaction.contexts?.trace?.data); + + // FIXME: This test fails with Turbopack enabled expect(serverTransaction.contexts?.trace?.data).toEqual( expect.objectContaining({ 'http.request.header.user_agent': 'Custom-NextJS-Agent/15.0', diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-4/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify-4/tests/propagation.test.ts index 2d92b571f203..6e6b20b916e8 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-4/tests/propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-fastify-4/tests/propagation.test.ts @@ -246,6 +246,16 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-inbound-headers/:id', + 'http.user_agent': 'node', + 'http.request.header.accept': '*/*', + 'http.request.header.accept_encoding': 'gzip, deflate', + 'http.request.header.accept_language': '*', + 'http.request.header.baggage': expect.any(String), + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sec_fetch_mode': 'cors', + 'http.request.header.sentry_trace': expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/), + 'http.request.header.user_agent': 'node', }, op: 'http.server', parent_span_id: outgoingHttpSpanId, diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/propagation.test.ts index 6605cf0c55be..4e903edf05b5 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/propagation.test.ts @@ -117,6 +117,10 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/test-inbound-headers/:id', + 'http.request.header.baggage': expect.any(String), + 'http.request.header.connection': 'keep-alive', + 'http.request.header.host': expect.any(String), + 'http.request.header.sentry_trace': expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/), }, op: 'http.server', parent_span_id: outgoingHttpSpanId, @@ -208,7 +212,7 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { 'http.request.header.accept_encoding': 'gzip, deflate', 'http.request.header.accept_language': '*', 'http.request.header.connection': 'keep-alive', - 'http.request.header.host': 'localhost:3030', + 'http.request.header.host': expect.any(String), 'http.request.header.sec_fetch_mode': 'cors', 'http.request.header.user_agent': 'node', }, From f5973c7644452bf189be4584f09b017e8aa3cf37 Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Mon, 8 Sep 2025 17:40:37 +0200 Subject: [PATCH 18/24] remove log --- .../test-applications/nextjs-15/tests/pageload-tracing.test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/tests/pageload-tracing.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/pageload-tracing.test.ts index f81214b4b18f..ddce3f35709f 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15/tests/pageload-tracing.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/pageload-tracing.test.ts @@ -41,8 +41,6 @@ test('extracts HTTP request headers as span attributes', async ({ baseURL }) => const serverTransaction = await serverTransactionPromise; - console.log('serverTransaction', serverTransaction.contexts?.trace?.data); - // FIXME: This test fails with Turbopack enabled expect(serverTransaction.contexts?.trace?.data).toEqual( expect.objectContaining({ From 35da0634ce78d543eff0fde778dbc25f6a09315b Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Mon, 8 Sep 2025 17:40:37 +0200 Subject: [PATCH 19/24] update lock file --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index b805c7d43906..c532ad020359 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21301,9 +21301,9 @@ magic-string@^0.30.0, magic-string@^0.30.10, magic-string@^0.30.11, magic-string "@jridgewell/sourcemap-codec" "^1.5.5" magic-string@^0.30.18: - version "0.30.18" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.18.tgz#905bfbbc6aa5692703a93db26a9edcaa0007d2bb" - integrity sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ== + version "0.30.19" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.19.tgz#cebe9f104e565602e5d2098c5f2e79a77cc86da9" + integrity sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw== dependencies: "@jridgewell/sourcemap-codec" "^1.5.5" From 8f410c13f1ff583d7d2d5d7eafb42c4c80cc8b2e Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Mon, 8 Sep 2025 17:52:04 +0200 Subject: [PATCH 20/24] update lock file --- yarn.lock | 74 ------------------------------------------------------- 1 file changed, 74 deletions(-) diff --git a/yarn.lock b/yarn.lock index c532ad020359..a9f6f22ae5e8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1583,13 +1583,6 @@ dependencies: "@babel/types" "^7.28.4" -"@babel/parser@^7.28.3": - version "7.28.4" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.4.tgz#da25d4643532890932cc03f7705fe19637e03fa8" - integrity sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg== - dependencies: - "@babel/types" "^7.28.4" - "@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.24.4": version "7.24.4" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.4.tgz#6125f0158543fb4edf1c22f322f3db67f21cb3e1" @@ -2659,14 +2652,6 @@ "@babel/helper-string-parser" "^7.27.1" "@babel/helper-validator-identifier" "^7.27.1" -"@babel/types@^7.28.4": - version "7.28.4" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.4.tgz#0a4e618f4c60a7cd6c11cb2d48060e4dbe38ac3a" - integrity sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q== - dependencies: - "@babel/helper-string-parser" "^7.27.1" - "@babel/helper-validator-identifier" "^7.27.1" - "@bcoe/v8-coverage@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz#bbe12dca5b4ef983a0d0af4b07b9bc90ea0ababa" @@ -4842,11 +4827,6 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== -"@jridgewell/sourcemap-codec@^1.5.5": - version "1.5.5" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" - integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== - "@jridgewell/trace-mapping@0.3.9": version "0.3.9" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" @@ -9390,17 +9370,6 @@ estree-walker "^2.0.2" source-map-js "^1.2.1" -"@vue/compiler-core@3.5.21": - version "3.5.21" - resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.5.21.tgz#5915b19273f0492336f0beb227aba86813e2c8a8" - integrity sha512-8i+LZ0vf6ZgII5Z9XmUvrCyEzocvWT+TeR2VBUVlzIH6Tyv57E20mPZ1bCS+tbejgUgmjrEh7q/0F0bibskAmw== - dependencies: - "@babel/parser" "^7.28.3" - "@vue/shared" "3.5.21" - entities "^4.5.0" - estree-walker "^2.0.2" - source-map-js "^1.2.1" - "@vue/compiler-core@3.5.9": version "3.5.9" resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.5.9.tgz#d51fbfe6c18479b27fe6b1723344ba0832e4aacb" @@ -9428,14 +9397,6 @@ "@vue/compiler-core" "3.5.21" "@vue/shared" "3.5.21" -"@vue/compiler-dom@3.5.21": - version "3.5.21" - resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.21.tgz#26126447fe1e1d16c8cbac45b26e66b3f7175f65" - integrity sha512-jNtbu/u97wiyEBJlJ9kmdw7tAr5Vy0Aj5CgQmo+6pxWNQhXZDPsRr1UWPN4v3Zf82s2H3kF51IbzZ4jMWAgPlQ== - dependencies: - "@vue/compiler-core" "3.5.21" - "@vue/shared" "3.5.21" - "@vue/compiler-dom@3.5.9": version "3.5.9" resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.9.tgz#6fa2b7e536ae4c416fc2d60b7e9e33b3410eac7a" @@ -9490,21 +9451,6 @@ postcss "^8.5.6" source-map-js "^1.2.1" -"@vue/compiler-sfc@^3.5.13": - version "3.5.21" - resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.5.21.tgz#e48189ef3ffe334c864c2625389ebe3bb4fa41eb" - integrity sha512-SXlyk6I5eUGBd2v8Ie7tF6ADHE9kCR6mBEuPyH1nUZ0h6Xx6nZI29i12sJKQmzbDyr2tUHMhhTt51Z6blbkTTQ== - dependencies: - "@babel/parser" "^7.28.3" - "@vue/compiler-core" "3.5.21" - "@vue/compiler-dom" "3.5.21" - "@vue/compiler-ssr" "3.5.21" - "@vue/shared" "3.5.21" - estree-walker "^2.0.2" - magic-string "^0.30.18" - postcss "^8.5.6" - source-map-js "^1.2.1" - "@vue/compiler-ssr@3.2.45": version "3.2.45" resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.45.tgz#bd20604b6e64ea15344d5b6278c4141191c983b2" @@ -9521,14 +9467,6 @@ "@vue/compiler-dom" "3.5.21" "@vue/shared" "3.5.21" -"@vue/compiler-ssr@3.5.21": - version "3.5.21" - resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.5.21.tgz#f351c27aa5c075faa609596b2269c53df0df3aa1" - integrity sha512-vKQ5olH5edFZdf5ZrlEgSO1j1DMA4u23TVK5XR1uMhvwnYvVdDF0nHXJUblL/GvzlShQbjhZZ2uvYmDlAbgo9w== - dependencies: - "@vue/compiler-dom" "3.5.21" - "@vue/shared" "3.5.21" - "@vue/compiler-ssr@3.5.9": version "3.5.9" resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.5.9.tgz#e30f8e866589392421abcbfc0e0241470f3ca9a6" @@ -9673,11 +9611,6 @@ resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.21.tgz#505edb122629d1979f70a2a65ca0bd4050dc2e54" integrity sha512-+2k1EQpnYuVuu3N7atWyG3/xoFWIVJZq4Mz8XNOdScFI0etES75fbny/oU4lKWk/577P1zmg0ioYvpGEDZ3DLw== -"@vue/shared@3.5.21": - version "3.5.21" - resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.21.tgz#505edb122629d1979f70a2a65ca0bd4050dc2e54" - integrity sha512-+2k1EQpnYuVuu3N7atWyG3/xoFWIVJZq4Mz8XNOdScFI0etES75fbny/oU4lKWk/577P1zmg0ioYvpGEDZ3DLw== - "@vue/shared@3.5.9": version "3.5.9" resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.9.tgz#713257216ea2cbf4e200cb9ae395c34ae2349385" @@ -21300,13 +21233,6 @@ magic-string@^0.30.0, magic-string@^0.30.10, magic-string@^0.30.11, magic-string dependencies: "@jridgewell/sourcemap-codec" "^1.5.5" -magic-string@^0.30.18: - version "0.30.19" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.19.tgz#cebe9f104e565602e5d2098c5f2e79a77cc86da9" - integrity sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw== - dependencies: - "@jridgewell/sourcemap-codec" "^1.5.5" - magicast@^0.2.10: version "0.2.11" resolved "https://registry.yarnpkg.com/magicast/-/magicast-0.2.11.tgz#d5d9339ec59e5322cf331460d8e3db2f6585f5d5" From 61532680a41a874f71b0b15a496f162fb34d1715 Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Tue, 9 Sep 2025 10:24:47 +0200 Subject: [PATCH 21/24] exclude turbopack test --- .../e2e-tests/test-applications/nextjs-15/package.json | 3 ++- .../test-applications/nextjs-15/playwright.config.mjs | 2 +- .../nextjs-15/tests/pageload-tracing.test.ts | 6 +++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/package.json b/dev-packages/e2e-tests/test-applications/nextjs-15/package.json index 052dd62697a1..85ead75fc525 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/package.json @@ -6,6 +6,7 @@ "build": "next build > .tmp_build_stdout 2> .tmp_build_stderr || (cat .tmp_build_stdout && cat .tmp_build_stderr && exit 1)", "clean": "npx rimraf node_modules pnpm-lock.yaml .tmp_dev_server_logs", "test:prod": "TEST_ENV=production playwright test", + "test:prod-turbo": "TEST_ENV=production-turbo playwright test", "test:dev": "TEST_ENV=development playwright test", "test:dev-turbo": "TEST_ENV=dev-turbopack playwright test", "test:build": "pnpm install && pnpm build", @@ -46,7 +47,7 @@ }, { "build-command": "pnpm test:build-turbo", - "assert-command": "pnpm test:prod && pnpm test:dev-turbo", + "assert-command": "pnpm test:prod-turbo && pnpm test:dev-turbo", "label": "nextjs-15 (turbo)" } ] diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/nextjs-15/playwright.config.mjs index e1be6810f4dc..beab3285530b 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15/playwright.config.mjs +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/playwright.config.mjs @@ -14,7 +14,7 @@ const getStartCommand = () => { return 'pnpm next dev -p 3030 2>&1 | tee .tmp_dev_server_logs'; } - if (testEnv === 'production') { + if (testEnv === 'production' || testEnv === 'production-turbo') { return 'pnpm next start -p 3030'; } diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/tests/pageload-tracing.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/pageload-tracing.test.ts index ddce3f35709f..1f96759cb80b 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15/tests/pageload-tracing.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/pageload-tracing.test.ts @@ -24,6 +24,11 @@ test('App router transactions should be attached to the pageload request span', }); test('extracts HTTP request headers as span attributes', async ({ baseURL }) => { + // FIXME: This test fails with Turbopack enabled + if (process.env.TEST_ENV === 'production-turbo') { + return; + } + const serverTransactionPromise = waitForTransaction('nextjs-15', async transactionEvent => { return transactionEvent?.transaction === 'GET /pageload-tracing'; }); @@ -41,7 +46,6 @@ test('extracts HTTP request headers as span attributes', async ({ baseURL }) => const serverTransaction = await serverTransactionPromise; - // FIXME: This test fails with Turbopack enabled expect(serverTransaction.contexts?.trace?.data).toEqual( expect.objectContaining({ 'http.request.header.user_agent': 'Custom-NextJS-Agent/15.0', From 51c1098478400151da6ee53a1b58cc3a62fb9f97 Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Tue, 9 Sep 2025 11:05:53 +0200 Subject: [PATCH 22/24] exclude turbopack test 2 --- .../e2e-tests/test-applications/nextjs-15/next-env.d.ts | 2 +- .../e2e-tests/test-applications/nextjs-15/package.json | 2 +- .../test-applications/nextjs-15/playwright.config.mjs | 2 +- .../nextjs-15/tests/pageload-tracing.test.ts | 8 ++++---- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/next-env.d.ts b/dev-packages/e2e-tests/test-applications/nextjs-15/next-env.d.ts index 1b3be0840f3f..4f11a03dc6cc 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15/next-env.d.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/next-env.d.ts @@ -2,4 +2,4 @@ /// // NOTE: This file should not be edited -// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/package.json b/dev-packages/e2e-tests/test-applications/nextjs-15/package.json index 85ead75fc525..7f9b3e822628 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/package.json @@ -6,7 +6,7 @@ "build": "next build > .tmp_build_stdout 2> .tmp_build_stderr || (cat .tmp_build_stdout && cat .tmp_build_stderr && exit 1)", "clean": "npx rimraf node_modules pnpm-lock.yaml .tmp_dev_server_logs", "test:prod": "TEST_ENV=production playwright test", - "test:prod-turbo": "TEST_ENV=production-turbo playwright test", + "test:prod-turbo": "TEST_ENV=prod-turbopack playwright test", "test:dev": "TEST_ENV=development playwright test", "test:dev-turbo": "TEST_ENV=dev-turbopack playwright test", "test:build": "pnpm install && pnpm build", diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/nextjs-15/playwright.config.mjs index beab3285530b..2eaa19f24532 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15/playwright.config.mjs +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/playwright.config.mjs @@ -14,7 +14,7 @@ const getStartCommand = () => { return 'pnpm next dev -p 3030 2>&1 | tee .tmp_dev_server_logs'; } - if (testEnv === 'production' || testEnv === 'production-turbo') { + if (testEnv === 'production' || testEnv === 'prod-turbopack') { return 'pnpm next start -p 3030'; } diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/tests/pageload-tracing.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/pageload-tracing.test.ts index 1f96759cb80b..3cad4a546508 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15/tests/pageload-tracing.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/pageload-tracing.test.ts @@ -24,10 +24,10 @@ test('App router transactions should be attached to the pageload request span', }); test('extracts HTTP request headers as span attributes', async ({ baseURL }) => { - // FIXME: This test fails with Turbopack enabled - if (process.env.TEST_ENV === 'production-turbo') { - return; - } + test.skip( + process.env.TEST_ENV === 'prod-turbopack' || process.env.TEST_ENV === 'dev-turbopack', + 'Incoming fetch request headers are not added as span attributes when Turbopack is enabled (addHeadersAsAttributes)', + ); const serverTransactionPromise = waitForTransaction('nextjs-15', async transactionEvent => { return transactionEvent?.transaction === 'GET /pageload-tracing'; From 5584061dd2b7c5f93ae284c6294833d1a0a89f6a Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Tue, 9 Sep 2025 11:08:04 +0200 Subject: [PATCH 23/24] change function name --- .../pages-router-instrumentation/wrapApiHandlerWithSentry.ts | 2 +- .../utils/{headersToAttributes.ts => addHeadersAsAttributes.ts} | 0 packages/nextjs/src/common/wrapGenerationFunctionWithSentry.ts | 2 +- packages/nextjs/src/common/wrapMiddlewareWithSentry.ts | 2 +- packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts | 2 +- packages/nextjs/src/common/wrapServerComponentWithSentry.ts | 2 +- packages/nextjs/src/edge/wrapApiHandlerWithSentry.ts | 2 +- 7 files changed, 6 insertions(+), 6 deletions(-) rename packages/nextjs/src/common/utils/{headersToAttributes.ts => addHeadersAsAttributes.ts} (100%) diff --git a/packages/nextjs/src/common/pages-router-instrumentation/wrapApiHandlerWithSentry.ts b/packages/nextjs/src/common/pages-router-instrumentation/wrapApiHandlerWithSentry.ts index 9c82bf88cb88..a0e4112404cd 100644 --- a/packages/nextjs/src/common/pages-router-instrumentation/wrapApiHandlerWithSentry.ts +++ b/packages/nextjs/src/common/pages-router-instrumentation/wrapApiHandlerWithSentry.ts @@ -15,7 +15,7 @@ import { } from '@sentry/core'; import type { NextApiRequest } from 'next'; import type { AugmentedNextApiResponse, NextApiHandler } from '../types'; -import { addHeadersAsAttributes } from '../utils/headersToAttributes'; +import { addHeadersAsAttributes } from '../utils/addHeadersAsAttributes'; import { flushSafelyWithTimeout } from '../utils/responseEnd'; import { dropNextjsRootContext, escapeNextjsTracing } from '../utils/tracingUtils'; diff --git a/packages/nextjs/src/common/utils/headersToAttributes.ts b/packages/nextjs/src/common/utils/addHeadersAsAttributes.ts similarity index 100% rename from packages/nextjs/src/common/utils/headersToAttributes.ts rename to packages/nextjs/src/common/utils/addHeadersAsAttributes.ts diff --git a/packages/nextjs/src/common/wrapGenerationFunctionWithSentry.ts b/packages/nextjs/src/common/wrapGenerationFunctionWithSentry.ts index 201e20067694..304066cc2313 100644 --- a/packages/nextjs/src/common/wrapGenerationFunctionWithSentry.ts +++ b/packages/nextjs/src/common/wrapGenerationFunctionWithSentry.ts @@ -22,7 +22,7 @@ import { import type { GenerationFunctionContext } from '../common/types'; import { isNotFoundNavigationError, isRedirectNavigationError } from './nextNavigationErrorUtils'; import { TRANSACTION_ATTR_SENTRY_TRACE_BACKFILL } from './span-attributes-with-logic-attached'; -import { addHeadersAsAttributes } from './utils/headersToAttributes'; +import { addHeadersAsAttributes } from './utils/addHeadersAsAttributes'; import { commonObjectToIsolationScope, commonObjectToPropagationContext } from './utils/tracingUtils'; import { getSanitizedRequestUrl } from './utils/urls'; import { maybeExtractSynchronousParamsAndSearchParams } from './utils/wrapperUtils'; diff --git a/packages/nextjs/src/common/wrapMiddlewareWithSentry.ts b/packages/nextjs/src/common/wrapMiddlewareWithSentry.ts index f82b1a31f7a1..bd84fb4195b7 100644 --- a/packages/nextjs/src/common/wrapMiddlewareWithSentry.ts +++ b/packages/nextjs/src/common/wrapMiddlewareWithSentry.ts @@ -13,7 +13,7 @@ import { winterCGRequestToRequestData, withIsolationScope, } from '@sentry/core'; -import { addHeadersAsAttributes } from '../common/utils/headersToAttributes'; +import { addHeadersAsAttributes } from '../common/utils/addHeadersAsAttributes'; import { flushSafelyWithTimeout } from '../common/utils/responseEnd'; import type { EdgeRouteHandler } from '../edge/types'; diff --git a/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts b/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts index 1d258a8ae6f6..e10d51321a0e 100644 --- a/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts +++ b/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts @@ -19,7 +19,7 @@ import { } from '@sentry/core'; import { isNotFoundNavigationError, isRedirectNavigationError } from './nextNavigationErrorUtils'; import type { RouteHandlerContext } from './types'; -import { addHeadersAsAttributes } from './utils/headersToAttributes'; +import { addHeadersAsAttributes } from './utils/addHeadersAsAttributes'; import { flushSafelyWithTimeout } from './utils/responseEnd'; import { commonObjectToIsolationScope } from './utils/tracingUtils'; diff --git a/packages/nextjs/src/common/wrapServerComponentWithSentry.ts b/packages/nextjs/src/common/wrapServerComponentWithSentry.ts index 0bddaeadff9c..f0f8e9df8717 100644 --- a/packages/nextjs/src/common/wrapServerComponentWithSentry.ts +++ b/packages/nextjs/src/common/wrapServerComponentWithSentry.ts @@ -24,7 +24,7 @@ import { isNotFoundNavigationError, isRedirectNavigationError } from '../common/ import type { ServerComponentContext } from '../common/types'; import { flushSafelyWithTimeout } from '../common/utils/responseEnd'; import { TRANSACTION_ATTR_SENTRY_TRACE_BACKFILL } from './span-attributes-with-logic-attached'; -import { addHeadersAsAttributes } from './utils/headersToAttributes'; +import { addHeadersAsAttributes } from './utils/addHeadersAsAttributes'; import { commonObjectToIsolationScope, commonObjectToPropagationContext } from './utils/tracingUtils'; import { getSanitizedRequestUrl } from './utils/urls'; import { maybeExtractSynchronousParamsAndSearchParams } from './utils/wrapperUtils'; diff --git a/packages/nextjs/src/edge/wrapApiHandlerWithSentry.ts b/packages/nextjs/src/edge/wrapApiHandlerWithSentry.ts index cd5649d0b1b4..6002981cfcf4 100644 --- a/packages/nextjs/src/edge/wrapApiHandlerWithSentry.ts +++ b/packages/nextjs/src/edge/wrapApiHandlerWithSentry.ts @@ -13,7 +13,7 @@ import { winterCGRequestToRequestData, withIsolationScope, } from '@sentry/core'; -import { addHeadersAsAttributes } from '../common/utils/headersToAttributes'; +import { addHeadersAsAttributes } from '../common/utils/addHeadersAsAttributes'; import { flushSafelyWithTimeout } from '../common/utils/responseEnd'; import type { EdgeRouteHandler } from './types'; From 15ea0a60a5aaaccae26324df266bb62332c2e266 Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Tue, 9 Sep 2025 11:40:25 +0200 Subject: [PATCH 24/24] increase size limit --- .size-limit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.size-limit.js b/.size-limit.js index 3819ee2e91f6..36bf0607e840 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -233,7 +233,7 @@ module.exports = [ import: createImport('init'), ignore: [...builtinModules, ...nodePrefixedBuiltinModules], gzip: true, - limit: '152 KB', + limit: '154 KB', }, { name: '@sentry/node - without tracing',