Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions packages/angular/src/tracing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@ import {
import { CODE_FUNCTION_NAME, SENTRY_OP, URL_FULL, URL_PATH, URL_TEMPLATE } from '@sentry/conventions/attributes';
import { GENERAL_FUNCTION_SPAN_OP } from '@sentry/conventions/op';
import type { Integration, Span } from '@sentry/core';
import { debug, parseStringToURLObject, stripUrlQueryAndFragment, timestampInSeconds } from '@sentry/core';
import {
debug,
parseStringToURLObject,
stripUrlQueryAndFragment,
timestampInSeconds,
filterCollectedUrl,
} from '@sentry/core';
import type { Observable } from 'rxjs';
import { Subscription } from 'rxjs';
import { filter, tap } from 'rxjs/operators';
Expand Down Expand Up @@ -72,7 +78,7 @@ export function _updateSpanAttributesForParametrizedUrl(route: string, url: stri
span.setAttributes({
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: `auto.${op}.angular`,
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
[URL_FULL]: absoluteUrl,
[URL_FULL]: filterCollectedUrl(absoluteUrl),
[URL_PATH]: parseStringToURLObject(absoluteUrl)?.pathname,
[URL_TEMPLATE]: route,
});
Expand Down
6 changes: 4 additions & 2 deletions packages/astro/src/server/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
SEMANTIC_ATTRIBUTE_HTTP_REQUEST_METHOD,
spanToJSON,
winterCGRequestToRequestData,
filterCollectedUrl,
filterCollectedUrlQuery,
} from '@sentry/core';
import {
captureException,
Expand Down Expand Up @@ -214,7 +216,7 @@ async function instrumentRequestStartHttpServerSpan(
[SEMANTIC_ATTRIBUTE_HTTP_REQUEST_METHOD]: method,
// This is here for backwards compatibility, we used to set this here before
method,
[URL_FULL]: ctx.url.href,
[URL_FULL]: filterCollectedUrl(ctx.url.href),
[URL_PATH]: ctx.url.pathname,
...httpHeadersToSpanAttributes(winterCGHeadersToDict(request.headers), client.getDataCollectionOptions()),
};
Expand All @@ -223,7 +225,7 @@ async function instrumentRequestStartHttpServerSpan(
attributes[HTTP_ROUTE] = parametrizedRoute;
}

attributes[URL_QUERY] = getUrlQuery(ctx.url.search);
attributes[URL_QUERY] = filterCollectedUrlQuery(getUrlQuery(ctx.url.search));
attributes[URL_FRAGMENT] = getUrlFragment(ctx.url.hash);

const name = `${method} ${parametrizedRoute || ctx.url.pathname}`;
Expand Down
4 changes: 2 additions & 2 deletions packages/aws-serverless/src/requestSpanOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
} from '@sentry/conventions/attributes';
import { FAAS_FUNCTION_AWS_SPAN_OP } from '@sentry/conventions/op';
import type { SpanAttributes, StartSpanOptions } from '@sentry/core';
import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core';
import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, filterCollectedUrl } from '@sentry/core';
import type { Context } from 'aws-lambda';
import { ATTR_FAAS_EXECUTION, ATTR_FAAS_ID } from './semconv';

