Skip to content

Commit 3fa7406

Browse files
Lms24claude
andcommitted
fix(core): Set http header span attributes as string arrays
`httpHeadersToSpanAttributes` now writes `http.request.header.<key>` / `http.response.header.<key>` as a string array instead of a single string. Headers sent multiple times previously got their values joined with `;`, which lost the boundaries between values; each value is now its own array entry. This matches the Sentry/OTel semantic conventions and the shape the outgoing-fetch (undici) instrumentation already emits for the same attributes. `packages/browser`'s `httpContext` integration is updated too, so `http.request.header.referer` isn't the one header attribute left as a string. Cookie attributes (`http.request.header.cookie.<name>`) stay single strings, as a cookie only ever has one value. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 13df712 commit 3fa7406

61 files changed

Lines changed: 675 additions & 661 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

MIGRATION.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -798,6 +798,8 @@ On server-side HTTP spans, the `content-length` header is now always reported as
798798

799799
The `http.request.header.<key>`/`http.response.header.<key>` 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.<name>` and `http.request.header.set-cookie.<name>`.
800800

801+
Furthermore, the values of `http.request.header.<key>`/`http.response.header.<key>` 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 yield 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.<name>`/`http.request.header.set-cookie.<name>`) continue to hold a single string, since a cookie only ever has one value.
802+
801803
#### Network attributes
802804

803805
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:

dev-packages/browser-integration-tests/suites/integrations/httpContext-streamed/test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ sentryTest('httpContextIntegration captures url, user-agent, and referer', async
2222
value: expect.any(String),
2323
});
2424
expect(pageloadSpan!.attributes['http.request.header.referer']).toEqual({
25-
type: 'string',
26-
value: 'https://sentry.io/',
25+
type: 'array',
26+
value: ['https://sentry.io/'],
2727
});
2828
});
2929

dev-packages/cloudflare-integration-tests/suites/public-api/startSpan-streamed/test.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -226,36 +226,36 @@ it('sends a streamed span envelope with correct spans for a manually started spa
226226
value: 'node',
227227
},
228228
'http.request.header.accept': {
229-
type: 'string',
230-
value: '*/*',
229+
type: 'array',
230+
value: ['*/*'],
231231
},
232232
'http.request.header.accept-encoding': {
233-
type: 'string',
234-
value: 'br, gzip',
233+
type: 'array',
234+
value: ['br, gzip'],
235235
},
236236
'http.request.header.accept-language': {
237-
type: 'string',
238-
value: '*',
237+
type: 'array',
238+
value: ['*'],
239239
},
240240
'http.request.header.cf-connecting-ip': {
241-
type: 'string',
242-
value: '127.0.0.1',
241+
type: 'array',
242+
value: ['127.0.0.1'],
243243
},
244244
'user.ip_address': {
245245
type: 'string',
246246
value: '127.0.0.1',
247247
},
248248
'http.request.header.host': {
249-
type: 'string',
250-
value: expect.stringMatching(/^localhost:.+$/),
249+
type: 'array',
250+
value: [expect.stringMatching(/^localhost:.+$/)],
251251
},
252252
'http.request.header.sec-fetch-mode': {
253-
type: 'string',
254-
value: 'cors',
253+
type: 'array',
254+
value: ['cors'],
255255
},
256256
'http.request.header.user-agent': {
257-
type: 'string',
258-
value: 'node',
257+
type: 'array',
258+
value: ['node'],
259259
},
260260
'http.request.method': {
261261
type: 'string',

dev-packages/e2e-tests/test-applications/astro-4/tests/tracing.dynamic.test.ts

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,11 @@ test.describe('tracing in dynamically rendered (ssr) routes', () => {
6161
'sentry.sdk.name': { value: 'sentry.javascript.astro', type: 'string' },
6262
'url.full': { value: expect.stringContaining('/test-ssr'), type: 'string' },
6363
// demonstrates that the request data integration can extract headers
64-
'http.request.header.accept': { value: expect.any(String), type: 'string' },
65-
'http.request.header.accept-encoding': { value: 'gzip, deflate, br, zstd', type: 'string' },
66-
'http.request.header.accept-language': { value: 'en-US', type: 'string' },
67-
'http.request.header.sec-fetch-mode': { value: 'navigate', type: 'string' },
68-
'http.request.header.user-agent': { value: expect.any(String), type: 'string' },
64+
'http.request.header.accept': { value: [expect.any(String)], type: 'array' },
65+
'http.request.header.accept-encoding': { value: ['gzip, deflate, br, zstd'], type: 'array' },
66+
'http.request.header.accept-language': { value: ['en-US'], type: 'array' },
67+
'http.request.header.sec-fetch-mode': { value: ['navigate'], type: 'array' },
68+
'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' },
6969
});
7070
});
7171
});
@@ -152,11 +152,11 @@ test.describe('nested SSR routes (client, server, server request)', () => {
152152
'sentry.origin': { value: 'auto.http.astro', type: 'string' },
153153
'sentry.segment.name.source': { value: 'route', type: 'string' },
154154
'url.full': { value: expect.stringContaining('/user-page/myUsername123'), type: 'string' },
155-
'http.request.header.accept': { value: expect.any(String), type: 'string' },
156-
'http.request.header.accept-encoding': { value: 'gzip, deflate, br, zstd', type: 'string' },
157-
'http.request.header.accept-language': { value: 'en-US', type: 'string' },
158-
'http.request.header.sec-fetch-mode': { value: 'navigate', type: 'string' },
159-
'http.request.header.user-agent': { value: expect.any(String), type: 'string' },
155+
'http.request.header.accept': { value: [expect.any(String)], type: 'array' },
156+
'http.request.header.accept-encoding': { value: ['gzip, deflate, br, zstd'], type: 'array' },
157+
'http.request.header.accept-language': { value: ['en-US'], type: 'array' },
158+
'http.request.header.sec-fetch-mode': { value: ['navigate'], type: 'array' },
159+
'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' },
160160
});
161161

