diff --git a/MIGRATION.md b/MIGRATION.md index 9140be4fe8ee..83b7be338dbd 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -798,6 +798,8 @@ On server-side HTTP spans, the `content-length` header is now always reported as The `http.request.header.`/`http.response.header.` attributes now write the header name lowercased as previously but no longer replaces dashes (`-`) with underscores (`_`). For example, the SDK now sets `http.request.header.user-agent` rather than `http.request.header.user_agent`. The same applies to the cookie names in `http.request.header.cookie.` and `http.request.header.set-cookie.`. +Furthermore, the values of `http.request.header.`/`http.response.header.` are now string arrays instead of single strings, as mandated by the semantic conventions. Headers that were sent multiple times previously had their values joined into one string with a semicolon (`;`); they now have one array entry per value. For example, the SDK now sets `http.request.header.accept-encoding` to `['gzip', 'deflate']` rather than `'gzip;deflate'`, and `http.request.header.user-agent` to `['Mozilla/5.0 ...']` rather than `'Mozilla/5.0 ...'`. The cookie attributes (`http.request.header.cookie.`/`http.request.header.set-cookie.`) continue to hold a single string, since a cookie only ever has one value. + #### Network attributes Network-related span attributes now use the current Sentry semantic conventions, aligned across SDKs. If you query, transform, or alert on the legacy `net.*` fields, update those references: diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpContext-streamed/test.ts b/dev-packages/browser-integration-tests/suites/integrations/httpContext-streamed/test.ts index 332ceda450e8..d37127a7897a 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpContext-streamed/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/httpContext-streamed/test.ts @@ -22,8 +22,8 @@ sentryTest('httpContextIntegration captures url, user-agent, and referer', async value: expect.any(String), }); expect(pageloadSpan!.attributes['http.request.header.referer']).toEqual({ - type: 'string', - value: 'https://sentry.io/', + type: 'array', + value: ['https://sentry.io/'], }); }); diff --git a/dev-packages/cloudflare-integration-tests/suites/public-api/startSpan-streamed/test.ts b/dev-packages/cloudflare-integration-tests/suites/public-api/startSpan-streamed/test.ts index de670f15cea1..73af51173484 100644 --- a/dev-packages/cloudflare-integration-tests/suites/public-api/startSpan-streamed/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/public-api/startSpan-streamed/test.ts @@ -226,36 +226,36 @@ it('sends a streamed span envelope with correct spans for a manually started spa value: 'node', }, 'http.request.header.accept': { - type: 'string', - value: '*/*', + type: 'array', + value: ['*/*'], }, 'http.request.header.accept-encoding': { - type: 'string', - value: 'br, gzip', + type: 'array', + value: ['br, gzip'], }, 'http.request.header.accept-language': { - type: 'string', - value: '*', + type: 'array', + value: ['*'], }, 'http.request.header.cf-connecting-ip': { - type: 'string', - value: '127.0.0.1', + type: 'array', + value: ['127.0.0.1'], }, 'user.ip_address': { type: 'string', value: '127.0.0.1', }, 'http.request.header.host': { - type: 'string', - value: expect.stringMatching(/^localhost:.+$/), + type: 'array', + value: [expect.stringMatching(/^localhost:.+$/)], }, 'http.request.header.sec-fetch-mode': { - type: 'string', - value: 'cors', + type: 'array', + value: ['cors'], }, 'http.request.header.user-agent': { - type: 'string', - value: 'node', + type: 'array', + value: ['node'], }, 'http.request.method': { type: 'string', 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 d9ffda6a1c68..88d123e2c8f6 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 @@ -61,11 +61,11 @@ test.describe('tracing in dynamically rendered (ssr) routes', () => { 'sentry.sdk.name': { value: 'sentry.javascript.astro', type: 'string' }, 'url.full': { value: expect.stringContaining('/test-ssr'), type: 'string' }, // demonstrates that the request data integration can extract headers - 'http.request.header.accept': { value: expect.any(String), type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate, br, zstd', type: 'string' }, - 'http.request.header.accept-language': { value: 'en-US', type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'navigate', type: 'string' }, - 'http.request.header.user-agent': { value: expect.any(String), type: 'string' }, + 'http.request.header.accept': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate, br, zstd'], type: 'array' }, + 'http.request.header.accept-language': { value: ['en-US'], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['navigate'], type: 'array' }, + 'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' }, }); }); }); @@ -152,11 +152,11 @@ test.describe('nested SSR routes (client, server, server request)', () => { 'sentry.origin': { value: 'auto.http.astro', type: 'string' }, 'sentry.segment.name.source': { value: 'route', type: 'string' }, 'url.full': { value: expect.stringContaining('/user-page/myUsername123'), type: 'string' }, - 'http.request.header.accept': { value: expect.any(String), type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate, br, zstd', type: 'string' }, - 'http.request.header.accept-language': { value: 'en-US', type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'navigate', type: 'string' }, - 'http.request.header.user-agent': { value: expect.any(String), type: 'string' }, + 'http.request.header.accept': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate, br, zstd'], type: 'array' }, + 'http.request.header.accept-language': { value: ['en-US'], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['navigate'], type: 'array' }, + 'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' }, }); // HTTP client span - with span streaming only the domain is kept in the name, the URL lives in @@ -174,11 +174,11 @@ test.describe('nested SSR routes (client, server, server request)', () => { 'sentry.op': { value: 'http.server', type: 'string' }, 'sentry.origin': { value: 'auto.http.astro', type: 'string' }, 'sentry.segment.name.source': { value: 'route', type: 'string' }, - 'http.request.header.accept': { value: expect.any(String), type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate', type: 'string' }, - 'http.request.header.accept-language': { value: '*', type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'cors', type: 'string' }, - 'http.request.header.user-agent': { value: expect.any(String), type: 'string' }, + 'http.request.header.accept': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate'], type: 'array' }, + 'http.request.header.accept-language': { value: ['*'], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['cors'], type: 'array' }, + 'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' }, }); }); @@ -215,11 +215,11 @@ test.describe('nested SSR routes (client, server, server request)', () => { 'sentry.origin': { value: 'auto.http.astro', type: 'string' }, 'sentry.segment.name.source': { value: 'route', type: 'string' }, 'url.full': { value: expect.stringContaining('/catchAll/hell0/whatever-do'), type: 'string' }, - 'http.request.header.accept': { value: expect.any(String), type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate, br, zstd', type: 'string' }, - 'http.request.header.accept-language': { value: 'en-US', type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'navigate', type: 'string' }, - 'http.request.header.user-agent': { value: expect.any(String), type: 'string' }, + 'http.request.header.accept': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate, br, zstd'], type: 'array' }, + 'http.request.header.accept-language': { value: ['en-US'], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['navigate'], type: 'array' }, + 'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' }, }); }); }); @@ -253,11 +253,11 @@ test.describe('parametrized vs static paths', () => { 'sentry.origin': { value: 'auto.http.astro', type: 'string' }, 'sentry.segment.name.source': { value: 'route', type: 'string' }, 'url.full': { value: expect.stringContaining('/user-page/settings'), type: 'string' }, - 'http.request.header.accept': { value: expect.any(String), type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate, br, zstd', type: 'string' }, - 'http.request.header.accept-language': { value: 'en-US', type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'navigate', type: 'string' }, - 'http.request.header.user-agent': { value: expect.any(String), type: 'string' }, + 'http.request.header.accept': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate, br, zstd'], type: 'array' }, + 'http.request.header.accept-language': { value: ['en-US'], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['navigate'], type: 'array' }, + 'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' }, }); }); }); 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 ad56501fc6f3..dfc5c3b5de16 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 @@ -61,11 +61,11 @@ test.describe('tracing in dynamically rendered (ssr) routes', () => { 'sentry.sdk.name': { value: 'sentry.javascript.astro', type: 'string' }, 'url.full': { value: expect.stringContaining('/test-ssr'), type: 'string' }, // demonstrates that the request data integration can extract headers - 'http.request.header.accept': { value: expect.any(String), type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate, br, zstd', type: 'string' }, - 'http.request.header.accept-language': { value: 'en-US', type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'navigate', type: 'string' }, - 'http.request.header.user-agent': { value: expect.any(String), type: 'string' }, + 'http.request.header.accept': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate, br, zstd'], type: 'array' }, + 'http.request.header.accept-language': { value: ['en-US'], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['navigate'], type: 'array' }, + 'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' }, }); }); }); @@ -152,11 +152,11 @@ test.describe('nested SSR routes (client, server, server request)', () => { 'sentry.origin': { value: 'auto.http.astro', type: 'string' }, 'sentry.segment.name.source': { value: 'route', type: 'string' }, 'url.full': { value: expect.stringContaining('/user-page/myUsername123'), type: 'string' }, - 'http.request.header.accept': { value: expect.any(String), type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate, br, zstd', type: 'string' }, - 'http.request.header.accept-language': { value: 'en-US', type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'navigate', type: 'string' }, - 'http.request.header.user-agent': { value: expect.any(String), type: 'string' }, + 'http.request.header.accept': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate, br, zstd'], type: 'array' }, + 'http.request.header.accept-language': { value: ['en-US'], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['navigate'], type: 'array' }, + 'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' }, }); // HTTP client span - with span streaming only the domain is kept in the name, the URL lives in @@ -174,11 +174,11 @@ test.describe('nested SSR routes (client, server, server request)', () => { 'sentry.op': { value: 'http.server', type: 'string' }, 'sentry.origin': { value: 'auto.http.astro', type: 'string' }, 'sentry.segment.name.source': { value: 'route', type: 'string' }, - 'http.request.header.accept': { value: expect.any(String), type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate', type: 'string' }, - 'http.request.header.accept-language': { value: '*', type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'cors', type: 'string' }, - 'http.request.header.user-agent': { value: expect.any(String), type: 'string' }, + 'http.request.header.accept': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate'], type: 'array' }, + 'http.request.header.accept-language': { value: ['*'], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['cors'], type: 'array' }, + 'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' }, }); }); @@ -215,11 +215,11 @@ test.describe('nested SSR routes (client, server, server request)', () => { 'sentry.origin': { value: 'auto.http.astro', type: 'string' }, 'sentry.segment.name.source': { value: 'route', type: 'string' }, 'url.full': { value: expect.stringContaining('/catchAll/hell0/whatever-do'), type: 'string' }, - 'http.request.header.accept': { value: expect.any(String), type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate, br, zstd', type: 'string' }, - 'http.request.header.accept-language': { value: 'en-US', type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'navigate', type: 'string' }, - 'http.request.header.user-agent': { value: expect.any(String), type: 'string' }, + 'http.request.header.accept': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate, br, zstd'], type: 'array' }, + 'http.request.header.accept-language': { value: ['en-US'], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['navigate'], type: 'array' }, + 'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' }, }); }); }); @@ -253,11 +253,11 @@ test.describe('parametrized vs static paths', () => { 'sentry.origin': { value: 'auto.http.astro', type: 'string' }, 'sentry.segment.name.source': { value: 'route', type: 'string' }, 'url.full': { value: expect.stringContaining('/user-page/settings'), type: 'string' }, - 'http.request.header.accept': { value: expect.any(String), type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate, br, zstd', type: 'string' }, - 'http.request.header.accept-language': { value: 'en-US', type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'navigate', type: 'string' }, - 'http.request.header.user-agent': { value: expect.any(String), type: 'string' }, + 'http.request.header.accept': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate, br, zstd'], type: 'array' }, + 'http.request.header.accept-language': { value: ['en-US'], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['navigate'], type: 'array' }, + 'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' }, }); }); 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 9cc43ad5b6d3..db1539b43870 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 @@ -55,11 +55,11 @@ test.describe('tracing in static routes with server islands', () => { 'sentry.op': { value: 'http.server', type: 'string' }, 'sentry.origin': { value: 'auto.http.astro', type: 'string' }, 'sentry.segment.name.source': { value: 'route', type: 'string' }, - 'http.request.header.accept': { value: expect.any(String), type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate, br, zstd', type: 'string' }, - 'http.request.header.accept-language': { value: 'en-US', type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'cors', type: 'string' }, - 'http.request.header.user-agent': { value: expect.any(String), type: 'string' }, + 'http.request.header.accept': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate, br, zstd'], type: 'array' }, + 'http.request.header.accept-language': { value: ['en-US'], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['cors'], type: 'array' }, + 'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' }, }); // unfortunately, the server island trace id is not the same as the client pageload trace id diff --git a/dev-packages/e2e-tests/test-applications/astro-6-cf-workers/tests/tracing.dynamic.test.ts b/dev-packages/e2e-tests/test-applications/astro-6-cf-workers/tests/tracing.dynamic.test.ts index fe78ed789bf1..51a22ac11f06 100644 --- a/dev-packages/e2e-tests/test-applications/astro-6-cf-workers/tests/tracing.dynamic.test.ts +++ b/dev-packages/e2e-tests/test-applications/astro-6-cf-workers/tests/tracing.dynamic.test.ts @@ -60,11 +60,11 @@ test.describe('tracing in dynamically rendered (ssr) routes', () => { 'sentry.environment': { value: 'qa', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.cloudflare', type: 'string' }, // demonstrates that the request data integration can extract headers - 'http.request.header.accept': { value: expect.any(String), type: 'string' }, - 'http.request.header.accept-encoding': { value: 'br, gzip', type: 'string' }, - 'http.request.header.accept-language': { value: 'en-US', type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'navigate', type: 'string' }, - 'http.request.header.user-agent': { value: expect.any(String), type: 'string' }, + 'http.request.header.accept': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['br, gzip'], type: 'array' }, + 'http.request.header.accept-language': { value: ['en-US'], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['navigate'], type: 'array' }, + 'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' }, }); }); }); @@ -151,11 +151,11 @@ test.describe('nested SSR routes (client, server, server request)', () => { 'sentry.origin': { value: 'auto.http.astro', type: 'string' }, 'sentry.segment.name.source': { value: 'route', type: 'string' }, 'url.full': { value: expect.stringContaining('/user-page/myUsername123'), type: 'string' }, - 'http.request.header.accept': { value: expect.any(String), type: 'string' }, - 'http.request.header.accept-encoding': { value: 'br, gzip', type: 'string' }, - 'http.request.header.accept-language': { value: 'en-US', type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'navigate', type: 'string' }, - 'http.request.header.user-agent': { value: expect.any(String), type: 'string' }, + 'http.request.header.accept': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['br, gzip'], type: 'array' }, + 'http.request.header.accept-language': { value: ['en-US'], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['navigate'], type: 'array' }, + 'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' }, }); // HTTP client span - with span streaming only the domain is kept in the name, the URL lives in @@ -172,7 +172,7 @@ test.describe('nested SSR routes (client, server, server request)', () => { 'sentry.op': { value: 'http.server', type: 'string' }, 'sentry.origin': { value: 'auto.http.astro', type: 'string' }, 'sentry.segment.name.source': { value: 'route', type: 'string' }, - 'http.request.header.accept-encoding': { value: 'br, gzip', type: 'string' }, + 'http.request.header.accept-encoding': { value: ['br, gzip'], type: 'array' }, }); }); @@ -209,11 +209,11 @@ test.describe('nested SSR routes (client, server, server request)', () => { 'sentry.origin': { value: 'auto.http.astro', type: 'string' }, 'sentry.segment.name.source': { value: 'route', type: 'string' }, 'url.full': { value: expect.stringContaining('/catchAll/hell0/whatever-do'), type: 'string' }, - 'http.request.header.accept': { value: expect.any(String), type: 'string' }, - 'http.request.header.accept-encoding': { value: 'br, gzip', type: 'string' }, - 'http.request.header.accept-language': { value: 'en-US', type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'navigate', type: 'string' }, - 'http.request.header.user-agent': { value: expect.any(String), type: 'string' }, + 'http.request.header.accept': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['br, gzip'], type: 'array' }, + 'http.request.header.accept-language': { value: ['en-US'], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['navigate'], type: 'array' }, + 'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' }, }); }); }); @@ -247,11 +247,11 @@ test.describe('parametrized vs static paths', () => { 'sentry.origin': { value: 'auto.http.astro', type: 'string' }, 'sentry.segment.name.source': { value: 'route', type: 'string' }, 'url.full': { value: expect.stringContaining('/user-page/settings'), type: 'string' }, - 'http.request.header.accept': { value: expect.any(String), type: 'string' }, - 'http.request.header.accept-encoding': { value: 'br, gzip', type: 'string' }, - 'http.request.header.accept-language': { value: 'en-US', type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'navigate', type: 'string' }, - 'http.request.header.user-agent': { value: expect.any(String), type: 'string' }, + 'http.request.header.accept': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['br, gzip'], type: 'array' }, + 'http.request.header.accept-language': { value: ['en-US'], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['navigate'], type: 'array' }, + 'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' }, }); }); diff --git a/dev-packages/e2e-tests/test-applications/astro-6/tests/tracing.dynamic.test.ts b/dev-packages/e2e-tests/test-applications/astro-6/tests/tracing.dynamic.test.ts index 37bc5aa93360..8b272e8c1929 100644 --- a/dev-packages/e2e-tests/test-applications/astro-6/tests/tracing.dynamic.test.ts +++ b/dev-packages/e2e-tests/test-applications/astro-6/tests/tracing.dynamic.test.ts @@ -61,11 +61,11 @@ test.describe('tracing in dynamically rendered (ssr) routes', () => { 'sentry.sdk.name': { value: 'sentry.javascript.astro', type: 'string' }, 'url.full': { value: expect.stringContaining('/test-ssr'), type: 'string' }, // demonstrates that the request data integration can extract headers - 'http.request.header.accept': { value: expect.any(String), type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate, br, zstd', type: 'string' }, - 'http.request.header.accept-language': { value: 'en-US', type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'navigate', type: 'string' }, - 'http.request.header.user-agent': { value: expect.any(String), type: 'string' }, + 'http.request.header.accept': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate, br, zstd'], type: 'array' }, + 'http.request.header.accept-language': { value: ['en-US'], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['navigate'], type: 'array' }, + 'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' }, }); }); }); @@ -152,11 +152,11 @@ test.describe('nested SSR routes (client, server, server request)', () => { 'sentry.origin': { value: 'auto.http.astro', type: 'string' }, 'sentry.segment.name.source': { value: 'route', type: 'string' }, 'url.full': { value: expect.stringContaining('/user-page/myUsername123'), type: 'string' }, - 'http.request.header.accept': { value: expect.any(String), type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate, br, zstd', type: 'string' }, - 'http.request.header.accept-language': { value: 'en-US', type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'navigate', type: 'string' }, - 'http.request.header.user-agent': { value: expect.any(String), type: 'string' }, + 'http.request.header.accept': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate, br, zstd'], type: 'array' }, + 'http.request.header.accept-language': { value: ['en-US'], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['navigate'], type: 'array' }, + 'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' }, }); // HTTP client span - with span streaming only the domain is kept in the name, the URL lives in @@ -175,11 +175,11 @@ test.describe('nested SSR routes (client, server, server request)', () => { 'sentry.origin': { value: 'auto.http.astro', type: 'string' }, 'sentry.segment.name.source': { value: 'route', type: 'string' }, 'url.full': { value: expect.stringContaining('/api/user/myUsername123.json'), type: 'string' }, - 'http.request.header.accept': { value: expect.any(String), type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate', type: 'string' }, - 'http.request.header.accept-language': { value: '*', type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'cors', type: 'string' }, - 'http.request.header.user-agent': { value: expect.any(String), type: 'string' }, + 'http.request.header.accept': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate'], type: 'array' }, + 'http.request.header.accept-language': { value: ['*'], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['cors'], type: 'array' }, + 'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' }, }); }); @@ -216,11 +216,11 @@ test.describe('nested SSR routes (client, server, server request)', () => { 'sentry.origin': { value: 'auto.http.astro', type: 'string' }, 'sentry.segment.name.source': { value: 'route', type: 'string' }, 'url.full': { value: expect.stringContaining('/catchAll/hell0/whatever-do'), type: 'string' }, - 'http.request.header.accept': { value: expect.any(String), type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate, br, zstd', type: 'string' }, - 'http.request.header.accept-language': { value: 'en-US', type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'navigate', type: 'string' }, - 'http.request.header.user-agent': { value: expect.any(String), type: 'string' }, + 'http.request.header.accept': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate, br, zstd'], type: 'array' }, + 'http.request.header.accept-language': { value: ['en-US'], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['navigate'], type: 'array' }, + 'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' }, }); }); }); @@ -254,11 +254,11 @@ test.describe('parametrized vs static paths', () => { 'sentry.origin': { value: 'auto.http.astro', type: 'string' }, 'sentry.segment.name.source': { value: 'route', type: 'string' }, 'url.full': { value: expect.stringContaining('/user-page/settings'), type: 'string' }, - 'http.request.header.accept': { value: expect.any(String), type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate, br, zstd', type: 'string' }, - 'http.request.header.accept-language': { value: 'en-US', type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'navigate', type: 'string' }, - 'http.request.header.user-agent': { value: expect.any(String), type: 'string' }, + 'http.request.header.accept': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate, br, zstd'], type: 'array' }, + 'http.request.header.accept-language': { value: ['en-US'], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['navigate'], type: 'array' }, + 'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' }, }); }); diff --git a/dev-packages/e2e-tests/test-applications/astro-6/tests/tracing.serverIslands.test.ts b/dev-packages/e2e-tests/test-applications/astro-6/tests/tracing.serverIslands.test.ts index 4947ba4f65e0..48528dda3721 100644 --- a/dev-packages/e2e-tests/test-applications/astro-6/tests/tracing.serverIslands.test.ts +++ b/dev-packages/e2e-tests/test-applications/astro-6/tests/tracing.serverIslands.test.ts @@ -55,11 +55,11 @@ test.describe('tracing in static routes with server islands', () => { 'sentry.op': { value: 'http.server', type: 'string' }, 'sentry.origin': { value: 'auto.http.astro', type: 'string' }, 'sentry.segment.name.source': { value: 'route', type: 'string' }, - 'http.request.header.accept': { value: expect.any(String), type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate, br, zstd', type: 'string' }, - 'http.request.header.accept-language': { value: 'en-US', type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'cors', type: 'string' }, - 'http.request.header.user-agent': { value: expect.any(String), type: 'string' }, + 'http.request.header.accept': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate, br, zstd'], type: 'array' }, + 'http.request.header.accept-language': { value: ['en-US'], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['cors'], type: 'array' }, + 'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' }, }); // unfortunately, the server island trace id is not the same as the client pageload trace id diff --git a/dev-packages/e2e-tests/test-applications/astro-7-static/tests/tracing.dynamic.test.ts b/dev-packages/e2e-tests/test-applications/astro-7-static/tests/tracing.dynamic.test.ts index 103c4d2f083d..c33f97788382 100644 --- a/dev-packages/e2e-tests/test-applications/astro-7-static/tests/tracing.dynamic.test.ts +++ b/dev-packages/e2e-tests/test-applications/astro-7-static/tests/tracing.dynamic.test.ts @@ -78,11 +78,11 @@ test.describe('tracing in dynamically rendered (ssr) routes', () => { 'sentry.sample_rate': 1, 'sentry.segment.name.source': 'route', 'url.full': 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), + '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', @@ -230,11 +230,11 @@ test.describe('nested SSR routes (client, server, server request)', () => { 'sentry.origin': 'auto.http.astro', 'sentry.segment.name.source': 'route', 'url.full': 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), + '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)], }, }, }, @@ -268,11 +268,11 @@ test.describe('nested SSR routes (client, server, server request)', () => { 'sentry.origin': 'auto.http.astro', 'sentry.segment.name.source': 'route', 'url.full': 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), + '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)], }, }, }, @@ -325,11 +325,11 @@ test.describe('nested SSR routes (client, server, server request)', () => { 'sentry.origin': 'auto.http.astro', 'sentry.segment.name.source': 'route', 'url.full': 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), + '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)], }, }, }, @@ -382,11 +382,11 @@ test.describe('parametrized vs static paths', () => { 'sentry.origin': 'auto.http.astro', 'sentry.segment.name.source': 'route', 'url.full': 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), + '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-7-static/tests/tracing.serverIslands.test.ts b/dev-packages/e2e-tests/test-applications/astro-7-static/tests/tracing.serverIslands.test.ts index e713662b1843..6e7a97dc5154 100644 --- a/dev-packages/e2e-tests/test-applications/astro-7-static/tests/tracing.serverIslands.test.ts +++ b/dev-packages/e2e-tests/test-applications/astro-7-static/tests/tracing.serverIslands.test.ts @@ -70,11 +70,11 @@ test.describe('tracing in static routes with server islands', () => { 'sentry.op': 'http.server', 'sentry.origin': 'auto.http.astro', 'sentry.segment.name.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), + '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/astro-7/tests/tracing.dynamic.test.ts b/dev-packages/e2e-tests/test-applications/astro-7/tests/tracing.dynamic.test.ts index 7b5325dc3c89..4733d5e64ff0 100644 --- a/dev-packages/e2e-tests/test-applications/astro-7/tests/tracing.dynamic.test.ts +++ b/dev-packages/e2e-tests/test-applications/astro-7/tests/tracing.dynamic.test.ts @@ -58,11 +58,11 @@ test.describe('tracing in dynamically rendered (ssr) routes', () => { 'sentry.sdk.name': { value: 'sentry.javascript.astro', type: 'string' }, 'url.full': { value: expect.stringContaining('/test-ssr'), type: 'string' }, // demonstrates that the request data integration can extract headers - 'http.request.header.accept': { value: expect.any(String), type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate, br, zstd', type: 'string' }, - 'http.request.header.accept-language': { value: 'en-US', type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'navigate', type: 'string' }, - 'http.request.header.user-agent': { value: expect.any(String), type: 'string' }, + 'http.request.header.accept': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate, br, zstd'], type: 'array' }, + 'http.request.header.accept-language': { value: ['en-US'], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['navigate'], type: 'array' }, + 'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' }, }); }); }); @@ -143,11 +143,11 @@ test.describe('nested SSR routes (client, server, server request)', () => { 'sentry.origin': { value: 'auto.http.astro', type: 'string' }, 'sentry.segment.name.source': { value: 'route', type: 'string' }, 'url.full': { value: expect.stringContaining('/user-page/myUsername123'), type: 'string' }, - 'http.request.header.accept': { value: expect.any(String), type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate, br, zstd', type: 'string' }, - 'http.request.header.accept-language': { value: 'en-US', type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'navigate', type: 'string' }, - 'http.request.header.user-agent': { value: expect.any(String), type: 'string' }, + 'http.request.header.accept': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate, br, zstd'], type: 'array' }, + 'http.request.header.accept-language': { value: ['en-US'], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['navigate'], type: 'array' }, + 'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' }, }); // HTTP client span - with span streaming only the domain is kept in the name, the URL lives in @@ -166,11 +166,11 @@ test.describe('nested SSR routes (client, server, server request)', () => { 'sentry.origin': { value: 'auto.http.astro', type: 'string' }, 'sentry.segment.name.source': { value: 'route', type: 'string' }, 'url.full': { value: expect.stringContaining('/api/user/myUsername123.json'), type: 'string' }, - 'http.request.header.accept': { value: expect.any(String), type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate', type: 'string' }, - 'http.request.header.accept-language': { value: '*', type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'cors', type: 'string' }, - 'http.request.header.user-agent': { value: expect.any(String), type: 'string' }, + 'http.request.header.accept': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate'], type: 'array' }, + 'http.request.header.accept-language': { value: ['*'], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['cors'], type: 'array' }, + 'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' }, }); }); @@ -201,11 +201,11 @@ test.describe('nested SSR routes (client, server, server request)', () => { 'sentry.origin': { value: 'auto.http.astro', type: 'string' }, 'sentry.segment.name.source': { value: 'route', type: 'string' }, 'url.full': { value: expect.stringContaining('/catchAll/hell0/whatever-do'), type: 'string' }, - 'http.request.header.accept': { value: expect.any(String), type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate, br, zstd', type: 'string' }, - 'http.request.header.accept-language': { value: 'en-US', type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'navigate', type: 'string' }, - 'http.request.header.user-agent': { value: expect.any(String), type: 'string' }, + 'http.request.header.accept': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate, br, zstd'], type: 'array' }, + 'http.request.header.accept-language': { value: ['en-US'], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['navigate'], type: 'array' }, + 'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' }, }); }); }); @@ -236,11 +236,11 @@ test.describe('parametrized vs static paths', () => { 'sentry.origin': { value: 'auto.http.astro', type: 'string' }, 'sentry.segment.name.source': { value: 'route', type: 'string' }, 'url.full': { value: expect.stringContaining('/user-page/settings'), type: 'string' }, - 'http.request.header.accept': { value: expect.any(String), type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate, br, zstd', type: 'string' }, - 'http.request.header.accept-language': { value: 'en-US', type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'navigate', type: 'string' }, - 'http.request.header.user-agent': { value: expect.any(String), type: 'string' }, + 'http.request.header.accept': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate, br, zstd'], type: 'array' }, + 'http.request.header.accept-language': { value: ['en-US'], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['navigate'], type: 'array' }, + 'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' }, }); }); diff --git a/dev-packages/e2e-tests/test-applications/astro-7/tests/tracing.serverIslands.test.ts b/dev-packages/e2e-tests/test-applications/astro-7/tests/tracing.serverIslands.test.ts index bd9e0813277e..0e72346c9b6b 100644 --- a/dev-packages/e2e-tests/test-applications/astro-7/tests/tracing.serverIslands.test.ts +++ b/dev-packages/e2e-tests/test-applications/astro-7/tests/tracing.serverIslands.test.ts @@ -52,11 +52,11 @@ test.describe('tracing in static routes with server islands', () => { 'sentry.op': { value: 'http.server', type: 'string' }, 'sentry.origin': { value: 'auto.http.astro', type: 'string' }, 'sentry.segment.name.source': { value: 'route', type: 'string' }, - 'http.request.header.accept': { value: expect.any(String), type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate, br, zstd', type: 'string' }, - 'http.request.header.accept-language': { value: 'en-US', type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'cors', type: 'string' }, - 'http.request.header.user-agent': { value: expect.any(String), type: 'string' }, + 'http.request.header.accept': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate, br, zstd'], type: 'array' }, + 'http.request.header.accept-language': { value: ['en-US'], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['cors'], type: 'array' }, + 'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' }, }); // unfortunately, the server island trace id is not the same as the client pageload trace id diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-mcp/tests/index.test.ts b/dev-packages/e2e-tests/test-applications/cloudflare-mcp/tests/index.test.ts index 64a42cc105ab..d47c2977a6e9 100644 --- a/dev-packages/e2e-tests/test-applications/cloudflare-mcp/tests/index.test.ts +++ b/dev-packages/e2e-tests/test-applications/cloudflare-mcp/tests/index.test.ts @@ -71,7 +71,7 @@ test('sends spans for MCP 2026-07-28 tool calls', async ({ baseURL }) => { expect(requestSpan.attributes['server.address']?.value).toBe('localhost'); expect(requestSpan.attributes['http.request.body.size']?.value).toBe(341); expect(requestSpan.attributes['user_agent.original']?.value).toBe('node'); - expect(requestSpan.attributes['http.request.header.content-type']?.value).toBe('application/json'); + expect(requestSpan.attributes['http.request.header.content-type']?.value).toEqual(['application/json']); expect(requestSpan.attributes['network.protocol.name']?.value).toBe('http'); expect(requestSpan.attributes['network.protocol.version']?.value).toBe('1.1'); expect(requestSpan.attributes['http.response.status_code']?.value).toBe(200); diff --git a/dev-packages/e2e-tests/test-applications/deno/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/deno/tests/transactions.test.ts index 3b219c52262a..41ae5a43d59a 100644 --- a/dev-packages/e2e-tests/test-applications/deno/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/deno/tests/transactions.test.ts @@ -38,40 +38,40 @@ const SEGMENT_SPAN = { value: expect.any(Number), }, 'http.request.header.accept': { - type: 'string', - value: '*/*', + type: 'array', + value: ['*/*'], }, 'http.request.header.accept-encoding': { - type: 'string', - value: 'gzip, deflate', + type: 'array', + value: ['gzip, deflate'], }, 'http.request.header.accept-language': { - type: 'string', - value: '*', + type: 'array', + value: ['*'], }, 'http.request.header.connection': { - type: 'string', - value: 'keep-alive', + type: 'array', + value: ['keep-alive'], }, 'http.request.header.host': { - type: 'string', - value: expect.stringMatching(/^localhost:\d+$/), + type: 'array', + value: [expect.stringMatching(/^localhost:\d+$/)], }, 'http.request.header.sec-fetch-mode': { - type: 'string', - value: 'cors', + type: 'array', + value: ['cors'], }, 'http.request.header.user-agent': { - type: 'string', - value: 'node', + type: 'array', + value: ['node'], }, 'http.request.method': { type: 'string', value: 'GET', }, 'http.response.header.content-type': { - type: 'string', - value: 'application/json', + type: 'array', + value: ['application/json'], }, 'http.response.status_code': { type: 'integer', diff --git a/dev-packages/e2e-tests/test-applications/elysia-node/tests/spans.test.ts b/dev-packages/e2e-tests/test-applications/elysia-node/tests/spans.test.ts index 556375501d6a..1ea7cafa50b2 100644 --- a/dev-packages/e2e-tests/test-applications/elysia-node/tests/spans.test.ts +++ b/dev-packages/e2e-tests/test-applications/elysia-node/tests/spans.test.ts @@ -217,5 +217,5 @@ test('Captures request metadata for POST requests', async ({ baseURL, request }) expect(segmentEvent.attributes['http.request.method']?.value).toEqual('POST'); expect(segmentEvent.attributes['url.full']?.value).toEqual(expect.stringContaining('/test-post')); - expect(segmentEvent.attributes['http.request.header.content-type']?.value).toEqual('application/json'); + expect(segmentEvent.attributes['http.request.header.content-type']?.value).toEqual(['application/json']); }); diff --git a/dev-packages/e2e-tests/test-applications/hono-4/tests/route-patterns.test.ts b/dev-packages/e2e-tests/test-applications/hono-4/tests/route-patterns.test.ts index 8c353dde7a1e..bfebb9f5b6ab 100644 --- a/dev-packages/e2e-tests/test-applications/hono-4/tests/route-patterns.test.ts +++ b/dev-packages/e2e-tests/test-applications/hono-4/tests/route-patterns.test.ts @@ -149,7 +149,7 @@ test.describe('request data extraction', () => { const segment = await segmentPromise; expect(segment.attributes['http.request.method']?.value).toBe('POST'); expect(segment.attributes['url.full']?.value).toContain(PREFIX); - expect(segment.attributes['http.request.header.x-custom-header']?.value).toBe('test-value'); + expect(segment.attributes['http.request.header.x-custom-header']?.value).toEqual(['test-value']); }); }); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-11-static/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-11-static/tests/transactions.test.ts index e3129513bae1..1667a68372c0 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-11-static/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-11-static/tests/transactions.test.ts @@ -39,13 +39,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'server.port': 3030, 'http.response.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 d63b55c1282c..e00e199f8880 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 @@ -62,13 +62,13 @@ function serverRequestAttributes(): Record { function testRequestHeaderAttributes(): Record { return { 'user_agent.original': { type: 'string', value: expect.any(String) }, - 'http.request.header.accept': { type: 'string', value: '*/*' }, - 'http.request.header.accept-encoding': { type: 'string', value: 'gzip, deflate' }, - 'http.request.header.accept-language': { type: 'string', value: '*' }, - 'http.request.header.connection': { type: 'string', value: 'keep-alive' }, - 'http.request.header.host': { type: 'string', value: expect.any(String) }, - 'http.request.header.sec-fetch-mode': { type: 'string', value: 'cors' }, - 'http.request.header.user-agent': { type: 'string', value: 'node' }, + 'http.request.header.accept': { type: 'array', value: ['*/*'] }, + 'http.request.header.accept-encoding': { type: 'array', value: ['gzip, deflate'] }, + 'http.request.header.accept-language': { type: 'array', value: ['*'] }, + 'http.request.header.connection': { type: 'array', value: ['keep-alive'] }, + 'http.request.header.host': { type: 'array', value: [expect.any(String)] }, + 'http.request.header.sec-fetch-mode': { type: 'array', value: ['cors'] }, + 'http.request.header.user-agent': { type: 'array', value: ['node'] }, }; } @@ -137,13 +137,13 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'http.route': { type: 'string', value: '/test-inbound-headers/:id' }, 'url.full': { type: 'string', value: `http://localhost:3030/test-inbound-headers/${id}` }, 'url.path': { type: 'string', value: `/test-inbound-headers/${id}` }, - 'http.request.header.connection': { type: 'string', value: 'keep-alive' }, - 'http.request.header.host': { type: 'string', value: expect.any(String) }, + 'http.request.header.connection': { type: 'array', value: ['keep-alive'] }, + 'http.request.header.host': { type: 'array', value: [expect.any(String)] }, 'http.request.header.sentry-trace': { - type: 'string', - value: expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/), + type: 'array', + value: [expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/)], }, - 'http.request.header.baggage': { type: 'string', value: expect.any(String) }, + 'http.request.header.baggage': { type: 'array', value: [expect.any(String)] }, }), }); }); 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 445ab706f934..6803c5ea30ab 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 @@ -20,10 +20,12 @@ test('App router spans should be attached to the pageload request span', async ( test('extracts HTTP request headers as span attributes', async ({ baseURL }) => { const serverSpanPromise = waitForStreamedSpan('nextjs-15', span => { + const requestId = span.attributes['http.request.header.x-request-id']; return ( span.name === 'GET /pageload-tracing' && span.is_segment && - span.attributes['http.request.header.x-request-id']?.value === 'nextjs-789' + requestId?.type === 'array' && + requestId.value[0] === 'nextjs-789' ); }); @@ -41,11 +43,11 @@ test('extracts HTTP request headers as span attributes', async ({ baseURL }) => const serverSpan = await serverSpanPromise; expect(serverSpan.attributes).toMatchObject({ - 'http.request.header.user-agent': { value: 'Custom-NextJS-Agent/15.0', type: 'string' }, - 'http.request.header.content-type': { value: 'text/html', type: 'string' }, - 'http.request.header.x-nextjs-test': { value: 'nextjs-header-value', type: 'string' }, - 'http.request.header.accept': { value: 'text/html, application/xhtml+xml', type: 'string' }, - 'http.request.header.x-framework': { value: 'Next.js', type: 'string' }, - 'http.request.header.x-request-id': { value: 'nextjs-789', type: 'string' }, + 'http.request.header.user-agent': { value: ['Custom-NextJS-Agent/15.0'], type: 'array' }, + 'http.request.header.content-type': { value: ['text/html'], type: 'array' }, + 'http.request.header.x-nextjs-test': { value: ['nextjs-header-value'], type: 'array' }, + 'http.request.header.accept': { value: ['text/html, application/xhtml+xml'], type: 'array' }, + 'http.request.header.x-framework': { value: ['Next.js'], type: 'array' }, + 'http.request.header.x-request-id': { value: ['nextjs-789'], type: 'array' }, }); }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/tests/route-handler.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/route-handler.test.ts index 869d343e0494..a033f1aa0eb5 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15/tests/route-handler.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/route-handler.test.ts @@ -16,7 +16,7 @@ test('Should create a span for node route handlers', async ({ request }) => { // This is flaking on dev mode if (process.env.TEST_ENV !== 'development' && process.env.TEST_ENV !== 'dev-turbopack') { - expect(routehandlerSpan.attributes['http.request.header.x-charly']?.value).toBe('gomez'); + expect(routehandlerSpan.attributes['http.request.header.x-charly']?.value).toEqual(['gomez']); } }); @@ -35,7 +35,7 @@ test('Should create a span for edge route handlers', async ({ request }) => { expect(routehandlerSpan.status).toBe('ok'); expect(getSpanOp(routehandlerSpan)).toBe('http.server'); - expect(routehandlerSpan.attributes['http.request.header.x-charly']?.value).toBe('gomez'); + expect(routehandlerSpan.attributes['http.request.header.x-charly']?.value).toEqual(['gomez']); }); test('Should create a span for static route handlers', async ({ request }) => { diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/pageload-tracing.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/pageload-tracing.test.ts index b1e6c36266b9..19879c4e6853 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/pageload-tracing.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/pageload-tracing.test.ts @@ -22,10 +22,12 @@ test.skip('App router spans should be attached to the pageload request span', as // TODO: HTTP request headers are not extracted as span attributes on Cloudflare Workers test.skip('extracts HTTP request headers as span attributes', async ({ baseURL }) => { const serverSpanPromise = waitForStreamedSpan('nextjs-16-cf-workers', span => { + const requestId = span.attributes['http.request.header.x-request-id']; return ( span.name === 'GET /pageload-tracing' && span.is_segment && - span.attributes['http.request.header.x-request-id']?.value === 'nextjs-789' + requestId?.type === 'array' && + requestId.value[0] === 'nextjs-789' ); }); @@ -43,11 +45,11 @@ test.skip('extracts HTTP request headers as span attributes', async ({ baseURL } const serverSpan = await serverSpanPromise; expect(serverSpan.attributes).toMatchObject({ - 'http.request.header.user-agent': { value: 'Custom-NextJS-Agent/15.0', type: 'string' }, - 'http.request.header.content-type': { value: 'text/html', type: 'string' }, - 'http.request.header.x-nextjs-test': { value: 'nextjs-header-value', type: 'string' }, - 'http.request.header.accept': { value: 'text/html, application/xhtml+xml', type: 'string' }, - 'http.request.header.x-framework': { value: 'Next.js', type: 'string' }, - 'http.request.header.x-request-id': { value: 'nextjs-789', type: 'string' }, + 'http.request.header.user-agent': { value: ['Custom-NextJS-Agent/15.0'], type: 'array' }, + 'http.request.header.content-type': { value: ['text/html'], type: 'array' }, + 'http.request.header.x-nextjs-test': { value: ['nextjs-header-value'], type: 'array' }, + 'http.request.header.accept': { value: ['text/html, application/xhtml+xml'], type: 'array' }, + 'http.request.header.x-framework': { value: ['Next.js'], type: 'array' }, + 'http.request.header.x-request-id': { value: ['nextjs-789'], type: 'array' }, }); }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/route-handler.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/route-handler.test.ts index a478d0be69b5..7623d1c29e31 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/route-handler.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/route-handler.test.ts @@ -33,5 +33,5 @@ test('Should create a span for edge route handlers', async ({ request }) => { expect(routehandlerSpan.status).toBe('ok'); expect(getSpanOp(routehandlerSpan)).toBe('http.server'); - expect(routehandlerSpan.attributes['http.request.header.x-charly']?.value).toBe('gomez'); + expect(routehandlerSpan.attributes['http.request.header.x-charly']?.value).toEqual(['gomez']); }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-static/tests/pageload-tracing.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-static/tests/pageload-tracing.test.ts index 37c87a699414..2a5c41b21597 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-static/tests/pageload-tracing.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-static/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/nextjs-16-static/tests/route-handler.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-static/tests/route-handler.test.ts index 2837cd84820b..41447d6db2a6 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-static/tests/route-handler.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-static/tests/route-handler.test.ts @@ -16,7 +16,7 @@ test('Should create a transaction for node route handlers', async ({ request }) expect(routehandlerTransaction.contexts?.trace?.status).toBe('ok'); expect(routehandlerTransaction.contexts?.trace?.op).toBe('http.server'); - expect(routehandlerTransaction.contexts?.trace?.data?.['http.request.header.x-charly']).toBe('gomez'); + expect(routehandlerTransaction.contexts?.trace?.data?.['http.request.header.x-charly']).toEqual(['gomez']); }); test('Should create a transaction for edge route handlers', async ({ request }) => { @@ -34,7 +34,7 @@ test('Should create a transaction for edge route handlers', async ({ request }) expect(routehandlerTransaction.contexts?.trace?.status).toBe('ok'); expect(routehandlerTransaction.contexts?.trace?.op).toBe('http.server'); - expect(routehandlerTransaction.contexts?.trace?.data?.['http.request.header.x-charly']).toBe('gomez'); + expect(routehandlerTransaction.contexts?.trace?.data?.['http.request.header.x-charly']).toEqual(['gomez']); }); test('Should report an error with a parameterized transaction name for a throwing route handler', async ({ diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/pageload-tracing.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/pageload-tracing.test.ts index 97d2e9e128e2..9dd4b972a21c 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/pageload-tracing.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/pageload-tracing.test.ts @@ -20,10 +20,12 @@ test('App router spans should be attached to the pageload request span', async ( test('extracts HTTP request headers as span attributes', async ({ baseURL }) => { const serverSpanPromise = waitForStreamedSpan('nextjs-16', span => { + const requestId = span.attributes['http.request.header.x-request-id']; return ( span.name === 'GET /pageload-tracing' && span.is_segment && - span.attributes['http.request.header.x-request-id']?.value === 'nextjs-789' + requestId?.type === 'array' && + requestId.value[0] === 'nextjs-789' ); }); @@ -41,11 +43,11 @@ test('extracts HTTP request headers as span attributes', async ({ baseURL }) => const serverSpan = await serverSpanPromise; expect(serverSpan.attributes).toMatchObject({ - 'http.request.header.user-agent': { value: 'Custom-NextJS-Agent/15.0', type: 'string' }, - 'http.request.header.content-type': { value: 'text/html', type: 'string' }, - 'http.request.header.x-nextjs-test': { value: 'nextjs-header-value', type: 'string' }, - 'http.request.header.accept': { value: 'text/html, application/xhtml+xml', type: 'string' }, - 'http.request.header.x-framework': { value: 'Next.js', type: 'string' }, - 'http.request.header.x-request-id': { value: 'nextjs-789', type: 'string' }, + 'http.request.header.user-agent': { value: ['Custom-NextJS-Agent/15.0'], type: 'array' }, + 'http.request.header.content-type': { value: ['text/html'], type: 'array' }, + 'http.request.header.x-nextjs-test': { value: ['nextjs-header-value'], type: 'array' }, + 'http.request.header.accept': { value: ['text/html, application/xhtml+xml'], type: 'array' }, + 'http.request.header.x-framework': { value: ['Next.js'], type: 'array' }, + 'http.request.header.x-request-id': { value: ['nextjs-789'], type: 'array' }, }); }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/route-handler.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/route-handler.test.ts index 7d47945d9aed..31936d435283 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/route-handler.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/route-handler.test.ts @@ -16,7 +16,7 @@ test('Should create a span for node route handlers', async ({ request }) => { expect(routehandlerSpan.status).toBe('ok'); expect(getSpanOp(routehandlerSpan)).toBe('http.server'); - expect(routehandlerSpan.attributes['http.request.header.x-charly']?.value).toBe('gomez'); + expect(routehandlerSpan.attributes['http.request.header.x-charly']?.value).toEqual(['gomez']); }); test('Should create a span for edge route handlers', async ({ request }) => { @@ -34,7 +34,7 @@ test('Should create a span for edge route handlers', async ({ request }) => { expect(routehandlerSpan.status).toBe('ok'); expect(getSpanOp(routehandlerSpan)).toBe('http.server'); - expect(routehandlerSpan.attributes['http.request.header.x-charly']?.value).toBe('gomez'); + expect(routehandlerSpan.attributes['http.request.header.x-charly']?.value).toEqual(['gomez']); }); test('Should report an error with a parameterized span name for a throwing route handler', async ({ request }) => { diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/route-handlers.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/route-handlers.test.ts index 29b4bcbb8dac..025bfd2d95c3 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/route-handlers.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/route-handlers.test.ts @@ -13,7 +13,7 @@ test('Should create a span for route handlers', async ({ request }) => { expect(routehandlerSpan.status).toBe('ok'); expect(getSpanOp(routehandlerSpan)).toBe('http.server'); - expect(routehandlerSpan.attributes['http.request.header.x-yeet']?.value).toBe('test-value'); + expect(routehandlerSpan.attributes['http.request.header.x-yeet']?.value).toEqual(['test-value']); }); test('Should create a span for route handlers and correctly set span status depending on http status', async ({ diff --git a/dev-packages/e2e-tests/test-applications/node-express-static/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-express-static/tests/transactions.test.ts index 37c782f5f34c..0ba20e57da0e 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-static/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express-static/tests/transactions.test.ts @@ -39,13 +39,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'server.port': 3030, 'http.response.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}/), @@ -268,11 +268,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-express-streaming/tests/spans.test.ts b/dev-packages/e2e-tests/test-applications/node-express-streaming/tests/spans.test.ts index 8b8c2a5b6b2d..4b0d82cc9b88 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-streaming/tests/spans.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express-streaming/tests/spans.test.ts @@ -124,11 +124,13 @@ test.skip('Outgoing fetch spans include response headers when headersToSpanAttri test('Extracts HTTP request headers as streamed span attributes', async ({ baseURL }) => { const rootSpanPromise = waitForStreamedSpan('node-express-streaming', span => { + const userAgent = span.attributes['http.request.header.user-agent']; return ( span.name === 'GET /test-transaction' && getSpanOp(span) === 'http.server' && span.is_segment && - span.attributes['http.request.header.user-agent']?.value === 'Custom-Agent/1.0 (Test)' + userAgent?.type === 'array' && + userAgent.value[0] === 'Custom-Agent/1.0 (Test)' ); }); @@ -144,9 +146,9 @@ test('Extracts HTTP request headers as streamed span attributes', async ({ baseU const rootSpan = await rootSpanPromise; - expect(rootSpan.attributes['http.request.header.user-agent']?.value).toBe('Custom-Agent/1.0 (Test)'); - expect(rootSpan.attributes['http.request.header.content-type']?.value).toBe('application/json'); - expect(rootSpan.attributes['http.request.header.x-custom-header']?.value).toBe('test-value'); - expect(rootSpan.attributes['http.request.header.accept']?.value).toBe('application/json, text/plain'); - expect(rootSpan.attributes['http.request.header.x-request-id']?.value).toBe('req-123'); + expect(rootSpan.attributes['http.request.header.user-agent']?.value).toEqual(['Custom-Agent/1.0 (Test)']); + expect(rootSpan.attributes['http.request.header.content-type']?.value).toEqual(['application/json']); + expect(rootSpan.attributes['http.request.header.x-custom-header']?.value).toEqual(['test-value']); + expect(rootSpan.attributes['http.request.header.accept']?.value).toEqual(['application/json, text/plain']); + expect(rootSpan.attributes['http.request.header.x-request-id']?.value).toEqual(['req-123']); }); diff --git a/dev-packages/e2e-tests/test-applications/node-express-v5/tests/spans.test.ts b/dev-packages/e2e-tests/test-applications/node-express-v5/tests/spans.test.ts index 519332ae945f..926328c3f8e5 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-v5/tests/spans.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express-v5/tests/spans.test.ts @@ -44,13 +44,13 @@ test('Sends an API route span', async ({ baseURL }) => { 'server.port': { value: 3030, type: 'integer' }, 'http.response.status_text': { value: 'OK', type: 'string' }, 'http.route': { value: '/test-transaction', type: 'string' }, - 'http.request.header.accept': { value: '*/*', type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate', type: 'string' }, - 'http.request.header.accept-language': { value: '*', type: 'string' }, - 'http.request.header.connection': { value: 'keep-alive', type: 'string' }, - 'http.request.header.host': { value: expect.any(String), type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'cors', type: 'string' }, - 'http.request.header.user-agent': { value: 'node', type: 'string' }, + 'http.request.header.accept': { value: ['*/*'], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate'], type: 'array' }, + 'http.request.header.accept-language': { value: ['*'], type: 'array' }, + 'http.request.header.connection': { value: ['keep-alive'], type: 'array' }, + 'http.request.header.host': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['cors'], type: 'array' }, + 'http.request.header.user-agent': { value: ['node'], type: 'array' }, }), }), ); diff --git a/dev-packages/e2e-tests/test-applications/node-express/tests/spans.test.ts b/dev-packages/e2e-tests/test-applications/node-express/tests/spans.test.ts index 46ca784f61b3..0a9b2112a40d 100644 --- a/dev-packages/e2e-tests/test-applications/node-express/tests/spans.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express/tests/spans.test.ts @@ -44,13 +44,13 @@ test('Sends an API route span', async ({ baseURL }) => { 'server.port': { value: 3030, type: 'integer' }, 'http.response.status_text': { value: 'OK', type: 'string' }, 'http.route': { value: '/test-transaction', type: 'string' }, - 'http.request.header.accept': { value: '*/*', type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate', type: 'string' }, - 'http.request.header.accept-language': { value: '*', type: 'string' }, - 'http.request.header.connection': { value: 'keep-alive', type: 'string' }, - 'http.request.header.host': { value: expect.any(String), type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'cors', type: 'string' }, - 'http.request.header.user-agent': { value: 'node', type: 'string' }, + 'http.request.header.accept': { value: ['*/*'], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate'], type: 'array' }, + 'http.request.header.accept-language': { value: ['*'], type: 'array' }, + 'http.request.header.connection': { value: ['keep-alive'], type: 'array' }, + 'http.request.header.host': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['cors'], type: 'array' }, + 'http.request.header.user-agent': { value: ['node'], type: 'array' }, }), }), ); @@ -277,11 +277,11 @@ test('Extracts HTTP request headers as span attributes', async ({ baseURL }) => expect(segmentEvent.attributes).toEqual( expect.objectContaining({ - 'http.request.header.user-agent': { value: 'Custom-Agent/1.0 (Test)', type: 'string' }, - 'http.request.header.content-type': { value: 'application/json', type: 'string' }, - 'http.request.header.x-custom-header': { value: 'test-value', type: 'string' }, - 'http.request.header.accept': { value: 'application/json, text/plain', type: 'string' }, - 'http.request.header.x-request-id': { value: 'req-123', type: 'string' }, + 'http.request.header.user-agent': { value: ['Custom-Agent/1.0 (Test)'], type: 'array' }, + 'http.request.header.content-type': { value: ['application/json'], type: 'array' }, + 'http.request.header.x-custom-header': { value: ['test-value'], type: 'array' }, + 'http.request.header.accept': { value: ['application/json, text/plain'], type: 'array' }, + 'http.request.header.x-request-id': { value: ['req-123'], type: 'array' }, }), ); }); 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 ff130a1261fd..c11a12c0e138 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 @@ -92,13 +92,13 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'server.port': { value: 3030, type: 'integer' }, 'http.response.status_text': { value: 'OK', type: 'string' }, 'http.route': { value: '/test-outgoing-http/:id', type: 'string' }, - 'http.request.header.accept': { value: '*/*', type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate', type: 'string' }, - 'http.request.header.accept-language': { value: '*', type: 'string' }, - 'http.request.header.connection': { value: 'keep-alive', type: 'string' }, - 'http.request.header.host': { value: expect.any(String), type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'cors', type: 'string' }, - 'http.request.header.user-agent': { value: 'node', type: 'string' }, + 'http.request.header.accept': { value: ['*/*'], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate'], type: 'array' }, + 'http.request.header.accept-language': { value: ['*'], type: 'array' }, + 'http.request.header.connection': { value: ['keep-alive'], type: 'array' }, + 'http.request.header.host': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['cors'], type: 'array' }, + 'http.request.header.user-agent': { value: ['node'], type: 'array' }, }), }), ); @@ -132,12 +132,12 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'server.port': { value: 3030, type: 'integer' }, 'http.response.status_text': { value: 'OK', type: 'string' }, 'http.route': { value: '/test-inbound-headers/:id', type: 'string' }, - 'http.request.header.baggage': { value: expect.any(String), type: 'string' }, - 'http.request.header.connection': { value: 'keep-alive', type: 'string' }, - 'http.request.header.host': { value: expect.any(String), type: 'string' }, + 'http.request.header.baggage': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.connection': { value: ['keep-alive'], type: 'array' }, + 'http.request.header.host': { value: [expect.any(String)], type: 'array' }, 'http.request.header.sentry-trace': { - value: expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/), - type: 'string', + value: [expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/)], + type: 'array', }, }), }), @@ -234,13 +234,13 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { 'server.port': { value: 3030, type: 'integer' }, 'http.response.status_text': { value: 'OK', type: 'string' }, 'http.route': { value: '/test-outgoing-fetch/:id', type: 'string' }, - 'http.request.header.accept': { value: '*/*', type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate', type: 'string' }, - 'http.request.header.accept-language': { value: '*', type: 'string' }, - 'http.request.header.connection': { value: 'keep-alive', type: 'string' }, - 'http.request.header.host': { value: expect.any(String), type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'cors', type: 'string' }, - 'http.request.header.user-agent': { value: 'node', type: 'string' }, + 'http.request.header.accept': { value: ['*/*'], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate'], type: 'array' }, + 'http.request.header.accept-language': { value: ['*'], type: 'array' }, + 'http.request.header.connection': { value: ['keep-alive'], type: 'array' }, + 'http.request.header.host': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['cors'], type: 'array' }, + 'http.request.header.user-agent': { value: ['node'], type: 'array' }, }), }), ); @@ -275,18 +275,18 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { 'http.response.status_text': { value: 'OK', type: 'string' }, 'user_agent.original': { value: 'node', type: 'string' }, 'http.route': { value: '/test-inbound-headers/:id', type: 'string' }, - 'http.request.header.accept': { value: '*/*', type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate', type: 'string' }, - 'http.request.header.accept-language': { value: '*', type: 'string' }, - 'http.request.header.baggage': { value: expect.any(String), type: 'string' }, - 'http.request.header.connection': { value: 'keep-alive', type: 'string' }, - 'http.request.header.host': { value: expect.any(String), type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'cors', type: 'string' }, + 'http.request.header.accept': { value: ['*/*'], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate'], type: 'array' }, + 'http.request.header.accept-language': { value: ['*'], type: 'array' }, + 'http.request.header.baggage': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.connection': { value: ['keep-alive'], type: 'array' }, + 'http.request.header.host': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['cors'], type: 'array' }, 'http.request.header.sentry-trace': { - value: expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/), - type: 'string', + value: [expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/)], + type: 'array', }, - 'http.request.header.user-agent': { value: 'node', type: 'string' }, + 'http.request.header.user-agent': { value: ['node'], type: 'array' }, }), }), ); diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/spans.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/spans.test.ts index ea264ee9f841..20c5ea6f3a3f 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/spans.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/spans.test.ts @@ -44,13 +44,13 @@ test('Sends an API route span', async ({ baseURL }) => { 'server.port': { value: 3030, type: 'integer' }, 'http.response.status_text': { value: 'OK', type: 'string' }, 'http.route': { value: '/test-transaction', type: 'string' }, - 'http.request.header.accept': { value: '*/*', type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate', type: 'string' }, - 'http.request.header.accept-language': { value: '*', type: 'string' }, - 'http.request.header.connection': { value: 'keep-alive', type: 'string' }, - 'http.request.header.host': { value: expect.any(String), type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'cors', type: 'string' }, - 'http.request.header.user-agent': { value: 'node', type: 'string' }, + 'http.request.header.accept': { value: ['*/*'], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate'], type: 'array' }, + 'http.request.header.accept-language': { value: ['*'], type: 'array' }, + 'http.request.header.connection': { value: ['keep-alive'], type: 'array' }, + 'http.request.header.host': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['cors'], type: 'array' }, + 'http.request.header.user-agent': { value: ['node'], type: 'array' }, }), }), ); @@ -139,8 +139,8 @@ test('Captures request metadata', async ({ baseURL }) => { expect.stringMatching(/^http:\/\/localhost:(\d+)\/test-post$/), ); expect(segmentEvent.attributes['http.request.method']?.value).toEqual('POST'); - expect(segmentEvent.attributes['http.request.header.user-agent']?.value).toEqual(expect.stringContaining('')); - expect(segmentEvent.attributes['http.request.header.content-type']?.value).toEqual('application/json'); + expect(segmentEvent.attributes['http.request.header.user-agent']?.value).toEqual([expect.stringContaining('')]); + expect(segmentEvent.attributes['http.request.header.content-type']?.value).toEqual(['application/json']); expect(segmentEvent.attributes['http.request.body.data']?.value).toBe(JSON.stringify({ foo: 'bar', other: 1 })); expect(segmentEvent.attributes['user.ip_address']?.value).toEqual('::1'); 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 811a8ac6c17c..67302acc3bd2 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 @@ -92,13 +92,13 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'server.port': { value: 3030, type: 'integer' }, 'http.response.status_text': { value: 'OK', type: 'string' }, 'http.route': { value: '/test-outgoing-http/:id', type: 'string' }, - 'http.request.header.accept': { value: '*/*', type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate', type: 'string' }, - 'http.request.header.accept-language': { value: '*', type: 'string' }, - 'http.request.header.connection': { value: 'keep-alive', type: 'string' }, - 'http.request.header.host': { value: expect.any(String), type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'cors', type: 'string' }, - 'http.request.header.user-agent': { value: 'node', type: 'string' }, + 'http.request.header.accept': { value: ['*/*'], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate'], type: 'array' }, + 'http.request.header.accept-language': { value: ['*'], type: 'array' }, + 'http.request.header.connection': { value: ['keep-alive'], type: 'array' }, + 'http.request.header.host': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['cors'], type: 'array' }, + 'http.request.header.user-agent': { value: ['node'], type: 'array' }, }), }), ); @@ -132,12 +132,12 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'server.port': { value: 3030, type: 'integer' }, 'http.response.status_text': { value: 'OK', type: 'string' }, 'http.route': { value: '/test-inbound-headers/:id', type: 'string' }, - 'http.request.header.baggage': { value: expect.any(String), type: 'string' }, - 'http.request.header.connection': { value: 'keep-alive', type: 'string' }, - 'http.request.header.host': { value: expect.any(String), type: 'string' }, + 'http.request.header.baggage': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.connection': { value: ['keep-alive'], type: 'array' }, + 'http.request.header.host': { value: [expect.any(String)], type: 'array' }, 'http.request.header.sentry-trace': { - value: expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/), - type: 'string', + value: [expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/)], + type: 'array', }, }), }), @@ -234,13 +234,13 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { 'server.port': { value: 3030, type: 'integer' }, 'http.response.status_text': { value: 'OK', type: 'string' }, 'http.route': { value: '/test-outgoing-fetch/:id', type: 'string' }, - 'http.request.header.accept': { value: '*/*', type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate', type: 'string' }, - 'http.request.header.accept-language': { value: '*', type: 'string' }, - 'http.request.header.connection': { value: 'keep-alive', type: 'string' }, - 'http.request.header.host': { value: expect.any(String), type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'cors', type: 'string' }, - 'http.request.header.user-agent': { value: 'node', type: 'string' }, + 'http.request.header.accept': { value: ['*/*'], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate'], type: 'array' }, + 'http.request.header.accept-language': { value: ['*'], type: 'array' }, + 'http.request.header.connection': { value: ['keep-alive'], type: 'array' }, + 'http.request.header.host': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['cors'], type: 'array' }, + 'http.request.header.user-agent': { value: ['node'], type: 'array' }, }), }), ); @@ -275,18 +275,18 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { 'http.response.status_text': { value: 'OK', type: 'string' }, 'http.route': { value: '/test-inbound-headers/:id', type: 'string' }, 'user_agent.original': { value: 'node', type: 'string' }, - 'http.request.header.accept': { value: '*/*', type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate', type: 'string' }, - 'http.request.header.accept-language': { value: '*', type: 'string' }, - 'http.request.header.baggage': { value: expect.any(String), type: 'string' }, - 'http.request.header.connection': { value: 'keep-alive', type: 'string' }, - 'http.request.header.host': { value: expect.any(String), type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'cors', type: 'string' }, + 'http.request.header.accept': { value: ['*/*'], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate'], type: 'array' }, + 'http.request.header.accept-language': { value: ['*'], type: 'array' }, + 'http.request.header.baggage': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.connection': { value: ['keep-alive'], type: 'array' }, + 'http.request.header.host': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['cors'], type: 'array' }, 'http.request.header.sentry-trace': { - value: expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/), - type: 'string', + value: [expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/)], + type: 'array', }, - 'http.request.header.user-agent': { value: 'node', type: 'string' }, + 'http.request.header.user-agent': { value: ['node'], type: 'array' }, }), }), ); diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-4/tests/spans.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify-4/tests/spans.test.ts index 8da2eb18a882..d6c534203d72 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-4/tests/spans.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-fastify-4/tests/spans.test.ts @@ -44,13 +44,13 @@ test('Sends an API route span', async ({ baseURL }) => { 'server.port': { value: 3030, type: 'integer' }, 'http.response.status_text': { value: 'OK', type: 'string' }, 'http.route': { value: '/test-transaction', type: 'string' }, - 'http.request.header.accept': { value: '*/*', type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate', type: 'string' }, - 'http.request.header.accept-language': { value: '*', type: 'string' }, - 'http.request.header.connection': { value: 'keep-alive', type: 'string' }, - 'http.request.header.host': { value: expect.any(String), type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'cors', type: 'string' }, - 'http.request.header.user-agent': { value: 'node', type: 'string' }, + 'http.request.header.accept': { value: ['*/*'], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate'], type: 'array' }, + 'http.request.header.accept-language': { value: ['*'], type: 'array' }, + 'http.request.header.connection': { value: ['keep-alive'], type: 'array' }, + 'http.request.header.host': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['cors'], type: 'array' }, + 'http.request.header.user-agent': { value: ['node'], type: 'array' }, }), }), ); @@ -139,8 +139,8 @@ test('Captures request metadata', async ({ baseURL }) => { expect.stringMatching(/^http:\/\/localhost:(\d+)\/test-post$/), ); expect(segmentEvent.attributes['http.request.method']?.value).toEqual('POST'); - expect(segmentEvent.attributes['http.request.header.user-agent']?.value).toEqual(expect.stringContaining('')); - expect(segmentEvent.attributes['http.request.header.content-type']?.value).toEqual('application/json'); + expect(segmentEvent.attributes['http.request.header.user-agent']?.value).toEqual([expect.stringContaining('')]); + expect(segmentEvent.attributes['http.request.header.content-type']?.value).toEqual(['application/json']); expect(segmentEvent.attributes['http.request.body.data']?.value).toBe(JSON.stringify({ foo: 'bar', other: 1 })); expect(segmentEvent.attributes['user.ip_address']?.value).toEqual('::1'); 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 504d06efd221..144fb7c9d534 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 @@ -92,13 +92,13 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'server.port': { value: 3030, type: 'integer' }, 'http.response.status_text': { value: 'OK', type: 'string' }, 'http.route': { value: '/test-outgoing-http/:id', type: 'string' }, - 'http.request.header.accept': { value: '*/*', type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate', type: 'string' }, - 'http.request.header.accept-language': { value: '*', type: 'string' }, - 'http.request.header.connection': { value: 'keep-alive', type: 'string' }, - 'http.request.header.host': { value: 'localhost:3030', type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'cors', type: 'string' }, - 'http.request.header.user-agent': { value: 'node', type: 'string' }, + 'http.request.header.accept': { value: ['*/*'], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate'], type: 'array' }, + 'http.request.header.accept-language': { value: ['*'], type: 'array' }, + 'http.request.header.connection': { value: ['keep-alive'], type: 'array' }, + 'http.request.header.host': { value: ['localhost:3030'], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['cors'], type: 'array' }, + 'http.request.header.user-agent': { value: ['node'], type: 'array' }, }), }), ); @@ -132,12 +132,12 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'server.port': { value: 3030, type: 'integer' }, 'http.response.status_text': { value: 'OK', type: 'string' }, 'http.route': { value: '/test-inbound-headers/:id', type: 'string' }, - 'http.request.header.baggage': { value: expect.any(String), type: 'string' }, - 'http.request.header.connection': { value: 'keep-alive', type: 'string' }, - 'http.request.header.host': { value: expect.any(String), type: 'string' }, + 'http.request.header.baggage': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.connection': { value: ['keep-alive'], type: 'array' }, + 'http.request.header.host': { value: [expect.any(String)], type: 'array' }, 'http.request.header.sentry-trace': { - value: expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/), - type: 'string', + value: [expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/)], + type: 'array', }, }), }), @@ -234,13 +234,13 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { 'server.port': { value: 3030, type: 'integer' }, 'http.response.status_text': { value: 'OK', type: 'string' }, 'http.route': { value: '/test-outgoing-fetch/:id', type: 'string' }, - 'http.request.header.accept': { value: '*/*', type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate', type: 'string' }, - 'http.request.header.accept-language': { value: '*', type: 'string' }, - 'http.request.header.connection': { value: 'keep-alive', type: 'string' }, - 'http.request.header.host': { value: expect.any(String), type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'cors', type: 'string' }, - 'http.request.header.user-agent': { value: 'node', type: 'string' }, + 'http.request.header.accept': { value: ['*/*'], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate'], type: 'array' }, + 'http.request.header.accept-language': { value: ['*'], type: 'array' }, + 'http.request.header.connection': { value: ['keep-alive'], type: 'array' }, + 'http.request.header.host': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['cors'], type: 'array' }, + 'http.request.header.user-agent': { value: ['node'], type: 'array' }, }), }), ); @@ -275,18 +275,18 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { 'http.response.status_text': { value: 'OK', type: 'string' }, 'user_agent.original': { value: 'node', type: 'string' }, 'http.route': { value: '/test-inbound-headers/:id', type: 'string' }, - 'http.request.header.accept': { value: '*/*', type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate', type: 'string' }, - 'http.request.header.accept-language': { value: '*', type: 'string' }, - 'http.request.header.baggage': { value: expect.any(String), type: 'string' }, - 'http.request.header.connection': { value: 'keep-alive', type: 'string' }, - 'http.request.header.host': { value: expect.any(String), type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'cors', type: 'string' }, + 'http.request.header.accept': { value: ['*/*'], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate'], type: 'array' }, + 'http.request.header.accept-language': { value: ['*'], type: 'array' }, + 'http.request.header.baggage': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.connection': { value: ['keep-alive'], type: 'array' }, + 'http.request.header.host': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['cors'], type: 'array' }, 'http.request.header.sentry-trace': { - value: expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/), - type: 'string', + value: [expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/)], + type: 'array', }, - 'http.request.header.user-agent': { value: 'node', type: 'string' }, + 'http.request.header.user-agent': { value: ['node'], type: 'array' }, }), }), ); diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/spans.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/spans.test.ts index 71fecfdd9984..d6bf9a4d10b5 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/spans.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/spans.test.ts @@ -44,13 +44,13 @@ test('Sends an API route span', async ({ baseURL }) => { 'server.port': { value: 3030, type: 'integer' }, 'http.response.status_text': { value: 'OK', type: 'string' }, 'http.route': { value: '/test-transaction', type: 'string' }, - 'http.request.header.accept': { value: '*/*', type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate', type: 'string' }, - 'http.request.header.accept-language': { value: '*', type: 'string' }, - 'http.request.header.connection': { value: 'keep-alive', type: 'string' }, - 'http.request.header.host': { value: expect.any(String), type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'cors', type: 'string' }, - 'http.request.header.user-agent': { value: 'node', type: 'string' }, + 'http.request.header.accept': { value: ['*/*'], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate'], type: 'array' }, + 'http.request.header.accept-language': { value: ['*'], type: 'array' }, + 'http.request.header.connection': { value: ['keep-alive'], type: 'array' }, + 'http.request.header.host': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['cors'], type: 'array' }, + 'http.request.header.user-agent': { value: ['node'], type: 'array' }, }), }), ); @@ -139,8 +139,8 @@ test('Captures request metadata', async ({ baseURL }) => { expect.stringMatching(/^http:\/\/localhost:(\d+)\/test-post$/), ); expect(segmentEvent.attributes['http.request.method']?.value).toEqual('POST'); - expect(segmentEvent.attributes['http.request.header.user-agent']?.value).toEqual(expect.stringContaining('')); - expect(segmentEvent.attributes['http.request.header.content-type']?.value).toEqual('application/json'); + expect(segmentEvent.attributes['http.request.header.user-agent']?.value).toEqual([expect.stringContaining('')]); + expect(segmentEvent.attributes['http.request.header.content-type']?.value).toEqual(['application/json']); expect(segmentEvent.attributes['http.request.body.data']?.value).toBe(JSON.stringify({ foo: 'bar', other: 1 })); expect(segmentEvent.attributes['user.ip_address']?.value).toEqual('::1'); diff --git a/dev-packages/e2e-tests/test-applications/node-hapi/tests/spans.test.ts b/dev-packages/e2e-tests/test-applications/node-hapi/tests/spans.test.ts index 553b8fcaddaf..6f69c36ae622 100644 --- a/dev-packages/e2e-tests/test-applications/node-hapi/tests/spans.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-hapi/tests/spans.test.ts @@ -44,13 +44,13 @@ test('Sends successful span', async ({ baseURL }) => { 'server.port': { value: 3030, type: 'integer' }, 'http.response.status_text': { value: 'OK', type: 'string' }, 'http.route': { value: '/test-success', type: 'string' }, - 'http.request.header.accept': { value: '*/*', type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate', type: 'string' }, - 'http.request.header.accept-language': { value: '*', type: 'string' }, - 'http.request.header.connection': { value: 'keep-alive', type: 'string' }, - 'http.request.header.host': { value: expect.any(String), type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'cors', type: 'string' }, - 'http.request.header.user-agent': { value: 'node', type: 'string' }, + 'http.request.header.accept': { value: ['*/*'], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate'], type: 'array' }, + 'http.request.header.accept-language': { value: ['*'], type: 'array' }, + 'http.request.header.connection': { value: ['keep-alive'], type: 'array' }, + 'http.request.header.host': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['cors'], type: 'array' }, + 'http.request.header.user-agent': { value: ['node'], type: 'array' }, }), }), ); 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 e8c903379177..3a8437a9a26f 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 @@ -91,13 +91,13 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'server.port': { value: 3030, type: 'integer' }, 'http.response.status_text': { value: 'OK', type: 'string' }, 'http.route': { value: '/test-outgoing-http/:id', type: 'string' }, - 'http.request.header.accept': { value: '*/*', type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate', type: 'string' }, - 'http.request.header.accept-language': { value: '*', type: 'string' }, - 'http.request.header.connection': { value: 'keep-alive', type: 'string' }, - 'http.request.header.host': { value: expect.any(String), type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'cors', type: 'string' }, - 'http.request.header.user-agent': { value: 'node', type: 'string' }, + 'http.request.header.accept': { value: ['*/*'], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate'], type: 'array' }, + 'http.request.header.accept-language': { value: ['*'], type: 'array' }, + 'http.request.header.connection': { value: ['keep-alive'], type: 'array' }, + 'http.request.header.host': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['cors'], type: 'array' }, + 'http.request.header.user-agent': { value: ['node'], type: 'array' }, }), }), ); @@ -131,12 +131,12 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'server.port': { value: 3030, type: 'integer' }, 'http.response.status_text': { value: 'OK', type: 'string' }, 'http.route': { value: '/test-inbound-headers/:id', type: 'string' }, - 'http.request.header.baggage': { value: expect.stringContaining(traceId!), type: 'string' }, - 'http.request.header.connection': { value: 'keep-alive', type: 'string' }, - 'http.request.header.host': { value: expect.any(String), type: 'string' }, + 'http.request.header.baggage': { value: [expect.stringContaining(traceId!)], type: 'array' }, + 'http.request.header.connection': { value: ['keep-alive'], type: 'array' }, + 'http.request.header.host': { value: [expect.any(String)], type: 'array' }, 'http.request.header.sentry-trace': { - value: expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/), - type: 'string', + value: [expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/)], + type: 'array', }, }), }), @@ -233,13 +233,13 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { 'server.port': { value: 3030, type: 'integer' }, 'http.response.status_text': { value: 'OK', type: 'string' }, 'http.route': { value: '/test-outgoing-fetch/:id', type: 'string' }, - 'http.request.header.accept': { value: '*/*', type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate', type: 'string' }, - 'http.request.header.accept-language': { value: '*', type: 'string' }, - 'http.request.header.connection': { value: 'keep-alive', type: 'string' }, - 'http.request.header.host': { value: 'localhost:3030', type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'cors', type: 'string' }, - 'http.request.header.user-agent': { value: 'node', type: 'string' }, + 'http.request.header.accept': { value: ['*/*'], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate'], type: 'array' }, + 'http.request.header.accept-language': { value: ['*'], type: 'array' }, + 'http.request.header.connection': { value: ['keep-alive'], type: 'array' }, + 'http.request.header.host': { value: ['localhost:3030'], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['cors'], type: 'array' }, + 'http.request.header.user-agent': { value: ['node'], type: 'array' }, }), }), ); @@ -274,18 +274,18 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { 'http.response.status_text': { value: 'OK', type: 'string' }, 'http.route': { value: '/test-inbound-headers/:id', type: 'string' }, 'user_agent.original': { value: 'node', type: 'string' }, - 'http.request.header.accept': { value: '*/*', type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate', type: 'string' }, - 'http.request.header.accept-language': { value: '*', type: 'string' }, - 'http.request.header.baggage': { value: expect.stringContaining(traceId!), type: 'string' }, - 'http.request.header.connection': { value: 'keep-alive', type: 'string' }, - 'http.request.header.host': { value: expect.any(String), type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'cors', type: 'string' }, + 'http.request.header.accept': { value: ['*/*'], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate'], type: 'array' }, + 'http.request.header.accept-language': { value: ['*'], type: 'array' }, + 'http.request.header.baggage': { value: [expect.stringContaining(traceId!)], type: 'array' }, + 'http.request.header.connection': { value: ['keep-alive'], type: 'array' }, + 'http.request.header.host': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['cors'], type: 'array' }, 'http.request.header.sentry-trace': { - value: expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/), - type: 'string', + value: [expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/)], + type: 'array', }, - 'http.request.header.user-agent': { value: 'node', type: 'string' }, + 'http.request.header.user-agent': { value: ['node'], type: 'array' }, }), }), ); diff --git a/dev-packages/e2e-tests/test-applications/node-koa/tests/spans.test.ts b/dev-packages/e2e-tests/test-applications/node-koa/tests/spans.test.ts index f2e127be64cf..69c327d69df4 100644 --- a/dev-packages/e2e-tests/test-applications/node-koa/tests/spans.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-koa/tests/spans.test.ts @@ -44,13 +44,13 @@ test('Sends an API route span', async ({ baseURL }) => { 'server.port': { value: 3030, type: 'integer' }, 'http.response.status_text': { value: 'OK', type: 'string' }, 'http.route': { value: '/test-transaction', type: 'string' }, - 'http.request.header.accept': { value: '*/*', type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate', type: 'string' }, - 'http.request.header.accept-language': { value: '*', type: 'string' }, - 'http.request.header.connection': { value: 'keep-alive', type: 'string' }, - 'http.request.header.host': { value: expect.any(String), type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'cors', type: 'string' }, - 'http.request.header.user-agent': { value: 'node', type: 'string' }, + 'http.request.header.accept': { value: ['*/*'], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate'], type: 'array' }, + 'http.request.header.accept-language': { value: ['*'], type: 'array' }, + 'http.request.header.connection': { value: ['keep-alive'], type: 'array' }, + 'http.request.header.host': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['cors'], type: 'array' }, + 'http.request.header.user-agent': { value: ['node'], type: 'array' }, }), }), ); @@ -146,8 +146,8 @@ test('Captures request metadata', async ({ baseURL }) => { expect.stringMatching(/^http:\/\/localhost:(\d+)\/test-post$/), ); expect(segmentEvent.attributes['http.request.method']?.value).toEqual('POST'); - expect(segmentEvent.attributes['http.request.header.user-agent']?.value).toEqual(expect.stringContaining('')); - expect(segmentEvent.attributes['http.request.header.content-type']?.value).toEqual('application/json'); + expect(segmentEvent.attributes['http.request.header.user-agent']?.value).toEqual([expect.stringContaining('')]); + expect(segmentEvent.attributes['http.request.header.content-type']?.value).toEqual(['application/json']); expect(segmentEvent.attributes['http.request.body.data']?.value).toBe(JSON.stringify({ foo: 'bar', other: 1 })); expect(segmentEvent.attributes['user.ip_address']?.value).toEqual('::1'); 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 51ad935bcf47..985d6bd0f225 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 @@ -57,11 +57,11 @@ test('extracts HTTP request headers as span attributes', async ({ baseURL }) => const serverSpan = await serverSpanPromise; expect(serverSpan.attributes).toMatchObject({ - 'http.request.header.user-agent': { type: 'string', value: 'Custom-Nuxt-Agent/3.0' }, - 'http.request.header.content-type': { type: 'string', value: 'application/json' }, - 'http.request.header.x-nuxt-test': { type: 'string', value: 'nuxt-header-value' }, - 'http.request.header.accept': { type: 'string', value: 'application/json, text/html' }, - 'http.request.header.x-framework': { type: 'string', value: 'Nuxt' }, - 'http.request.header.x-request-id': { type: 'string', value: 'nuxt-456' }, + 'http.request.header.user-agent': { type: 'array', value: ['Custom-Nuxt-Agent/3.0'] }, + 'http.request.header.content-type': { type: 'array', value: ['application/json'] }, + 'http.request.header.x-nuxt-test': { type: 'array', value: ['nuxt-header-value'] }, + 'http.request.header.accept': { type: 'array', value: ['application/json, text/html'] }, + 'http.request.header.x-framework': { type: 'array', value: ['Nuxt'] }, + 'http.request.header.x-request-id': { type: 'array', value: ['nuxt-456'] }, }); }); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-kit-tracing/tests/tracing.server.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2-kit-tracing/tests/tracing.server.test.ts index 4c625738eeca..1f3f4b93eab8 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2-kit-tracing/tests/tracing.server.test.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2-kit-tracing/tests/tracing.server.test.ts @@ -20,8 +20,8 @@ test('server pageload request span has nested request span for sub request', asy 'http.route': { value: '/server-load-fetch', type: 'string' }, 'sveltekit.tracing.original_name': { value: 'sveltekit.handle.root', type: 'string' }, 'url.full': { value: 'http://localhost:3030/server-load-fetch', type: 'string' }, - 'http.request.header.accept': { value: expect.any(String), type: 'string' }, - 'http.request.header.user-agent': { value: expect.any(String), type: 'string' }, + 'http.request.header.accept': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' }, }); const spans = getSegmentChildSpans(serverTraceSpans, serverSpan); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-static/tests/performance.server.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2-static/tests/performance.server.test.ts index 1fa63c4f5408..6eb052e8d11d 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2-static/tests/performance.server.test.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2-static/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/sveltekit-2-svelte-5/tests/performance.server.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2-svelte-5/tests/performance.server.test.ts index 2bf341ee709b..a0d31e53d3e9 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2-svelte-5/tests/performance.server.test.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2-svelte-5/tests/performance.server.test.ts @@ -15,8 +15,8 @@ test('server pageload request span has nested request span for sub request', asy 'sentry.segment.name.source': { value: 'route', type: 'string' }, 'http.request.method': { value: 'GET', type: 'string' }, 'url.path': { value: '/server-load-fetch', type: 'string' }, - 'http.request.header.accept': { value: expect.any(String), type: 'string' }, - 'http.request.header.user-agent': { value: expect.any(String), type: 'string' }, + 'http.request.header.accept': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' }, }); expect(serverTraceSpans).toEqual( 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 1818cb311dd7..fd6565d07f21 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 @@ -52,11 +52,11 @@ test('extracts HTTP request headers as span attributes', async ({ baseURL }) => const serverSpan = await serverSpanPromise; expect(serverSpan.attributes).toMatchObject({ - 'http.request.header.user-agent': { value: 'Custom-SvelteKit-Agent/1.0', type: 'string' }, - 'http.request.header.content-type': { value: 'application/json', type: 'string' }, - 'http.request.header.x-test-header': { value: 'sveltekit-test-value', type: 'string' }, - 'http.request.header.accept': { value: 'application/json', type: 'string' }, - 'http.request.header.x-framework': { value: 'SvelteKit', type: 'string' }, - 'http.request.header.x-request-id': { value: 'sveltekit-123', type: 'string' }, + 'http.request.header.user-agent': { value: ['Custom-SvelteKit-Agent/1.0'], type: 'array' }, + 'http.request.header.content-type': { value: ['application/json'], type: 'array' }, + 'http.request.header.x-test-header': { value: ['sveltekit-test-value'], type: 'array' }, + 'http.request.header.accept': { value: ['application/json'], type: 'array' }, + 'http.request.header.x-framework': { value: ['SvelteKit'], type: 'array' }, + 'http.request.header.x-request-id': { value: ['sveltekit-123'], type: 'array' }, }); }); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-3/tests/tracing.server.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit-3/tests/tracing.server.test.ts index 0ca667dda716..995f766a619d 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-3/tests/tracing.server.test.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit-3/tests/tracing.server.test.ts @@ -20,8 +20,8 @@ test('server pageload request span has nested request span for sub request', asy 'http.route': { value: '/server-load-fetch', type: 'string' }, 'sveltekit.tracing.original_name': { value: 'sveltekit.handle.root', type: 'string' }, 'url.full': { value: 'https://localhost:3030/server-load-fetch', type: 'string' }, - 'http.request.header.accept': { value: expect.any(String), type: 'string' }, - 'http.request.header.user-agent': { value: expect.any(String), type: 'string' }, + 'http.request.header.accept': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' }, }); const spans = getSegmentChildSpans(serverTraceSpans, serverSpan); diff --git a/dev-packages/e2e-tests/test-applications/tsx-express/tests/spans.test.ts b/dev-packages/e2e-tests/test-applications/tsx-express/tests/spans.test.ts index 704d6e00bf83..fc05bef83de0 100644 --- a/dev-packages/e2e-tests/test-applications/tsx-express/tests/spans.test.ts +++ b/dev-packages/e2e-tests/test-applications/tsx-express/tests/spans.test.ts @@ -44,13 +44,13 @@ test('Sends an API route span', async ({ baseURL }) => { 'server.port': { value: 3030, type: 'integer' }, 'http.response.status_text': { value: 'OK', type: 'string' }, 'http.route': { value: '/test-transaction', type: 'string' }, - 'http.request.header.accept': { value: '*/*', type: 'string' }, - 'http.request.header.accept-encoding': { value: 'gzip, deflate', type: 'string' }, - 'http.request.header.accept-language': { value: '*', type: 'string' }, - 'http.request.header.connection': { value: 'keep-alive', type: 'string' }, - 'http.request.header.host': { value: expect.any(String), type: 'string' }, - 'http.request.header.sec-fetch-mode': { value: 'cors', type: 'string' }, - 'http.request.header.user-agent': { value: 'node', type: 'string' }, + 'http.request.header.accept': { value: ['*/*'], type: 'array' }, + 'http.request.header.accept-encoding': { value: ['gzip, deflate'], type: 'array' }, + 'http.request.header.accept-language': { value: ['*'], type: 'array' }, + 'http.request.header.connection': { value: ['keep-alive'], type: 'array' }, + 'http.request.header.host': { value: [expect.any(String)], type: 'array' }, + 'http.request.header.sec-fetch-mode': { value: ['cors'], type: 'array' }, + 'http.request.header.user-agent': { value: ['node'], type: 'array' }, }), }), ); 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 a769453b1718..76c62fdf7bd3 100644 --- a/dev-packages/node-integration-tests/suites/tracing/httpIntegration/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/httpIntegration/test.ts @@ -5,13 +5,13 @@ import { cleanupChildProcesses, createEsmAndCjsTests, createRunner } from '../.. 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'], }; } @@ -160,8 +160,8 @@ describe('httpIntegration', () => { 'sentry.segment.name.source': 'route', [URL_FULL]: `http://localhost:${port}/test?a=1&b=2`, [URL_PATH]: '/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/dev-packages/node-integration-tests/suites/tracing/requestData-streamed/test.ts b/dev-packages/node-integration-tests/suites/tracing/requestData-streamed/test.ts index 4c341f69902b..5a356e3d91e9 100644 --- a/dev-packages/node-integration-tests/suites/tracing/requestData-streamed/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/requestData-streamed/test.ts @@ -31,8 +31,8 @@ describe('requestData-streamed', () => { }); expect(serverSpan?.attributes['http.request.header.host']).toEqual({ - type: 'string', - value: expect.any(String), + type: 'array', + value: [expect.any(String)], }); expect(serverSpan?.attributes['user.ip_address']).toEqual({ @@ -74,8 +74,8 @@ describe('requestData-streamed', () => { }); expect(serverSpan?.attributes['http.request.header.host']).toEqual({ - type: 'string', - value: expect.any(String), + type: 'array', + value: [expect.any(String)], }); expect(serverSpan?.attributes['user.ip_address']).toEqual({ @@ -106,8 +106,8 @@ describe('requestData-streamed', () => { expect(serverSpan).toBeDefined(); expect(serverSpan?.attributes['http.request.header.host']).toEqual({ - type: 'string', - value: expect.any(String), + type: 'array', + value: [expect.any(String)], }); expect(serverSpan?.attributes['user.ip_address']).toBeUndefined(); diff --git a/packages/browser/src/integrations/httpcontext.ts b/packages/browser/src/integrations/httpcontext.ts index 8f4c2f957bf8..227c4c375d3c 100644 --- a/packages/browser/src/integrations/httpcontext.ts +++ b/packages/browser/src/integrations/httpcontext.ts @@ -50,6 +50,7 @@ export const httpContextIntegration = defineIntegration(() => { reqData.headers, client.getDataCollectionOptions().httpHeaders.request, ); + const referer = headers['Referer']; safeSetSpanJSONAttributes(span, { // This attribute is used by the "Filter out events from legacy browsers and crawlers" features on the Sentry backend. @@ -61,7 +62,7 @@ export const httpContextIntegration = defineIntegration(() => { // Coerce empty string to undefined so the helper's nullish check drops it, // rather than writing an empty `url.full` attribute onto the span. [URL_FULL]: span.attributes?.[SENTRY_OP] !== 'http.client' ? filterCollectedUrl(reqData.url) : undefined, - [`${HTTP_REQUEST_HEADER_KEY_BASE}.referer`]: headers['Referer'], + [`${HTTP_REQUEST_HEADER_KEY_BASE}.referer`]: referer ? [referer] : undefined, }), }); }, diff --git a/packages/browser/test/integrations/httpcontext.test.ts b/packages/browser/test/integrations/httpcontext.test.ts index a571becfef0a..ff1894c4f521 100644 --- a/packages/browser/test/integrations/httpcontext.test.ts +++ b/packages/browser/test/integrations/httpcontext.test.ts @@ -40,7 +40,7 @@ describe('httpContextIntegration', () => { expect(span.attributes).not.toHaveProperty('url.full'); expect(span.attributes).toEqual({ [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.client', - 'http.request.header.referer': 'https://example.com', + 'http.request.header.referer': ['https://example.com'], 'user_agent.original': USER_AGENT, }); }); @@ -61,7 +61,7 @@ describe('httpContextIntegration', () => { expect(span.attributes).toEqual({ [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', - 'http.request.header.referer': 'https://example.com', + 'http.request.header.referer': ['https://example.com'], 'user_agent.original': USER_AGENT, 'url.full': 'https://example.com', }); @@ -183,7 +183,7 @@ describe('httpContextIntegration', () => { expect(span.attributes).toEqual({ [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', 'url.full': 'https://example.com', - 'http.request.header.referer': '[Filtered]', + 'http.request.header.referer': ['[Filtered]'], 'user_agent.original': USER_AGENT, }); }); diff --git a/packages/bun/test/integrations/bunserver.test.ts b/packages/bun/test/integrations/bunserver.test.ts index b96d5f5d68c8..7414958c94c9 100644 --- a/packages/bun/test/integrations/bunserver.test.ts +++ b/packages/bun/test/integrations/bunserver.test.ts @@ -69,11 +69,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')], }), name: 'GET', }, @@ -81,7 +81,7 @@ describe('Bun Serve Integration', () => { ); expect(setAttributesSpy).toHaveBeenCalledWith({ - 'http.response.header.x-custom': 'value', + 'http.response.header.x-custom': ['value'], }); }); @@ -112,12 +112,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')], }), name: 'POST', }, @@ -232,16 +232,16 @@ 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.baggage': expect.any(String), - 'http.request.header.sentry-trace': 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)], + 'http.request.header.baggage': [expect.any(String)], + 'http.request.header.sentry-trace': [expect.any(String)], }), name: 'POST', }), @@ -538,7 +538,7 @@ describe('Bun Serve Integration', () => { expect(startSpanSpy).toHaveBeenCalledTimes(1); const attributes = startSpanSpy.mock.calls[0]?.[0]?.attributes; - expect(attributes?.['http.request.header.x-forwarded-for']).toBe('203.0.113.7'); + expect(attributes?.['http.request.header.x-forwarded-for']).toEqual(['203.0.113.7']); }); test('filters request headers according to the dataCollection deny list', async () => { @@ -561,8 +561,8 @@ describe('Bun Serve Integration', () => { expect(startSpanSpy).toHaveBeenCalledTimes(1); const attributes = startSpanSpy.mock.calls[0]?.[0]?.attributes; - expect(attributes?.['http.request.header.x-internal']).toBe('[Filtered]'); - expect(attributes?.['http.request.header.x-public']).toBe('public-value'); + expect(attributes?.['http.request.header.x-internal']).toEqual(['[Filtered]']); + expect(attributes?.['http.request.header.x-public']).toEqual(['public-value']); }); test('filters always-sensitive request headers even when collection is permissive', async () => { @@ -583,7 +583,7 @@ describe('Bun Serve Integration', () => { expect(startSpanSpy).toHaveBeenCalledTimes(1); const attributes = startSpanSpy.mock.calls[0]?.[0]?.attributes; - expect(attributes?.['http.request.header.authorization']).toBe('[Filtered]'); + expect(attributes?.['http.request.header.authorization']).toEqual(['[Filtered]']); }); test('applies the dataCollection response header collection behavior', async () => { @@ -604,8 +604,8 @@ describe('Bun Serve Integration', () => { expect(setAttributesSpy).toHaveBeenCalledTimes(1); const responseAttributes = setAttributesSpy.mock.calls[0]?.[0]; - expect(responseAttributes?.['http.response.header.x-internal']).toBe('[Filtered]'); - expect(responseAttributes?.['http.response.header.x-public']).toBe('public-value'); + expect(responseAttributes?.['http.response.header.x-internal']).toEqual(['[Filtered]']); + expect(responseAttributes?.['http.response.header.x-public']).toEqual(['public-value']); }); }); }); diff --git a/packages/cloudflare/test/request.test.ts b/packages/cloudflare/test/request.test.ts index 95d482a6fd3f..052147f84bc4 100644 --- a/packages/cloudflare/test/request.test.ts +++ b/packages/cloudflare/test/request.test.ts @@ -637,7 +637,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 3911b97678ec..a02d36547736 100644 --- a/packages/core/src/utils/request.ts +++ b/packages/core/src/utils/request.ts @@ -7,7 +7,7 @@ import type { RequestEventData } from '../types/request'; import type { WebFetchHeaders, WebFetchRequest } from '../types/webfetchapi'; import { debug } from './debug-logger'; import { FILTERED_VALUE, SENSITIVE_COOKIE_NAME_SNIPPETS } from './data-collection/filtering-snippets'; -import { filterKeyValueData } from './data-collection/filterKeyValueData'; +import { filterKeyValueData, shouldFilterDataKey } from './data-collection/filterKeyValueData'; import { safeUnref } from './timer'; import { getUrlQuery } from './url'; @@ -265,7 +265,7 @@ function getAbsoluteUrl({ /** * Converts incoming HTTP request or response headers to OpenTelemetry span attributes following semantic conventions. * Header names are converted to the format: http..header. - * where is the header name in lowercase. + * where is the header name in lowercase. Header values are always emitted as a string array. * * @param lifecycle - The lifecycle of the headers, either 'request' or 'response' * @@ -279,16 +279,16 @@ export function httpHeadersToSpanAttributes( headers: Record, dataCollection: ResolvedDataCollection, lifecycle: 'request' | 'response' = 'request', -): Record { +): Record { const headerBehavior = lifecycle === 'request' ? dataCollection.httpHeaders.request : dataCollection.httpHeaders.response; const cookieBehavior = dataCollection.cookies; const prefix = `http.${lifecycle}.header.`; - const spanAttributes: Record = {}; + const spanAttributes: Record = {}; try { - const regularHeaders: Record = {}; + const regularHeaders: Record = {}; for (const [key, value] of Object.entries(headers)) { if (value == null) { @@ -310,7 +310,7 @@ export function httpHeadersToSpanAttributes( spanAttributes[`${prefix}${lowerKey}.${cookieKey}`] = cookieValue; } } else { - spanAttributes[`${prefix}${lowerKey}`] = FILTERED_VALUE; + spanAttributes[`${prefix}${lowerKey}`] = [FILTERED_VALUE]; } } else { if (headerBehavior === false) { @@ -318,17 +318,18 @@ export function httpHeadersToSpanAttributes( } if (Array.isArray(value)) { - regularHeaders[lowerKey] = value.map(v => (v != null ? String(v) : v)).join(';'); + regularHeaders[lowerKey] = value.filter(v => v != null).map(v => String(v)); } else if (typeof value === 'string') { - regularHeaders[lowerKey] = value; + regularHeaders[lowerKey] = [value]; } } } if (headerBehavior !== false) { - const filtered = filterKeyValueData(regularHeaders, headerBehavior); - for (const [headerKey, headerValue] of Object.entries(filtered)) { - spanAttributes[`${prefix}${headerKey}`] = headerValue; + for (const [headerKey, headerValues] of Object.entries(regularHeaders)) { + spanAttributes[`${prefix}${headerKey}`] = shouldFilterDataKey(headerKey, headerBehavior) + ? [FILTERED_VALUE] + : headerValues; } } } catch { diff --git a/packages/core/test/lib/integrations/requestdata.test.ts b/packages/core/test/lib/integrations/requestdata.test.ts index 98ce1529e0f8..4647fb59a019 100644 --- a/packages/core/test/lib/integrations/requestdata.test.ts +++ b/packages/core/test/lib/integrations/requestdata.test.ts @@ -889,8 +889,8 @@ describe('requestDataIntegration processSegmentSpan', () => { 'url.full': 'https://example.com/api/users', 'http.request.method': 'GET', 'url.query': 'page=1&limit=10', - 'http.request.header.content-type': 'application/json', - 'http.request.header.accept': 'application/json', + 'http.request.header.content-type': ['application/json'], + 'http.request.header.accept': ['application/json'], }); }); @@ -1111,7 +1111,7 @@ describe('requestDataIntegration processSegmentSpan', () => { integration.processSegmentSpan!(span, mockClient({ userInfo: false })); expect(span.attributes).toMatchObject({ - 'http.request.header.content-type': 'application/json', + 'http.request.header.content-type': ['application/json'], }); expect(span.attributes).not.toHaveProperty('http.request.header.cookie.theme'); }); @@ -1127,7 +1127,7 @@ describe('requestDataIntegration processSegmentSpan', () => { integration.processSegmentSpan!(span, mockClient({ userInfo: false })); expect(span.attributes).toMatchObject({ - 'http.request.header.content-type': 'application/json', + 'http.request.header.content-type': ['application/json'], }); expect(span.attributes).not.toHaveProperty('http.request.header.x-forwarded-for'); expect(span.attributes).not.toHaveProperty('user.ip_address'); @@ -1177,8 +1177,8 @@ describe('requestDataIntegration processSegmentSpan', () => { integration.processSegmentSpan!(span, mockClient({ httpHeaders: { request: false, response: false } })); expect(span.attributes).toMatchObject({ - 'http.request.header.content-type': 'application/json', - 'http.request.header.accept': 'text/html', + 'http.request.header.content-type': ['application/json'], + 'http.request.header.accept': ['text/html'], }); }); @@ -1193,8 +1193,8 @@ describe('requestDataIntegration processSegmentSpan', () => { mockClient({ httpHeaders: { request: { allow: ['accept'] }, response: true } }), ); - expect(span.attributes?.['http.request.header.accept']).toBe('application/json'); - expect(span.attributes?.['http.request.header.x-request-id']).toBe('[Filtered]'); + expect(span.attributes?.['http.request.header.accept']).toEqual(['application/json']); + expect(span.attributes?.['http.request.header.x-request-id']).toEqual(['[Filtered]']); }); it('include.cookies overrides dataCollection.cookies=false on spans', () => { @@ -1289,8 +1289,8 @@ describe('requestDataIntegration userInfo collection', () => { expect(span.attributes).toMatchObject({ 'user.ip_address': '203.0.113.50', - 'http.request.header.content-type': 'application/json', - 'http.request.header.x-forwarded-for': '203.0.113.50', + 'http.request.header.content-type': ['application/json'], + 'http.request.header.x-forwarded-for': ['203.0.113.50'], }); }); }); diff --git a/packages/core/test/lib/utils/request.test.ts b/packages/core/test/lib/utils/request.test.ts index 92cdefdf04af..db2bdbe17594 100644 --- a/packages/core/test/lib/utils/request.test.ts +++ b/packages/core/test/lib/utils/request.test.ts @@ -444,7 +444,7 @@ describe('request utils', () => { expect(httpHeadersToSpanAttributes({}, resolveDataCollectionOptions({}))).toEqual({}); }); - it('converts single string header values to strings', () => { + it('wraps single string header values in an array', () => { const headers = { 'Content-Type': 'application/json', 'user-agent': 'test-agent', @@ -453,12 +453,12 @@ describe('request utils', () => { const result = httpHeadersToSpanAttributes(headers, resolveDataCollectionOptions({})); 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 by joining with semicolons', () => { + it('keeps each value of an array header value as a separate entry', () => { const headers = { 'custom-header': ['value1', 'value2'], accept: ['application/json', 'text/html'], @@ -467,12 +467,12 @@ describe('request utils', () => { const result = httpHeadersToSpanAttributes(headers, resolveDataCollectionOptions({})); 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('filters undefined values in arrays when joining', () => { + it('drops undefined values in arrays', () => { const headers = { 'undefined-values': [undefined, undefined], 'valid-header': 'valid-value', @@ -481,8 +481,8 @@ describe('request utils', () => { const result = httpHeadersToSpanAttributes(headers, resolveDataCollectionOptions({})); expect(result).toEqual({ - 'http.request.header.valid-header': 'valid-value', - 'http.request.header.undefined-values': ';', + 'http.request.header.valid-header': ['valid-value'], + 'http.request.header.undefined-values': [], }); }); @@ -495,11 +495,11 @@ describe('request utils', () => { const result = httpHeadersToSpanAttributes(headers, resolveDataCollectionOptions({})); expect(result).toEqual({ - 'http.request.header.valid-header': 'valid-value', + 'http.request.header.valid-header': ['valid-value'], }); }); - it('adds empty array headers as empty string', () => { + it('adds empty array headers as empty array', () => { const headers = { 'empty-header': [], 'valid-header': 'valid-value', @@ -508,8 +508,8 @@ describe('request utils', () => { const result = httpHeadersToSpanAttributes(headers, resolveDataCollectionOptions({})); 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'], }); }); @@ -524,10 +524,10 @@ describe('request utils', () => { const result = httpHeadersToSpanAttributes(headers, resolveDataCollectionOptions({})); 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'], }); }); @@ -547,19 +547,19 @@ describe('request utils', () => { const result = httpHeadersToSpanAttributes(headers, resolveDataCollectionOptions({})); 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 by joining with semicolons', () => { + it('handles multiple values for the same header', () => { const headers = { 'x-random-header': ['test=abc123', 'preferences=dark-mode', 'number=three'], Accept: ['application/json', 'text/html'], @@ -568,8 +568,8 @@ describe('request utils', () => { const result = httpHeadersToSpanAttributes(headers, resolveDataCollectionOptions({})); 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'], }); }); @@ -582,8 +582,8 @@ describe('request utils', () => { const result = httpHeadersToSpanAttributes(headers, resolveDataCollectionOptions({})); 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'], }); }); @@ -601,7 +601,7 @@ describe('request utils', () => { expect(result).toEqual({}); }); - it('stringifies non-string values (except null) in arrays and joins them', () => { + it('stringifies non-nullish, non-string values in arrays', () => { const headers = { 'mixed-types': ['string-value', 123, true, null], } as any; @@ -609,7 +609,7 @@ describe('request utils', () => { const result = httpHeadersToSpanAttributes(headers, resolveDataCollectionOptions({})); expect(result).toEqual({ - 'http.request.header.mixed-types': 'string-value;123;true;', + 'http.request.header.mixed-types': ['string-value', '123', 'true'], }); }); @@ -625,7 +625,7 @@ describe('request utils', () => { const result = httpHeadersToSpanAttributes(headers, resolveDataCollectionOptions({})); expect(result).toEqual({ - 'http.request.header.string-header': 'valid-value', + 'http.request.header.string-header': ['valid-value'], }); }); @@ -641,10 +641,10 @@ describe('request utils', () => { const result = httpHeadersToSpanAttributes(headers, resolveDataCollectionOptions({})); expect(result).toEqual({ - 'http.request.header.content-type': 'application/json', + 'http.request.header.content-type': ['application/json'], 'http.request.header.cookie.session': '[Filtered]', - 'http.request.header.x-api-key': '[Filtered]', - 'http.request.header.authorization': '[Filtered]', + 'http.request.header.x-api-key': ['[Filtered]'], + 'http.request.header.authorization': ['[Filtered]'], }); }); @@ -710,11 +710,11 @@ describe('request utils', () => { it('adds a filtered cookie header when cookie header is present, but has no valid key=value pairs', () => { const headers1 = { Cookie: ['key', 'val'] }; const result1 = httpHeadersToSpanAttributes(headers1, resolveDataCollectionOptions({})); - expect(result1).toEqual({ 'http.request.header.cookie': '[Filtered]' }); + expect(result1).toEqual({ 'http.request.header.cookie': ['[Filtered]'] }); const headers3 = { Cookie: '' }; const result3 = httpHeadersToSpanAttributes(headers3, resolveDataCollectionOptions({})); - expect(result3).toEqual({ 'http.request.header.cookie': '[Filtered]' }); + expect(result3).toEqual({ 'http.request.header.cookie': ['[Filtered]'] }); }); it.each([ @@ -747,23 +747,23 @@ describe('request utils', () => { }, }), expected: { - 'http.request.header.content-type': 'application/json', - 'http.request.header.user-agent': 'Mozilla/5.0', - 'http.request.header.x-user': '[Filtered]', - 'http.request.header.x-forwarded-for': '[Filtered]', - 'http.request.header.x-forwarded-host': '[Filtered]', - 'http.request.header.x-forwarded-proto': '[Filtered]', + 'http.request.header.content-type': ['application/json'], + 'http.request.header.user-agent': ['Mozilla/5.0'], + 'http.request.header.x-user': ['[Filtered]'], + 'http.request.header.x-forwarded-for': ['[Filtered]'], + 'http.request.header.x-forwarded-host': ['[Filtered]'], + 'http.request.header.x-forwarded-proto': ['[Filtered]'], }, }, { dataCollection: resolveDataCollectionOptions({}), expected: { - 'http.request.header.content-type': 'application/json', - 'http.request.header.user-agent': 'Mozilla/5.0', - 'http.request.header.x-user': 'my-personal-username', - 'http.request.header.x-forwarded-for': '192.168.1.1', - 'http.request.header.x-forwarded-host': 'example.com', - 'http.request.header.x-forwarded-proto': 'https', + 'http.request.header.content-type': ['application/json'], + 'http.request.header.user-agent': ['Mozilla/5.0'], + 'http.request.header.x-user': ['my-personal-username'], + 'http.request.header.x-forwarded-for': ['192.168.1.1'], + 'http.request.header.x-forwarded-host': ['example.com'], + 'http.request.header.x-forwarded-proto': ['https'], }, }, ])('filters PII headers according to dataCollection.httpHeaders', ({ dataCollection, expected }) => { @@ -814,31 +814,31 @@ describe('request utils', () => { // Security-sensitive headers remain redacted with permissive collection defaults. 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.authorization': '[Filtered]', + '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.authorization': ['[Filtered]'], 'http.request.header.cookie.session': '[Filtered]', 'http.request.header.set-cookie.session': '[Filtered]', - 'http.request.header.x-api-key': '[Filtered]', - 'http.request.header.x-auth-token': '[Filtered]', - 'http.request.header.x-secret': '[Filtered]', - 'http.request.header.x-secret-key': '[Filtered]', - 'http.request.header.www-authenticate': '[Filtered]', - 'http.request.header.proxy-authorization': '[Filtered]', - 'http.request.header.x-access-token': '[Filtered]', - 'http.request.header.x-csrf': '[Filtered]', - 'http.request.header.x-xsrf': '[Filtered]', - 'http.request.header.x-session-token': '[Filtered]', - 'http.request.header.x-password': '[Filtered]', - 'http.request.header.x-private-key': '[Filtered]', - 'http.request.header.x-forwarded-user': 'user', - 'http.request.header.x-forwarded-authorization': '[Filtered]', - 'http.request.header.x-jwt-token': '[Filtered]', - 'http.request.header.x-bearer-token': '[Filtered]', - 'http.request.header.x-sso-token': '[Filtered]', - 'http.request.header.x-saml-token': '[Filtered]', + 'http.request.header.x-api-key': ['[Filtered]'], + 'http.request.header.x-auth-token': ['[Filtered]'], + 'http.request.header.x-secret': ['[Filtered]'], + 'http.request.header.x-secret-key': ['[Filtered]'], + 'http.request.header.www-authenticate': ['[Filtered]'], + 'http.request.header.proxy-authorization': ['[Filtered]'], + 'http.request.header.x-access-token': ['[Filtered]'], + 'http.request.header.x-csrf': ['[Filtered]'], + 'http.request.header.x-xsrf': ['[Filtered]'], + 'http.request.header.x-session-token': ['[Filtered]'], + 'http.request.header.x-password': ['[Filtered]'], + 'http.request.header.x-private-key': ['[Filtered]'], + 'http.request.header.x-forwarded-user': ['user'], + 'http.request.header.x-forwarded-authorization': ['[Filtered]'], + 'http.request.header.x-jwt-token': ['[Filtered]'], + 'http.request.header.x-bearer-token': ['[Filtered]'], + 'http.request.header.x-sso-token': ['[Filtered]'], + 'http.request.header.x-saml-token': ['[Filtered]'], }); }); @@ -864,19 +864,19 @@ describe('request utils', () => { const result = httpHeadersToSpanAttributes(headers, resolveDataCollectionOptions({}), 'response'); expect(result).toEqual({ - 'http.response.header.host': 'example.com', - 'http.response.header.user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', - 'http.response.header.accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', - 'http.response.header.accept-language': 'en-US,en;q=0.5', - 'http.response.header.accept-encoding': 'gzip, deflate', - 'http.response.header.connection': 'keep-alive', - 'http.response.header.upgrade-insecure-requests': '1', - 'http.response.header.cache-control': 'no-cache', - 'http.response.header.x-forwarded-for': '192.168.1.1', - 'http.response.header.authorization': '[Filtered]', - 'http.response.header.x-bearer-token': '[Filtered]', - 'http.response.header.x-saml-token': '[Filtered]', - 'http.response.header.x-sso-token': '[Filtered]', + 'http.response.header.host': ['example.com'], + 'http.response.header.user-agent': ['Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'], + 'http.response.header.accept': ['text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'], + 'http.response.header.accept-language': ['en-US,en;q=0.5'], + 'http.response.header.accept-encoding': ['gzip, deflate'], + 'http.response.header.connection': ['keep-alive'], + 'http.response.header.upgrade-insecure-requests': ['1'], + 'http.response.header.cache-control': ['no-cache'], + 'http.response.header.x-forwarded-for': ['192.168.1.1'], + 'http.response.header.authorization': ['[Filtered]'], + 'http.response.header.x-bearer-token': ['[Filtered]'], + 'http.response.header.x-saml-token': ['[Filtered]'], + 'http.response.header.x-sso-token': ['[Filtered]'], 'http.response.header.set-cookie.session': '[Filtered]', 'http.response.header.cookie.session': '[Filtered]', }); @@ -916,10 +916,10 @@ describe('request utils', () => { const result = httpHeadersToSpanAttributes(headers, dc); expect(result).toEqual({ - 'http.request.header.content-type': 'application/json', - 'http.request.header.x-request-id': 'abc-123', - 'http.request.header.x-trace-id': '[Filtered]', - 'http.request.header.user-agent': '[Filtered]', + 'http.request.header.content-type': ['application/json'], + 'http.request.header.x-request-id': ['abc-123'], + 'http.request.header.x-trace-id': ['[Filtered]'], + 'http.request.header.user-agent': ['[Filtered]'], }); }); @@ -942,10 +942,10 @@ describe('request utils', () => { const result = httpHeadersToSpanAttributes(headers, dc); expect(result).toEqual({ - 'http.request.header.content-type': 'application/json', - 'http.request.header.x-custom-secret': '[Filtered]', - 'http.request.header.x-forwarded-for': '192.168.1.1', - 'http.request.header.accept': 'text/html', + 'http.request.header.content-type': ['application/json'], + 'http.request.header.x-custom-secret': ['[Filtered]'], + 'http.request.header.x-forwarded-for': ['192.168.1.1'], + 'http.request.header.accept': ['text/html'], }); }); @@ -982,7 +982,7 @@ describe('request utils', () => { const result = httpHeadersToSpanAttributes(headers, dc); expect(result).toEqual({ - 'http.request.header.content-type': 'application/json', + 'http.request.header.content-type': ['application/json'], }); }); @@ -1022,8 +1022,8 @@ describe('request utils', () => { const result = httpHeadersToSpanAttributes(headers, dc, 'response'); expect(result).toEqual({ - 'http.response.header.content-type': 'application/json', - 'http.response.header.x-custom': '[Filtered]', + 'http.response.header.content-type': ['application/json'], + 'http.response.header.x-custom': ['[Filtered]'], }); }); @@ -1044,8 +1044,8 @@ describe('request utils', () => { const result = httpHeadersToSpanAttributes(headers, dc); expect(result).toEqual({ - 'http.request.header.authorization': '[Filtered]', - 'http.request.header.x-request-id': 'abc-123', + 'http.request.header.authorization': ['[Filtered]'], + 'http.request.header.x-request-id': ['abc-123'], }); }); }); diff --git a/packages/deno/test/deno-serve.test.ts b/packages/deno/test/deno-serve.test.ts index f56fca764b99..933aded91ee8 100644 --- a/packages/deno/test/deno-serve.test.ts +++ b/packages/deno/test/deno-serve.test.ts @@ -513,8 +513,8 @@ Deno.test('Deno.serve should capture request headers and set response context', // Check response context assertEquals(transaction?.contexts?.response?.status_code, 201); - assertEquals(transaction?.contexts?.trace?.data?.['http.response.header.content-type'], 'text/plain'); - assertEquals(transaction?.contexts?.trace?.data?.['http.response.header.x-custom-header'], 'test'); + assertEquals(transaction?.contexts?.trace?.data?.['http.response.header.content-type'], ['text/plain']); + assertEquals(transaction?.contexts?.trace?.data?.['http.response.header.x-custom-header'], ['test']); }); Deno.test('Deno.serve should capture client address and port by default', async () => { @@ -621,7 +621,7 @@ Deno.test('Deno.serve should keep PII request headers by default', async () => { assertEquals(transactionEvents.length, 1); const [transaction] = transactionEvents; - assertEquals(transaction?.contexts?.trace?.data?.['http.request.header.x-forwarded-for'], '203.0.113.7'); + assertEquals(transaction?.contexts?.trace?.data?.['http.request.header.x-forwarded-for'], ['203.0.113.7']); }); Deno.test('Deno.serve should filter PII request headers when configured', async () => { @@ -658,7 +658,7 @@ Deno.test('Deno.serve should filter PII request headers when configured', async assertEquals(transactionEvents.length, 1); const [transaction] = transactionEvents; - assertEquals(transaction?.contexts?.trace?.data?.['http.request.header.x-forwarded-for'], '[Filtered]'); + assertEquals(transaction?.contexts?.trace?.data?.['http.request.header.x-forwarded-for'], ['[Filtered]']); }); Deno.test('Deno.serve should support distributed tracing with sentry-trace header', async () => { diff --git a/packages/nextjs/src/common/utils/addHeadersAsAttributes.ts b/packages/nextjs/src/common/utils/addHeadersAsAttributes.ts index d61e6ab94e8e..64c64cd84f52 100644 --- a/packages/nextjs/src/common/utils/addHeadersAsAttributes.ts +++ b/packages/nextjs/src/common/utils/addHeadersAsAttributes.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/test/utils/addHeadersAsAttributes.test.ts b/packages/nextjs/test/utils/addHeadersAsAttributes.test.ts index d122cd9ef2dc..7765b49ce1a6 100644 --- a/packages/nextjs/test/utils/addHeadersAsAttributes.test.ts +++ b/packages/nextjs/test/utils/addHeadersAsAttributes.test.ts @@ -31,8 +31,8 @@ describe('addHeadersAsAttributes', () => { }); expect(result).toMatchObject({ - 'http.request.header.content-type': 'application/json', - 'http.request.header.x-forwarded-for': '127.0.0.1', + 'http.request.header.content-type': ['application/json'], + 'http.request.header.x-forwarded-for': ['127.0.0.1'], }); }); @@ -48,7 +48,7 @@ describe('addHeadersAsAttributes', () => { 'x-forwarded-for': '127.0.0.1', }); - expect(result['http.request.header.content-type']).toBe('application/json'); - expect(result['http.request.header.x-forwarded-for']).toBe('[Filtered]'); + expect(result['http.request.header.content-type']).toEqual(['application/json']); + expect(result['http.request.header.x-forwarded-for']).toEqual(['[Filtered]']); }); }); diff --git a/packages/nuxt/test/runtime/hooks/wrapMiddlewareHandler.test.ts b/packages/nuxt/test/runtime/hooks/wrapMiddlewareHandler.test.ts index 0507fe631d5a..d7fb10fe2eef 100644 --- a/packages/nuxt/test/runtime/hooks/wrapMiddlewareHandler.test.ts +++ b/packages/nuxt/test/runtime/hooks/wrapMiddlewareHandler.test.ts @@ -62,7 +62,9 @@ describe('wrapMiddlewareHandlerWithSentry', () => { frameContextLines: 5, }), }); - (SentryCore.httpHeadersToSpanAttributes as any).mockReturnValue({ 'http.request.header.user-agent': 'test-agent' }); + (SentryCore.httpHeadersToSpanAttributes as any).mockReturnValue({ + 'http.request.header.user-agent': ['test-agent'], + }); (SentryCoreServer.flushIfServerless as any).mockResolvedValue(undefined); });