Expand Down Expand Up @@ -75,7 +75,7 @@ function extractOtherEventFields(event: unknown): SpanAttributes {
const answer: SpanAttributes = {};
const fullUrl = extractFullUrl(event as ApiGatewayLikeEvent);
if (fullUrl) {
answer[URL_FULL] = fullUrl;
answer[URL_FULL] = filterCollectedUrl(fullUrl);
}
return answer;
}
Expand Down
3 changes: 2 additions & 1 deletion packages/browser-utils/src/metrics/browserMetrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
setMeasurement,
spanToJSON,
filterCollectedUrl,
} from '@sentry/core';
import { htmlTreeAsString } from '../htmlTreeAsString';
import { WINDOW } from '../types';
Expand Down Expand Up @@ -627,7 +628,7 @@ export function _addResourceSpans(

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

attributes[URL_FULL] = resourceUrl;
attributes[URL_FULL] = filterCollectedUrl(resourceUrl);

_setResourceRequestAttributes(entry, attributes, [
// https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming/responseStatus
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
startInactiveSpan,
stripDataUrlContent,
filterCollectedUrl,
} from '@sentry/core';

const responseToStreamSpan = new WeakMap<object, Span>();
Expand Down Expand Up @@ -81,7 +82,7 @@ export const fetchStreamPerformanceIntegration = defineIntegration(() => {
name: `${method} ${sanitizedUrl}`,
startTime: handlerData.endTimestamp,
attributes: {
[URL_FULL]: stripDataUrlContent(url),
[URL_FULL]: filterCollectedUrl(stripDataUrlContent(url)),
Comment thread
chargome marked this conversation as resolved.
'http.method': method,
type: 'fetch',
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.client.stream',
Expand Down
3 changes: 2 additions & 1 deletion packages/browser/src/integrations/httpcontext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
SEMANTIC_ATTRIBUTE_SENTRY_OP,
} from '@sentry/core/browser';
import { getHttpRequestData, WINDOW } from '../helpers';
import { filterCollectedUrl } from '@sentry/core';
import { URL_FULL } from '@sentry/conventions/attributes';

/**
Expand Down Expand Up @@ -59,7 +60,7 @@ export const httpContextIntegration = defineIntegration(() => {
safeSetSpanJSONAttributes(span, {
// 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]: spanOp !== 'http.client' ? reqData.url : undefined,
[URL_FULL]: spanOp !== 'http.client' ? filterCollectedUrl(reqData.url) : undefined,
'http.request.header.user_agent': headers['User-Agent'],
'http.request.header.referer': headers['Referer'],
});
Expand Down
3 changes: 2 additions & 1 deletion packages/browser/src/tracing/browserTracingIntegration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
startTrackingLongTasks,
} from '@sentry/browser-utils';
import { DEBUG_BUILD } from '../debug-build';
import { filterCollectedUrl } from '@sentry/core';
import { getHttpRequestData, WINDOW } from '../helpers';
import { fetchStreamPerformanceIntegration } from '../integrations/fetchStreamPerformance';
import { WEB_VITALS_INTEGRATION_NAME, webVitalsIntegration } from '../integrations/webVitals';
Expand Down Expand Up @@ -392,7 +393,7 @@ export const browserTracingIntegration = ((options: Partial<BrowserTracingOption

const attributes = {
...(urlObject?.pathname && { [URL_PATH]: urlObject.pathname }),
...(urlObject && !isURLObjectRelative(urlObject) && { [URL_FULL]: urlObject.href }),
...(urlObject && !isURLObjectRelative(urlObject) && { [URL_FULL]: filterCollectedUrl(urlObject.href) }),
...finalStartSpanOptions.attributes,
};

Expand Down
7 changes: 4 additions & 3 deletions packages/browser/src/tracing/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
timestampInSeconds,
} from '@sentry/core/browser';
import type { XhrHint } from '@sentry/browser-utils';
import { filterCollectedUrl, filterCollectedUrlQuery } from '@sentry/core';
import {
addPerformanceInstrumentationHandler,
addXhrInstrumentationHandler,
Expand Down Expand Up @@ -176,7 +177,7 @@ export function instrumentOutgoingRequests(client: Client, _options?: Partial<Re
const host = fullUrl ? parseUrl(fullUrl).host : undefined;
const sanitizedFullUrl = fullUrl ? stripDataUrlContent(fullUrl) : undefined;
createdSpan.setAttributes({
[URL_FULL]: sanitizedFullUrl,
[URL_FULL]: filterCollectedUrl(sanitizedFullUrl),
'server.address': host,
});

Expand Down Expand Up @@ -391,11 +392,11 @@ function xhrCallback(
type: 'xhr',
// eslint-disable-next-line typescript/no-deprecated
[HTTP_METHOD]: method,
[URL_FULL]: sanitizedFullUrl,
[URL_FULL]: filterCollectedUrl(sanitizedFullUrl),
[SERVER_ADDRESS]: parsedUrl?.host,
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.browser',
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.client',
[URL_QUERY]: getUrlQuery(parsedUrl?.search),
[URL_QUERY]: filterCollectedUrlQuery(getUrlQuery(parsedUrl?.search)),
[URL_FRAGMENT]: getUrlFragment(parsedUrl?.hash),
},
})
Expand Down
6 changes: 4 additions & 2 deletions packages/bun/src/integrations/bunserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {
setHttpStatus,
startSpan,
withIsolationScope,
filterCollectedUrl,
filterCollectedUrlQuery,
} from '@sentry/core';
import type { ServeOptions } from 'bun';
import {
Expand Down Expand Up @@ -289,13 +291,13 @@ function getSpanAttributesFromParsedUrl(
};

if (parsedUrl) {
attributes[URL_QUERY] = getUrlQuery(parsedUrl.search);
attributes[URL_QUERY] = filterCollectedUrlQuery(getUrlQuery(parsedUrl.search));
attributes[URL_FRAGMENT] = getUrlFragment(parsedUrl.hash);
if (parsedUrl.pathname) {
attributes[URL_PATH] = parsedUrl.pathname;
}
if (!isURLObjectRelative(parsedUrl)) {
attributes[URL_FULL] = parsedUrl.href;
attributes[URL_FULL] = filterCollectedUrl(parsedUrl.href);
if (parsedUrl.port) {
attributes[URL_PORT] = parsedUrl.port;
}
Expand Down
9 changes: 8 additions & 1 deletion packages/cloudflare/src/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,14 @@ export function wrapRequestHandlerWithInit(
isolationScope.setClient(client);

const urlObject = parseStringToURLObject(request.url);
const [name, attributes] = getHttpSpanDetailsFromUrlObject(urlObject, 'server', 'auto.http.cloudflare', request);
const [name, attributes] = getHttpSpanDetailsFromUrlObject(
urlObject,
'server',
'auto.http.cloudflare',
request,
undefined,
client,
);

const contentLength = request.headers.get('content-length');
if (contentLength) {
Expand Down
16 changes: 10 additions & 6 deletions packages/core/src/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable max-lines */
import { HTTP_METHOD, SERVER_ADDRESS, URL_FRAGMENT, URL_FULL, URL_QUERY } from '@sentry/conventions/attributes';
import type { Client } from './client';
import { getClient } from './currentScopes';
import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from './semanticAttributes';
import { setHttpStatus, SPAN_STATUS_ERROR, spanIsIgnored, startInactiveSpan } from './tracing';
Expand All @@ -10,6 +11,7 @@ import type { HandlerDataFetch } from './types/instrument';
import type { ResponseHookInfo } from './types/request';
import type { Span, SpanAttributes, SpanOrigin } from './types/span';
import { SENTRY_BAGGAGE_KEY_PREFIX } from './utils/baggage';
import { filterCollectedUrl, filterCollectedUrlQuery } from './utils/data-collection/filterCollectedUrl';
import { hasSpansEnabled } from './utils/hasSpansEnabled';
import { isInstanceOf, isRequest } from './utils/is';
import { getActiveSpan } from './utils/spanUtils';
Expand Down Expand Up @@ -120,7 +122,7 @@ export function instrumentFetchRequest(

const span =
shouldCreateSpanResult && shouldEmitSpan
? startInactiveSpan(getSpanStartOptions(url, method, spanOrigin))
? startInactiveSpan(getSpanStartOptions(url, method, spanOrigin, client))
: new SentryNonRecordingSpan();
const spanForTraceHeaders = spanIsIgnored(span) && hasParent ? undefined : span;

Expand Down Expand Up @@ -360,6 +362,7 @@ function getSpanStartOptions(
url: string,
method: string,
spanOrigin: SpanOrigin,
client: Client | undefined,
): Parameters<typeof startInactiveSpan>[0] {
// Data URLs need special handling because parseStringToURLObject treats them as "relative"
// (no "://"), causing getSanitizedUrlStringFromUrlObject to return just the pathname
Expand All @@ -369,15 +372,15 @@ function getSpanStartOptions(
const sanitizedUrl = stripDataUrlContent(url);
return {
name: `${method} ${sanitizedUrl}`,
attributes: getFetchSpanAttributes(url, undefined, method, spanOrigin),
attributes: getFetchSpanAttributes(url, undefined, method, spanOrigin, client),
};
}

const parsedUrl = parseStringToURLObject(url);
const sanitizedUrl = parsedUrl ? getSanitizedUrlStringFromUrlObject(parsedUrl) : url;
return {
name: `${method} ${sanitizedUrl}`,
attributes: getFetchSpanAttributes(url, parsedUrl, method, spanOrigin),
attributes: getFetchSpanAttributes(url, parsedUrl, method, spanOrigin, client),
};
}

Expand All @@ -386,9 +389,10 @@ function getFetchSpanAttributes(
parsedUrl: ReturnType<typeof parseStringToURLObject>,
method: string,
spanOrigin: SpanOrigin,
client: Client | undefined,
): SpanAttributes {
const attributes: SpanAttributes = {
[URL_FULL]: stripDataUrlContent(url),
[URL_FULL]: filterCollectedUrl(stripDataUrlContent(url), client),
type: 'fetch',
// oxlint-disable-next-line typescript/no-deprecated
[HTTP_METHOD]: method,
Expand All @@ -397,10 +401,10 @@ function getFetchSpanAttributes(
};
if (parsedUrl) {
if (!isURLObjectRelative(parsedUrl)) {
attributes[URL_FULL] = stripDataUrlContent(parsedUrl.href);
attributes[URL_FULL] = filterCollectedUrl(stripDataUrlContent(parsedUrl.href), client);
attributes[SERVER_ADDRESS] = parsedUrl.host;
}
attributes[URL_QUERY] = getUrlQuery(parsedUrl.search);
attributes[URL_QUERY] = filterCollectedUrlQuery(getUrlQuery(parsedUrl.search), client);
attributes[URL_FRAGMENT] = getUrlFragment(parsedUrl.hash);
}
return attributes;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { HTTP_METHOD, URL_FRAGMENT, URL_QUERY } from '@sentry/conventions/attributes';
import { addBreadcrumb } from '../../breadcrumbs';
import { getBreadcrumbLogLevelFromHttpStatusCode } from '../../utils/breadcrumb-log-level';
import { filterCollectedUrlQuery } from '../../utils/data-collection/filterCollectedUrl';
import { getSanitizedUrlString, getUrlFragment, getUrlQuery, parseUrl } from '../../utils/url';
import { getRequestUrlFromClientRequest } from './get-request-url';
import type { HttpClientRequest, HttpIncomingMessage } from './types';
Expand All @@ -26,7 +27,7 @@ export function addOutgoingRequestBreadcrumb(
url: getSanitizedUrlString(parsedUrl),
// eslint-disable-next-line typescript/no-deprecated
[HTTP_METHOD]: request.method || 'GET',
[URL_QUERY]: getUrlQuery(parsedUrl.search),
[URL_QUERY]: filterCollectedUrlQuery(getUrlQuery(parsedUrl.search)),
[URL_FRAGMENT]: getUrlFragment(parsedUrl.hash),
},
type: 'http',
Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/integrations/http/get-outgoing-span-data.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Span, SpanAttributes } from '../../types/span';
import { SEMANTIC_ATTRIBUTE_SENTRY_OP } from '../../semanticAttributes';
import { filterCollectedUrl } from '../../utils/data-collection/filterCollectedUrl';
import { getHttpSpanDetailsFromUrlObject, parseStringToURLObject } from '../../utils/url';
import type { HttpClientRequest, HttpIncomingMessage } from './types';
import { getRequestUrlFromClientRequest } from './get-request-url';
Expand Down Expand Up @@ -36,10 +37,10 @@ export function getOutgoingRequestSpanData(request: HttpClientRequest): StartSpa
// https://getsentry.github.io/sentry-conventions/attributes/
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.client',
[SENTRY_KIND]: 'client',
[URL_FULL]: url,
[URL_FULL]: filterCollectedUrl(url),
/* eslint-disable typescript/no-deprecated */
[HTTP_METHOD]: request.method,
[HTTP_TARGET]: request.path || '/',
[HTTP_TARGET]: filterCollectedUrl(request.path || '/'),
[NET_PEER_NAME]: request.host,
[HTTP_HOST]: request.getHeader('host') as string | undefined,
/* eslint-enable typescript/no-deprecated */
Expand Down
8 changes: 6 additions & 2 deletions packages/core/src/integrations/http/server-subscription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { safeMathRandom } from '../../utils/randomSafeContext';
import type { SpanAttributes } from '../../types/span';
import type { SpanStatus } from '../../types/spanStatus';
import { URL_FULL, URL_PATH, SENTRY_KIND } from '@sentry/conventions/attributes';
import { filterCollectedUrl } from '../../utils/data-collection/filterCollectedUrl';

// Tree-shakable guard to remove all code related to tracing
declare const __SENTRY_TRACING__: boolean;
Expand Down Expand Up @@ -298,10 +299,13 @@ function buildServerSpanWrap(
'net.peer.port': remotePort,
'sentry.http.prefetch': isKnownPrefetchRequest(request) || undefined,
// Old Semantic Conventions attributes for compatibility
[URL_FULL]: fullUrl,
[URL_FULL]: filterCollectedUrl(fullUrl, client),
[URL_PATH]: urlObj?.pathname ?? httpTargetWithoutQueryFragment,
'http.method': method,
'http.target': urlObj ? `${urlObj.pathname}${urlObj.search}` : httpTargetWithoutQueryFragment,
'http.target': filterCollectedUrl(
urlObj ? `${urlObj.pathname}${urlObj.search}` : httpTargetWithoutQueryFragment,
client,
),
'http.host': host,
'net.host.name': hostname,
'http.client_ip': typeof ips === 'string' ? ips.split(',')[0] : undefined,
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/shared-exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export {
export { filterKeyValueData as _INTERNAL_filterKeyValueData } from './utils/data-collection/filterKeyValueData';
export { filterCookies as _INTERNAL_filterCookies } from './utils/data-collection/filterCookies';
export { filterQueryParams as _INTERNAL_filterQueryParams } from './utils/data-collection/filterQueryParams';
export { filterCollectedUrl, filterCollectedUrlQuery } from './utils/data-collection/filterCollectedUrl';
export { envToBool } from './utils/envToBool';
export { applyScopeDataToEvent, mergeScopeData, getCombinedScopeData } from './utils/scopeData';
export { prepareEvent } from './utils/prepareEvent';
Expand Down
36 changes: 36 additions & 0 deletions packages/core/src/utils/data-collection/filterCollectedUrl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { Client } from '../../client';
import { getClient } from '../../currentScopes';
import type { CollectBehavior } from '../../types/datacollection';
import { filterQueryParams } from './filterQueryParams';
import { filterUrlQuery } from './filterUrlQuery';

function urlQueryParamsBehavior(client: Client | undefined): CollectBehavior {
// Instrumentation can run before a client exists; the denylist default is the safe fallback.
return (client ?? getClient())?.getDataCollectionOptions().urlQueryParams ?? true;
}

/**
* Applies `dataCollection.urlQueryParams` to a URL the SDK collected itself, for use as `url.full`.
*
* Call this at every site where instrumentation records a URL. Routing the SDK's own URLs through a
* helper is what makes the filtering provenance-correct: a URL a user attaches themselves never passes
* through here, and `dataCollection` is only meant to gate automatically collected data.
*
* Pass the `client` the URL belongs to whenever one is at hand — falling back to `getClient()` resolves
* against the current scope, which is the wrong client in a multi-client setup.
*/
export function filterCollectedUrl(url: string, client?: Client): string;
export function filterCollectedUrl(url: string | undefined, client?: Client): string | undefined;
export function filterCollectedUrl(url: string | undefined, client?: Client): string | undefined {
return url === undefined ? undefined : filterUrlQuery(url, urlQueryParamsBehavior(client));
}

/**
* Applies `dataCollection.urlQueryParams` to a query string the SDK collected itself, for use as
* `url.query`. Returns `undefined` when the query must not be collected at all.
*
* See {@link filterCollectedUrl} for why this is a helper and why passing `client` is preferred.
*/
export function filterCollectedUrlQuery(query: string | undefined, client?: Client): string | undefined {
return query ? filterQueryParams(query, urlQueryParamsBehavior(client)) : undefined;
}
Loading
Loading