Skip to content

Commit 86d017f

Browse files
authored
feat(v10/core): Add url.full attribute to core fetch instrumentation (#22436)
backport of #22415
1 parent 4d8cdbd commit 86d017f

19 files changed

Lines changed: 107 additions & 34 deletions

File tree

dev-packages/e2e-tests/test-applications/astro-6-cf-workers/tests/tracing.dynamic.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,8 @@ test.describe('nested SSR routes (client, server, server request)', () => {
242242
'sentry.op': 'http.client',
243243
'sentry.origin': 'auto.http.fetch',
244244
url: expect.stringContaining('/api/user/myUsername123.json'),
245+
'http.url': 'http://localhost:3030/api/user/myUsername123.json',
246+
'url.full': 'http://localhost:3030/api/user/myUsername123.json',
245247
},
246248
});
247249

dev-packages/e2e-tests/test-applications/nextjs-pages-dir/tests/middleware.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ test('Should trace outgoing fetch requests inside middleware and create breadcru
7575
type: 'fetch',
7676
url: 'http://localhost:3030/',
7777
'http.url': 'http://localhost:3030/',
78+
'url.full': 'http://localhost:3030/',
7879
'server.address': 'localhost:3030',
7980
'sentry.op': 'http.client',
8081
'sentry.origin': 'auto.http.wintercg_fetch',

packages/browser-utils/src/metrics/browserMetrics.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
isPrimitive,
99
parseUrl,
1010
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
11-
SEMANTIC_ATTRIBUTE_URL_FULL,
1211
setMeasurement,
1312
spanToJSON,
1413
stringMatchesSomePattern,
@@ -30,6 +29,7 @@ import { getActivationStart } from './web-vitals/lib/getActivationStart';
3029
import { getNavigationEntry } from './web-vitals/lib/getNavigationEntry';
3130
import { getVisibilityWatcher } from './web-vitals/lib/getVisibilityWatcher';
3231
import { DEBUG_BUILD } from '../debug-build';
32+
import { URL_FULL } from '@sentry/conventions/attributes';
3333
interface NavigatorNetworkInformation {
3434
readonly connection?: NetworkInformation;
3535
}
@@ -775,7 +775,7 @@ export function _addResourceSpans(
775775

776776
attributes['url.same_origin'] = resourceUrl.includes(WINDOW.location.origin);
777777

778-
attributes[SEMANTIC_ATTRIBUTE_URL_FULL] = resourceUrl;
778+
attributes[URL_FULL] = resourceUrl;
779779

780780
_setResourceRequestAttributes(entry, attributes, [
781781
// https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming/responseStatus

packages/browser/src/integrations/graphqlClient.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@ import {
55
isString,
66
SEMANTIC_ATTRIBUTE_HTTP_REQUEST_METHOD,
77
SEMANTIC_ATTRIBUTE_SENTRY_OP,
8-
SEMANTIC_ATTRIBUTE_URL_FULL,
98
spanToJSON,
109
stringMatchesSomePattern,
1110
} from '@sentry/core/browser';
1211
import type { FetchHint, XhrHint } from '@sentry/browser-utils';
1312
import { getBodyString, getFetchRequestArgBody, SENTRY_XHR_DATA_KEY } from '@sentry/browser-utils';
14-
import { GRAPHQL_DOCUMENT } from '@sentry/conventions/attributes';
13+
import { GRAPHQL_DOCUMENT, URL_FULL } from '@sentry/conventions/attributes';
1514

1615
interface GraphQLClientOptions {
1716
endpoints: Array<string | RegExp>;
@@ -71,7 +70,7 @@ function _updateSpanWithGraphQLData(client: Client, options: GraphQLClientOption
7170

7271
// Fall back to `url` because fetch instrumentation only sets `http.url` for absolute URLs;
7372
// relative URLs end up only in `url` (see `getFetchSpanAttributes` in packages/core/src/fetch.ts).
74-
const httpUrl = spanAttributes[SEMANTIC_ATTRIBUTE_URL_FULL] || spanAttributes['http.url'] || spanAttributes['url'];
73+
const httpUrl = spanAttributes[URL_FULL] || spanAttributes['http.url'] || spanAttributes['url'];
7574
const httpMethod = spanAttributes[SEMANTIC_ATTRIBUTE_HTTP_REQUEST_METHOD] || spanAttributes['http.method'];
7675

7776
if (!isString(httpUrl) || !isString(httpMethod)) {

packages/browser/src/integrations/httpcontext.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { defineIntegration, safeSetSpanJSONAttributes, SEMANTIC_ATTRIBUTE_SENTRY_OP } from '@sentry/core/browser';
22
import { getHttpRequestData, WINDOW } from '../helpers';
3+
import { URL_FULL } from '@sentry/conventions/attributes';
34

45
/**
56
* Collects information about HTTP request headers and
@@ -39,7 +40,7 @@ export const httpContextIntegration = defineIntegration(() => {
3940
safeSetSpanJSONAttributes(span, {
4041
// Coerce empty string to undefined so the helper's nullish check drops it,
4142
// rather than writing an empty `url.full` attribute onto the span.
42-
'url.full': spanOp !== 'http.client' ? reqData.url : undefined,
43+
[URL_FULL]: spanOp !== 'http.client' ? reqData.url : undefined,
4344
'http.request.header.user_agent': reqData.headers['User-Agent'],
4445
'http.request.header.referer': reqData.headers['Referer'],
4546
});

packages/browser/src/tracing/request.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import {
4040
} from '@sentry/browser-utils';
4141
import type { BrowserClient } from '../client';
4242
import { baggageHeaderHasSentryValues, createHeadersSafely, getFullURL, isPerformanceResourceTiming } from './utils';
43+
import { HTTP_URL, URL_FULL } from '@sentry/conventions/attributes';
4344

4445
/** Options for Request Instrumentation */
4546
export interface RequestInstrumentationOptions {
@@ -172,10 +173,11 @@ export function instrumentOutgoingRequests(client: Client, _options?: Partial<Re
172173
const host = fullUrl ? parseUrl(fullUrl).host : undefined;
173174
const sanitizedFullUrl = fullUrl ? stripDataUrlContent(fullUrl) : undefined;
174175
createdSpan.setAttributes({
175-
'http.url': sanitizedFullUrl,
176+
// oxlint-disable-next-line typescript/no-deprecated
177+
[HTTP_URL]: sanitizedFullUrl,
176178
// `url.full` must match `http.url`. Setting it here ensures parentless `http.client`
177179
// segment spans don't get `url.full` backfilled with the host page URL (see httpContextIntegration).
178-
'url.full': sanitizedFullUrl,
180+
[URL_FULL]: sanitizedFullUrl,
179181
'server.address': host,
180182
});
181183

@@ -393,7 +395,7 @@ function xhrCallback(
393395
'http.url': sanitizedFullUrl,
394396
// `url.full` must match `http.url`. Setting it here ensures parentless `http.client`
395397
// segment spans don't get `url.full` backfilled with the host page URL (see httpContextIntegration).
396-
'url.full': sanitizedFullUrl,
398+
[URL_FULL]: sanitizedFullUrl,
397399
'server.address': parsedUrl?.host,
398400
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.browser',
399401
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.client',

packages/browser/test/integrations/graphqlClient.test.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { Client } from '@sentry/core/browser';
66
import { SentrySpan, spanToJSON } from '@sentry/core/browser';
77
import type { FetchHint, XhrHint } from '@sentry/browser-utils';
88
import { SENTRY_XHR_DATA_KEY } from '@sentry/browser-utils';
9+
import { URL_FULL } from '@sentry/conventions/attributes';
910
import { describe, expect, test } from 'vitest';
1011
import {
1112
_getGraphQLOperation,
@@ -352,14 +353,15 @@ describe('GraphqlClient', () => {
352353
extensions: {},
353354
};
354355

355-
test('enriches http.client span for absolute URLs (http.url attribute)', () => {
356+
test('enriches http.client span for absolute URLs', () => {
356357
const handler = setupHandler([/\/graphql$/]);
357358
const span = new SentrySpan({
358359
name: 'POST http://localhost:4000/graphql',
359360
op: 'http.client',
360361
attributes: {
361362
'http.method': 'POST',
362363
'http.url': 'http://localhost:4000/graphql',
364+
[URL_FULL]: 'http://localhost:4000/graphql',
363365
url: 'http://localhost:4000/graphql',
364366
},
365367
});
@@ -371,9 +373,27 @@ describe('GraphqlClient', () => {
371373
expect(json.data['graphql.document']).toBe(requestBody.query);
372374
});
373375

376+
test('enriches http.client span when only url.full is present', () => {
377+
const handler = setupHandler([/\/graphql$/]);
378+
const span = new SentrySpan({
379+
name: 'POST http://localhost:4000/graphql',
380+
op: 'http.client',
381+
attributes: {
382+
'http.method': 'POST',
383+
[URL_FULL]: 'http://localhost:4000/graphql',
384+
},
385+
});
386+
387+
handler(span, makeFetchHint('http://localhost:4000/graphql', requestBody));
388+
389+
const json = spanToJSON(span);
390+
expect(json.description).toBe('POST http://localhost:4000/graphql (query GetHello)');
391+
expect(json.data['graphql.document']).toBe(requestBody.query);
392+
});
393+
374394
test('enriches http.client span for relative URLs (only url attribute)', () => {
375395
const handler = setupHandler([/\/graphql$/]);
376-
// Fetch instrumentation does NOT set http.url for relative URLs — only `url`.
396+
// Fetch instrumentation does not set `http.url` or `url.full` for relative URLs.
377397
const span = new SentrySpan({
378398
name: 'POST /graphql',
379399
op: 'http.client',
@@ -433,6 +453,7 @@ describe('GraphqlClient', () => {
433453
attributes: {
434454
'http.method': 'POST',
435455
'http.url': 'http://localhost:4000/graphql',
456+
[URL_FULL]: 'http://localhost:4000/graphql',
436457
url: 'http://localhost:4000/graphql',
437458
},
438459
});

packages/bun/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"dependencies": {
5252
"@apm-js-collab/code-transformer-bundler-plugins": "^0.7.1",
5353
"@sentry/core": "10.67.0",
54+
"@sentry/conventions": "^0.16.0",
5455
"@sentry/node": "10.67.0",
5556
"@sentry/server-utils": "10.67.0"
5657
},

packages/bun/src/integrations/bunserver.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
withIsolationScope,
1616
} from '@sentry/core';
1717
import type { ServeOptions } from 'bun';
18+
import { URL_FULL } from '@sentry/conventions/attributes';
1819

1920
const INTEGRATION_NAME = 'BunServer' as const;
2021

@@ -282,7 +283,7 @@ function getSpanAttributesFromParsedUrl(
282283
attributes['url.path'] = parsedUrl.pathname;
283284
}
284285
if (!isURLObjectRelative(parsedUrl)) {
285-
attributes['url.full'] = parsedUrl.href;
286+
attributes[URL_FULL] = parsedUrl.href;
286287
if (parsedUrl.port) {
287288
attributes['url.port'] = parsedUrl.port;
288289
}

packages/core/src/fetch.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { HTTP_URL, URL_FULL } from '@sentry/conventions/attributes';
12
import { getClient } from './currentScopes';
23
import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from './semanticAttributes';
34
import { setHttpStatus, SPAN_STATUS_ERROR, spanIsIgnored, startInactiveSpan } from './tracing';
@@ -388,7 +389,9 @@ function getFetchSpanAttributes(
388389
};
389390
if (parsedUrl) {
390391
if (!isURLObjectRelative(parsedUrl)) {
391-
attributes['http.url'] = stripDataUrlContent(parsedUrl.href);
392+
// oxlint-disable-next-line typescript/no-deprecated
393+
attributes[HTTP_URL] = stripDataUrlContent(parsedUrl.href);
394+
attributes[URL_FULL] = stripDataUrlContent(parsedUrl.href);
392395
attributes['server.address'] = parsedUrl.host;
393396
}
394397
if (parsedUrl.search) {

0 commit comments

Comments
 (0)