162162
// 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)', () => {
174174
'sentry.op': { value: 'http.server', type: 'string' },
175175
'sentry.origin': { value: 'auto.http.astro', type: 'string' },
176176
'sentry.segment.name.source': { value: 'route', type: 'string' },
177-
'http.request.header.accept': { value: expect.any(String), type: 'string' },
178-
'http.request.header.accept-encoding': { value: 'gzip, deflate', type: 'string' },
179-
'http.request.header.accept-language': { value: '*', type: 'string' },
180-
'http.request.header.sec-fetch-mode': { value: 'cors', type: 'string' },
181-
'http.request.header.user-agent': { value: expect.any(String), type: 'string' },
177+
'http.request.header.accept': { value: [expect.any(String)], type: 'array' },
178+
'http.request.header.accept-encoding': { value: ['gzip, deflate'], type: 'array' },
179+
'http.request.header.accept-language': { value: ['*'], type: 'array' },
180+
'http.request.header.sec-fetch-mode': { value: ['cors'], type: 'array' },
181+
'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' },
182182
});
183183
});
184184

@@ -215,11 +215,11 @@ test.describe('nested SSR routes (client, server, server request)', () => {
215215
'sentry.origin': { value: 'auto.http.astro', type: 'string' },
216216
'sentry.segment.name.source': { value: 'route', type: 'string' },
217217
'url.full': { value: expect.stringContaining('/catchAll/hell0/whatever-do'), type: 'string' },
218-
'http.request.header.accept': { value: expect.any(String), type: 'string' },
219-
'http.request.header.accept-encoding': { value: 'gzip, deflate, br, zstd', type: 'string' },
220-
'http.request.header.accept-language': { value: 'en-US', type: 'string' },
221-
'http.request.header.sec-fetch-mode': { value: 'navigate', type: 'string' },
222-
'http.request.header.user-agent': { value: expect.any(String), type: 'string' },
218+
'http.request.header.accept': { value: [expect.any(String)], type: 'array' },
219+
'http.request.header.accept-encoding': { value: ['gzip, deflate, br, zstd'], type: 'array' },
220+
'http.request.header.accept-language': { value: ['en-US'], type: 'array' },
221+
'http.request.header.sec-fetch-mode': { value: ['navigate'], type: 'array' },
222+
'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' },
223223
});
224224
});
225225
});
@@ -253,11 +253,11 @@ test.describe('parametrized vs static paths', () => {
253253
'sentry.origin': { value: 'auto.http.astro', type: 'string' },
254254
'sentry.segment.name.source': { value: 'route', type: 'string' },
255255
'url.full': { value: expect.stringContaining('/user-page/settings'), type: 'string' },
256-
'http.request.header.accept': { value: expect.any(String), type: 'string' },
257-
'http.request.header.accept-encoding': { value: 'gzip, deflate, br, zstd', type: 'string' },
258-
'http.request.header.accept-language': { value: 'en-US', type: 'string' },
259-
'http.request.header.sec-fetch-mode': { value: 'navigate', type: 'string' },
260-
'http.request.header.user-agent': { value: expect.any(String), type: 'string' },
256+
'http.request.header.accept': { value: [expect.any(String)], type: 'array' },
257+
'http.request.header.accept-encoding': { value: ['gzip, deflate, br, zstd'], type: 'array' },
258+
'http.request.header.accept-language': { value: ['en-US'], type: 'array' },
259+
'http.request.header.sec-fetch-mode': { value: ['navigate'], type: 'array' },
260+
'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' },
261261
});
262262
});
263263
});

dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.dynamic.test.ts

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,11 @@ test.describe('tracing in dynamically rendered (ssr) routes', () => {
6161
'sentry.sdk.name': { value: 'sentry.javascript.astro', type: 'string' },
6262
'url.full': { value: expect.stringContaining('/test-ssr'), type: 'string' },
6363
// demonstrates that the request data integration can extract headers
64-
'http.request.header.accept': { value: expect.any(String), type: 'string' },
65-
'http.request.header.accept-encoding': { value: 'gzip, deflate, br, zstd', type: 'string' },
66-
'http.request.header.accept-language': { value: 'en-US', type: 'string' },
67-
'http.request.header.sec-fetch-mode': { value: 'navigate', type: 'string' },
68-
'http.request.header.user-agent': { value: expect.any(String), type: 'string' },
64+
'http.request.header.accept': { value: [expect.any(String)], type: 'array' },
65+
'http.request.header.accept-encoding': { value: ['gzip, deflate, br, zstd'], type: 'array' },
66+
'http.request.header.accept-language': { value: ['en-US'], type: 'array' },
67+
'http.request.header.sec-fetch-mode': { value: ['navigate'], type: 'array' },
68+
'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' },
6969
});
7070
});
7171
});
@@ -152,11 +152,11 @@ test.describe('nested SSR routes (client, server, server request)', () => {
152152
'sentry.origin': { value: 'auto.http.astro', type: 'string' },
153153
'sentry.segment.name.source': { value: 'route', type: 'string' },
154154
'url.full': { value: expect.stringContaining('/user-page/myUsername123'), type: 'string' },
155-
'http.request.header.accept': { value: expect.any(String), type: 'string' },
156-
'http.request.header.accept-encoding': { value: 'gzip, deflate, br, zstd', type: 'string' },
157-
'http.request.header.accept-language': { value: 'en-US', type: 'string' },
158-
'http.request.header.sec-fetch-mode': { value: 'navigate', type: 'string' },
159-
'http.request.header.user-agent': { value: expect.any(String), type: 'string' },
155+
'http.request.header.accept': { value: [expect.any(String)], type: 'array' },
156+
'http.request.header.accept-encoding': { value: ['gzip, deflate, br, zstd'], type: 'array' },
157+
'http.request.header.accept-language': { value: ['en-US'], type: 'array' },
158+
'http.request.header.sec-fetch-mode': { value: ['navigate'], type: 'array' },
159+
'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' },
160160
});
161161

162162
// 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)', () => {
174174
'sentry.op': { value: 'http.server', type: 'string' },
175175
'sentry.origin': { value: 'auto.http.astro', type: 'string' },
176176
'sentry.segment.name.source': { value: 'route', type: 'string' },
177-
'http.request.header.accept': { value: expect.any(String), type: 'string' },
178-
'http.request.header.accept-encoding': { value: 'gzip, deflate', type: 'string' },
179-
'http.request.header.accept-language': { value: '*', type: 'string' },
180-
'http.request.header.sec-fetch-mode': { value: 'cors', type: 'string' },
181-
'http.request.header.user-agent': { value: expect.any(String), type: 'string' },
177+
'http.request.header.accept': { value: [expect.any(String)], type: 'array' },
178+
'http.request.header.accept-encoding': { value: ['gzip, deflate'], type: 'array' },
179+
'http.request.header.accept-language': { value: ['*'], type: 'array' },
180+
'http.request.header.sec-fetch-mode': { value: ['cors'], type: 'array' },
181+
'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' },
182182
});
183183
});
184184

@@ -215,11 +215,11 @@ test.describe('nested SSR routes (client, server, server request)', () => {
215215
'sentry.origin': { value: 'auto.http.astro', type: 'string' },
216216
'sentry.segment.name.source': { value: 'route', type: 'string' },
217217
'url.full': { value: expect.stringContaining('/catchAll/hell0/whatever-do'), type: 'string' },
218-
'http.request.header.accept': { value: expect.any(String), type: 'string' },
219-
'http.request.header.accept-encoding': { value: 'gzip, deflate, br, zstd', type: 'string' },
220-
'http.request.header.accept-language': { value: 'en-US', type: 'string' },
221-
'http.request.header.sec-fetch-mode': { value: 'navigate', type: 'string' },
222-
'http.request.header.user-agent': { value: expect.any(String), type: 'string' },
218+
'http.request.header.accept': { value: [expect.any(String)], type: 'array' },
219+
'http.request.header.accept-encoding': { value: ['gzip, deflate, br, zstd'], type: 'array' },
220+
'http.request.header.accept-language': { value: ['en-US'], type: 'array' },
221+
'http.request.header.sec-fetch-mode': { value: ['navigate'], type: 'array' },
222+
'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' },
223223
});
224224
});
225225
});
@@ -253,11 +253,11 @@ test.describe('parametrized vs static paths', () => {
253253
'sentry.origin': { value: 'auto.http.astro', type: 'string' },
254254
'sentry.segment.name.source': { value: 'route', type: 'string' },
255255
'url.full': { value: expect.stringContaining('/user-page/settings'), type: 'string' },
256-
'http.request.header.accept': { value: expect.any(String), type: 'string' },
257-
'http.request.header.accept-encoding': { value: 'gzip, deflate, br, zstd', type: 'string' },
258-
'http.request.header.accept-language': { value: 'en-US', type: 'string' },
259-
'http.request.header.sec-fetch-mode': { value: 'navigate', type: 'string' },
260-
'http.request.header.user-agent': { value: expect.any(String), type: 'string' },
256+
'http.request.header.accept': { value: [expect.any(String)], type: 'array' },
257+
'http.request.header.accept-encoding': { value: ['gzip, deflate, br, zstd'], type: 'array' },
258+
'http.request.header.accept-language': { value: ['en-US'], type: 'array' },
259+
'http.request.header.sec-fetch-mode': { value: ['navigate'], type: 'array' },
260+
'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' },
261261
});
262262
});
263263

dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.serverIslands.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,11 @@ test.describe('tracing in static routes with server islands', () => {
5555
'sentry.op': { value: 'http.server', type: 'string' },
5656
'sentry.origin': { value: 'auto.http.astro', type: 'string' },
5757
'sentry.segment.name.source': { value: 'route', type: 'string' },
58-
'http.request.header.accept': { value: expect.any(String), type: 'string' },
59-
'http.request.header.accept-encoding': { value: 'gzip, deflate, br, zstd', type: 'string' },
60-
'http.request.header.accept-language': { value: 'en-US', type: 'string' },
61-
'http.request.header.sec-fetch-mode': { value: 'cors', type: 'string' },
62-
'http.request.header.user-agent': { value: expect.any(String), type: 'string' },
58+
'http.request.header.accept': { value: [expect.any(String)], type: 'array' },
59+
'http.request.header.accept-encoding': { value: ['gzip, deflate, br, zstd'], type: 'array' },
60+
'http.request.header.accept-language': { value: ['en-US'], type: 'array' },
61+
'http.request.header.sec-fetch-mode': { value: ['cors'], type: 'array' },
62+
'http.request.header.user-agent': { value: [expect.any(String)], type: 'array' },
6363
});
6464

6565
// unfortunately, the server island trace id is not the same as the client pageload trace id

0 commit comments

Comments
 (0)