diff --git a/.size-limit.js b/.size-limit.js index 355e484c5bd1..dc2b7af8128f 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -132,7 +132,7 @@ module.exports = [ path: 'packages/vue/build/esm/index.js', import: createImport('init'), gzip: true, - limit: '28 KB', + limit: '29 KB', }, { name: '@sentry/vue (incl. Tracing)', diff --git a/dev-packages/browser-integration-tests/suites/integrations/ContextLines/noAddedLines/test.ts b/dev-packages/browser-integration-tests/suites/integrations/ContextLines/noAddedLines/test.ts index 8fa8ec16cddd..a3c8614bfb2b 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/ContextLines/noAddedLines/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/ContextLines/noAddedLines/test.ts @@ -7,6 +7,7 @@ sentryTest('should not add source context lines to errors from script files', as const url = await getLocalTestUrl({ testDir: __dirname }); const eventReqPromise = waitForErrorRequestOnUrl(page, url); + await page.waitForFunction('window.Sentry'); const clickPromise = page.locator('#script-error-btn').click(); diff --git a/docs/migration/draft-v9-migration-guide.md b/docs/migration/draft-v9-migration-guide.md index 763d0765cff5..a7ca036e71d2 100644 --- a/docs/migration/draft-v9-migration-guide.md +++ b/docs/migration/draft-v9-migration-guide.md @@ -40,6 +40,7 @@ If you are relying on `undefined` being passed in and having tracing enabled bec - Deprecated `transactionNamingScheme` option in `requestDataIntegration`. - Deprecated `debugIntegration`. To log outgoing events, use [Hook Options](https://docs.sentry.io/platforms/javascript/configuration/options/#hooks) (`beforeSend`, `beforeSendTransaction`, ...). - Deprecated `sessionTimingIntegration`. To capture session durations alongside events, use [Context](https://docs.sentry.io/platforms/javascript/enriching-events/context/) (`Sentry.setContext()`). +- Deprecated `addTracingHeadersToFetchRequest` method - this was only meant for internal use and is not needed anymore. ## `@sentry/nestjs` diff --git a/packages/browser/src/tracing/request.ts b/packages/browser/src/tracing/request.ts index 225903d32a4b..63b0c1f7049b 100644 --- a/packages/browser/src/tracing/request.ts +++ b/packages/browser/src/tracing/request.ts @@ -9,24 +9,17 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SentryNonRecordingSpan, getActiveSpan, - getClient, - getCurrentScope, - getDynamicSamplingContextFromClient, - getDynamicSamplingContextFromSpan, - getIsolationScope, + getTraceData, hasTracingEnabled, instrumentFetchRequest, setHttpStatus, spanToJSON, - spanToTraceHeader, startInactiveSpan, } from '@sentry/core'; import { addFetchEndInstrumentationHandler, addFetchInstrumentationHandler, browserPerformanceTimeOrigin, - dynamicSamplingContextToSentryBaggageHeader, - generateSentryTraceHeader, parseUrl, stringMatchesSomePattern, } from '@sentry/core'; @@ -76,7 +69,9 @@ export interface RequestInstrumentationOptions { * * Default: true */ - traceXHR: boolean /** + traceXHR: boolean; + + /** * Flag to disable tracking of long-lived streams, like server-sent events (SSE) via fetch. * Do not enable this in case you have live streams or very long running streams. * @@ -84,7 +79,7 @@ export interface RequestInstrumentationOptions { * (https://github.com/getsentry/sentry-javascript/issues/13950) * * Default: false - */; + */ trackFetchStreamPerformance: boolean; /** @@ -401,12 +396,9 @@ export function xhrCallback( xhr.__sentry_xhr_span_id__ = span.spanContext().spanId; spans[xhr.__sentry_xhr_span_id__] = span; - const client = getClient(); - - if (xhr.setRequestHeader && shouldAttachHeaders(sentryXhrData.url) && client) { + if (shouldAttachHeaders(sentryXhrData.url)) { addTracingHeadersToXhrRequest( xhr, - client, // If performance is disabled (TWP) or there's no active root span (pageload/navigation/interaction), // we do not want to use the span as base for the trace headers, // which means that the headers will be generated from the scope and the sampling decision is deferred @@ -417,22 +409,12 @@ export function xhrCallback( return span; } -function addTracingHeadersToXhrRequest(xhr: SentryWrappedXMLHttpRequest, client: Client, span?: Span): void { - const scope = getCurrentScope(); - const isolationScope = getIsolationScope(); - const { traceId, spanId, sampled, dsc } = { - ...isolationScope.getPropagationContext(), - ...scope.getPropagationContext(), - }; +function addTracingHeadersToXhrRequest(xhr: SentryWrappedXMLHttpRequest, span?: Span): void { + const { 'sentry-trace': sentryTrace, baggage } = getTraceData({ span }); - const sentryTraceHeader = - span && hasTracingEnabled() ? spanToTraceHeader(span) : generateSentryTraceHeader(traceId, spanId, sampled); - - const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader( - dsc || (span ? getDynamicSamplingContextFromSpan(span) : getDynamicSamplingContextFromClient(traceId, client)), - ); - - setHeaderOnXhr(xhr, sentryTraceHeader, sentryBaggageHeader); + if (sentryTrace) { + setHeaderOnXhr(xhr, sentryTrace, baggage); + } } function setHeaderOnXhr( diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index d86f170ca25d..23010505a7dc 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -32,7 +32,7 @@ import type { } from '@sentry/types'; import { getEnvelopeEndpointWithUrlEncodedAuth } from './api'; -import { getIsolationScope } from './currentScopes'; +import { getCurrentScope, getIsolationScope, getTraceContextFromScope } from './currentScopes'; import { DEBUG_BUILD } from './debug-build'; import { createEventEnvelope, createSessionEnvelope } from './envelope'; import type { IntegrationIndex } from './integration'; @@ -40,7 +40,7 @@ import { afterSetupIntegrations } from './integration'; import { setupIntegration, setupIntegrations } from './integration'; import type { Scope } from './scope'; import { updateSession } from './session'; -import { getDynamicSamplingContextFromClient } from './tracing/dynamicSamplingContext'; +import { getDynamicSamplingContextFromScope } from './tracing/dynamicSamplingContext'; import { createClientReportEnvelope } from './utils-hoist/clientreport'; import { dsnToString, makeDsn } from './utils-hoist/dsn'; import { addItemToEnvelope, createAttachmentEnvelopeItem } from './utils-hoist/envelope'; @@ -48,7 +48,6 @@ import { SentryError } from './utils-hoist/error'; import { isParameterizedString, isPlainObject, isPrimitive, isThenable } from './utils-hoist/is'; import { consoleSandbox, logger } from './utils-hoist/logger'; import { checkOrSetAlreadyCaught, uuid4 } from './utils-hoist/misc'; -import { dropUndefinedKeys } from './utils-hoist/object'; import { SyncPromise, rejectedSyncPromise, resolvedSyncPromise } from './utils-hoist/syncpromise'; import { parseSampleRate } from './utils/parseSampleRate'; import { prepareEvent } from './utils/prepareEvent'; @@ -672,7 +671,7 @@ export abstract class BaseClient implements Client { protected _prepareEvent( event: Event, hint: EventHint, - currentScope?: Scope, + currentScope = getCurrentScope(), isolationScope = getIsolationScope(), ): PromiseLike { const options = this.getOptions(); @@ -692,30 +691,18 @@ export abstract class BaseClient implements Client { return evt; } - const propagationContext = { - ...isolationScope.getPropagationContext(), - ...(currentScope ? currentScope.getPropagationContext() : undefined), + evt.contexts = { + trace: getTraceContextFromScope(currentScope), + ...evt.contexts, }; - const trace = evt.contexts && evt.contexts.trace; - if (!trace && propagationContext) { - const { traceId: trace_id, spanId, parentSpanId, dsc } = propagationContext; - evt.contexts = { - trace: dropUndefinedKeys({ - trace_id, - span_id: spanId, - parent_span_id: parentSpanId, - }), - ...evt.contexts, - }; + const dynamicSamplingContext = getDynamicSamplingContextFromScope(this, currentScope); - const dynamicSamplingContext = dsc ? dsc : getDynamicSamplingContextFromClient(trace_id, this); + evt.sdkProcessingMetadata = { + dynamicSamplingContext, + ...evt.sdkProcessingMetadata, + }; - evt.sdkProcessingMetadata = { - dynamicSamplingContext, - ...evt.sdkProcessingMetadata, - }; - } return evt; }); } diff --git a/packages/core/src/currentScopes.ts b/packages/core/src/currentScopes.ts index 77e8aa70a02a..825092fb2d5d 100644 --- a/packages/core/src/currentScopes.ts +++ b/packages/core/src/currentScopes.ts @@ -1,8 +1,9 @@ -import type { Scope } from '@sentry/types'; +import type { Scope, TraceContext } from '@sentry/types'; import type { Client } from '@sentry/types'; import { getAsyncContextStrategy } from './asyncContext'; import { getMainCarrier } from './carrier'; import { Scope as ScopeClass } from './scope'; +import { dropUndefinedKeys } from './utils-hoist/object'; import { getGlobalSingleton } from './utils-hoist/worldwide'; /** @@ -120,3 +121,20 @@ export function withIsolationScope( export function getClient(): C | undefined { return getCurrentScope().getClient(); } + +/** + * Get a trace context for the given scope. + */ +export function getTraceContextFromScope(scope: Scope): TraceContext { + const propagationContext = scope.getPropagationContext(); + + const { traceId, spanId, parentSpanId } = propagationContext; + + const traceContext: TraceContext = dropUndefinedKeys({ + trace_id: traceId, + span_id: spanId, + parent_span_id: parentSpanId, + }); + + return traceContext; +} diff --git a/packages/core/src/fetch.ts b/packages/core/src/fetch.ts index 65629d1291ac..1dbb84423678 100644 --- a/packages/core/src/fetch.ts +++ b/packages/core/src/fetch.ts @@ -1,20 +1,13 @@ import type { Client, HandlerDataFetch, Scope, Span, SpanOrigin } from '@sentry/types'; -import { getClient, getCurrentScope, getIsolationScope } from './currentScopes'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from './semanticAttributes'; -import { - SPAN_STATUS_ERROR, - getDynamicSamplingContextFromClient, - getDynamicSamplingContextFromSpan, - setHttpStatus, - startInactiveSpan, -} from './tracing'; +import { SPAN_STATUS_ERROR, setHttpStatus, startInactiveSpan } from './tracing'; import { SentryNonRecordingSpan } from './tracing/sentryNonRecordingSpan'; -import { SENTRY_BAGGAGE_KEY_PREFIX, dynamicSamplingContextToSentryBaggageHeader } from './utils-hoist/baggage'; +import { SENTRY_BAGGAGE_KEY_PREFIX } from './utils-hoist/baggage'; import { isInstanceOf } from './utils-hoist/is'; -import { generateSentryTraceHeader } from './utils-hoist/tracing'; import { parseUrl } from './utils-hoist/url'; import { hasTracingEnabled } from './utils/hasTracingEnabled'; -import { getActiveSpan, spanToTraceHeader } from './utils/spanUtils'; +import { getActiveSpan } from './utils/spanUtils'; +import { getTraceData } from './utils/traceData'; type PolymorphicRequestHeaders = | Record @@ -59,9 +52,6 @@ export function instrumentFetchRequest( return undefined; } - const scope = getCurrentScope(); - const client = getClient(); - const { method, url } = handlerData.fetchData; const fullUrl = getFullURL(url); @@ -88,37 +78,34 @@ export function instrumentFetchRequest( handlerData.fetchData.__span = span.spanContext().spanId; spans[span.spanContext().spanId] = span; - if (shouldAttachHeaders(handlerData.fetchData.url) && client) { + if (shouldAttachHeaders(handlerData.fetchData.url)) { const request: string | Request = handlerData.args[0]; - // In case the user hasn't set the second argument of a fetch call we default it to `{}`. - handlerData.args[1] = handlerData.args[1] || {}; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const options: { [key: string]: any } = handlerData.args[1]; + const options: { [key: string]: unknown } = handlerData.args[1] || {}; - options.headers = addTracingHeadersToFetchRequest( + const headers = _addTracingHeadersToFetchRequest( request, - client, - scope, options, // If performance is disabled (TWP) or there's no active root span (pageload/navigation/interaction), // we do not want to use the span as base for the trace headers, // which means that the headers will be generated from the scope and the sampling decision is deferred hasTracingEnabled() && hasParent ? span : undefined, ); + if (headers) { + // Ensure this is actually set, if no options have been passed previously + handlerData.args[1] = options; + options.headers = headers; + } } return span; } /** - * Adds sentry-trace and baggage headers to the various forms of fetch headers + * Adds sentry-trace and baggage headers to the various forms of fetch headers. */ -export function addTracingHeadersToFetchRequest( - request: string | unknown, // unknown is actually type Request but we can't export DOM types from this package, - client: Client, - scope: Scope, +function _addTracingHeadersToFetchRequest( + request: string | Request, fetchOptionsObj: { headers?: | { @@ -128,31 +115,24 @@ export function addTracingHeadersToFetchRequest( }, span?: Span, ): PolymorphicRequestHeaders | undefined { - const isolationScope = getIsolationScope(); + const traceHeaders = getTraceData({ span }); + const sentryTrace = traceHeaders['sentry-trace']; + const baggage = traceHeaders.baggage; - const { traceId, spanId, sampled, dsc } = { - ...isolationScope.getPropagationContext(), - ...scope.getPropagationContext(), - }; - - const sentryTraceHeader = span ? spanToTraceHeader(span) : generateSentryTraceHeader(traceId, spanId, sampled); - - const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader( - dsc || (span ? getDynamicSamplingContextFromSpan(span) : getDynamicSamplingContextFromClient(traceId, client)), - ); + // Nothing to do, when we return undefined here, the original headers will be used + if (!sentryTrace) { + return undefined; + } - const headers = - fetchOptionsObj.headers || - (typeof Request !== 'undefined' && isInstanceOf(request, Request) ? (request as Request).headers : undefined); + const headers = fetchOptionsObj.headers || (isRequest(request) ? request.headers : undefined); if (!headers) { - return { 'sentry-trace': sentryTraceHeader, baggage: sentryBaggageHeader }; - } else if (typeof Headers !== 'undefined' && isInstanceOf(headers, Headers)) { - const newHeaders = new Headers(headers as Headers); - - newHeaders.set('sentry-trace', sentryTraceHeader); + return { ...traceHeaders }; + } else if (isHeaders(headers)) { + const newHeaders = new Headers(headers); + newHeaders.set('sentry-trace', sentryTrace); - if (sentryBaggageHeader) { + if (baggage) { const prevBaggageHeader = newHeaders.get('baggage'); if (prevBaggageHeader) { const prevHeaderStrippedFromSentryBaggage = stripBaggageHeaderOfSentryBaggageValues(prevBaggageHeader); @@ -160,16 +140,14 @@ export function addTracingHeadersToFetchRequest( 'baggage', // If there are non-sentry entries (i.e. if the stripped string is non-empty/truthy) combine the stripped header and sentry baggage header // otherwise just set the sentry baggage header - prevHeaderStrippedFromSentryBaggage - ? `${prevHeaderStrippedFromSentryBaggage},${sentryBaggageHeader}` - : sentryBaggageHeader, + prevHeaderStrippedFromSentryBaggage ? `${prevHeaderStrippedFromSentryBaggage},${baggage}` : baggage, ); } else { - newHeaders.set('baggage', sentryBaggageHeader); + newHeaders.set('baggage', baggage); } } - return newHeaders as PolymorphicRequestHeaders; + return newHeaders; } else if (Array.isArray(headers)) { const newHeaders = [ ...headers @@ -187,13 +165,13 @@ export function addTracingHeadersToFetchRequest( } }), // Attach the new sentry-trace header - ['sentry-trace', sentryTraceHeader], + ['sentry-trace', sentryTrace], ]; - if (sentryBaggageHeader) { + if (baggage) { // If there are multiple entries with the same key, the browser will merge the values into a single request header. // Its therefore safe to simply push a "baggage" entry, even though there might already be another baggage header. - newHeaders.push(['baggage', sentryBaggageHeader]); + newHeaders.push(['baggage', baggage]); } return newHeaders as PolymorphicRequestHeaders; @@ -211,18 +189,39 @@ export function addTracingHeadersToFetchRequest( newBaggageHeaders.push(stripBaggageHeaderOfSentryBaggageValues(existingBaggageHeader)); } - if (sentryBaggageHeader) { - newBaggageHeaders.push(sentryBaggageHeader); + if (baggage) { + newBaggageHeaders.push(baggage); } return { ...(headers as Exclude), - 'sentry-trace': sentryTraceHeader, + 'sentry-trace': sentryTrace, baggage: newBaggageHeaders.length > 0 ? newBaggageHeaders.join(',') : undefined, }; } } +/** + * Adds sentry-trace and baggage headers to the various forms of fetch headers. + * + * @deprecated This function will not be exported anymore in v9. + */ +export function addTracingHeadersToFetchRequest( + request: string | unknown, + _client: Client | undefined, + _scope: Scope | undefined, + fetchOptionsObj: { + headers?: + | { + [key: string]: string[] | string | undefined; + } + | PolymorphicRequestHeaders; + }, + span?: Span, +): PolymorphicRequestHeaders | undefined { + return _addTracingHeadersToFetchRequest(request as Request, fetchOptionsObj, span); +} + function getFullURL(url: string): string | undefined { try { const parsed = new URL(url); @@ -260,3 +259,11 @@ function stripBaggageHeaderOfSentryBaggageValues(baggageHeader: string): string .join(',') ); } + +function isRequest(request: unknown): request is Request { + return typeof Request !== 'undefined' && isInstanceOf(request, Request); +} + +function isHeaders(headers: unknown): headers is Headers { + return typeof Headers !== 'undefined' && isInstanceOf(headers, Headers); +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index c56c568d092f..eed487e961ba 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -40,6 +40,7 @@ export { withScope, withIsolationScope, getClient, + getTraceContextFromScope, } from './currentScopes'; export { getDefaultCurrentScope, @@ -111,7 +112,11 @@ export type { MetricData } from '@sentry/types'; export { metricsDefault } from './metrics/exports-default'; export { BrowserMetricsAggregator } from './metrics/browser-aggregator'; export { getMetricSummaryJsonForSpan } from './metrics/metric-summary'; -export { addTracingHeadersToFetchRequest, instrumentFetchRequest } from './fetch'; +export { + // eslint-disable-next-line deprecation/deprecation + addTracingHeadersToFetchRequest, + instrumentFetchRequest, +} from './fetch'; export { trpcMiddleware } from './trpc'; export { captureFeedback } from './feedback'; diff --git a/packages/core/src/server-runtime-client.ts b/packages/core/src/server-runtime-client.ts index 93c1051ed6ec..c044c1be7bc1 100644 --- a/packages/core/src/server-runtime-client.ts +++ b/packages/core/src/server-runtime-client.ts @@ -14,12 +14,12 @@ import type { import { BaseClient } from './baseclient'; import { createCheckInEnvelope } from './checkin'; -import { getIsolationScope } from './currentScopes'; +import { getIsolationScope, getTraceContextFromScope } from './currentScopes'; import { DEBUG_BUILD } from './debug-build'; import type { Scope } from './scope'; import { SessionFlusher } from './sessionflusher'; import { - getDynamicSamplingContextFromClient, + getDynamicSamplingContextFromScope, getDynamicSamplingContextFromSpan, registerSpanErrorInstrumentation, } from './tracing'; @@ -28,7 +28,7 @@ import { logger } from './utils-hoist/logger'; import { uuid4 } from './utils-hoist/misc'; import { resolvedSyncPromise } from './utils-hoist/syncpromise'; import { _getSpanForScope } from './utils/spanOnScope'; -import { getRootSpan, spanToTraceContext } from './utils/spanUtils'; +import { spanToTraceContext } from './utils/spanUtils'; export interface ServerRuntimeClientOptions extends ClientOptions { platform?: string; @@ -251,7 +251,7 @@ export class ServerRuntimeClient< } /** Extract trace information from scope */ - private _getTraceInfoFromScope( + protected _getTraceInfoFromScope( scope: Scope | undefined, ): [dynamicSamplingContext: Partial | undefined, traceContext: TraceContext | undefined] { if (!scope) { @@ -259,22 +259,11 @@ export class ServerRuntimeClient< } const span = _getSpanForScope(scope); - if (span) { - const rootSpan = getRootSpan(span); - const samplingContext = getDynamicSamplingContextFromSpan(rootSpan); - return [samplingContext, spanToTraceContext(rootSpan)]; - } - - const { traceId, spanId, parentSpanId, dsc } = scope.getPropagationContext(); - const traceContext: TraceContext = { - trace_id: traceId, - span_id: spanId, - parent_span_id: parentSpanId, - }; - if (dsc) { - return [dsc, traceContext]; - } - return [getDynamicSamplingContextFromClient(traceId, this), traceContext]; + const traceContext = span ? spanToTraceContext(span) : getTraceContextFromScope(scope); + const dynamicSamplingContext = span + ? getDynamicSamplingContextFromSpan(span) + : getDynamicSamplingContextFromScope(this, scope); + return [dynamicSamplingContext, traceContext]; } } diff --git a/packages/core/src/tracing/dynamicSamplingContext.ts b/packages/core/src/tracing/dynamicSamplingContext.ts index 1e8ca0448b3b..a1bb008a2572 100644 --- a/packages/core/src/tracing/dynamicSamplingContext.ts +++ b/packages/core/src/tracing/dynamicSamplingContext.ts @@ -1,4 +1,4 @@ -import type { Client, DynamicSamplingContext, Span } from '@sentry/types'; +import type { Client, DynamicSamplingContext, Scope, Span } from '@sentry/types'; import { DEFAULT_ENVIRONMENT } from '../constants'; import { getClient } from '../currentScopes'; @@ -51,6 +51,14 @@ export function getDynamicSamplingContextFromClient(trace_id: string, client: Cl return dsc; } +/** + * Get the dynamic sampling context for the currently active scopes. + */ +export function getDynamicSamplingContextFromScope(client: Client, scope: Scope): Partial { + const propagationContext = scope.getPropagationContext(); + return propagationContext.dsc || getDynamicSamplingContextFromClient(propagationContext.traceId, client); +} + /** * Creates a dynamic sampling context from a span (and client and scope) * @@ -64,8 +72,6 @@ export function getDynamicSamplingContextFromSpan(span: Span): Readonly): Client { + getCurrentScope().setPropagationContext({ + traceId: SCOPE_TRACE_ID, + spanId: SCOPE_SPAN_ID, + }); -const mockedScope = { - getPropagationContext: () => ({ - traceId: '123', - }), -} as any; + const options = getDefaultTestClientOptions({ + dsn, + tracesSampleRate: 1, + ...opts, + }); + const client = new TestClient(options); + setCurrentClient(client); + client.init(); + + return client; +} describe('getTraceData', () => { beforeEach(() => { - jest.spyOn(SentryCoreExports, 'isEnabled').mockReturnValue(true); + setAsyncContextStrategy(undefined); + getCurrentScope().clear(); + getIsolationScope().clear(); + getGlobalScope().clear(); + getCurrentScope().setClient(undefined); }); afterEach(() => { jest.clearAllMocks(); }); - it('returns the tracing data from the span, if a span is available', () => { - { - jest.spyOn(SentryCoreTracing, 'getDynamicSamplingContextFromSpan').mockReturnValueOnce({ - environment: 'production', - }); - jest.spyOn(SentryCoreSpanUtils, 'getActiveSpan').mockImplementationOnce(() => mockedSpan); - jest.spyOn(SentryCoreCurrentScopes, 'getCurrentScope').mockImplementationOnce(() => mockedScope); + it('uses the ACS implementation, if available', () => { + setupClient(); + + const carrier = getMainCarrier(); + + const customFn = jest.fn((options?: { span?: Span }) => { + expect(options).toEqual({ span: undefined }); + return { + 'sentry-trace': 'abc', + baggage: 'xyz', + }; + }) as typeof getTraceData; + + const acs = { + ...getAsyncContextStrategy(carrier), + getTraceData: customFn, + }; + setAsyncContextStrategy(acs); + + const span = new SentrySpan({ + traceId: '12345678901234567890123456789012', + spanId: '1234567890123456', + sampled: true, + }); + withActiveSpan(span, () => { const data = getTraceData(); expect(data).toEqual({ - 'sentry-trace': '12345678901234567890123456789012-1234567890123456-1', - baggage: 'sentry-environment=production', + 'sentry-trace': 'abc', + baggage: 'xyz', }); - } + }); }); - it('returns propagationContext DSC data if no span is available', () => { - jest.spyOn(SentryCoreSpanUtils, 'getActiveSpan').mockImplementationOnce(() => undefined); - jest.spyOn(SentryCoreCurrentScopes, 'getCurrentScope').mockImplementationOnce( - () => - ({ - getPropagationContext: () => ({ - traceId: '12345678901234567890123456789012', - sampled: true, - spanId: '1234567890123456', - dsc: { - environment: 'staging', - public_key: 'key', - trace_id: '12345678901234567890123456789012', - }, - }), - }) as any, - ); - jest.spyOn(SentryCoreCurrentScopes, 'getClient').mockImplementationOnce(() => mockedClient); + it('passes span to ACS implementation, if available', () => { + setupClient(); - const traceData = getTraceData(); + const carrier = getMainCarrier(); - expect(traceData).toEqual({ - 'sentry-trace': expect.stringMatching(/12345678901234567890123456789012-(.{16})-1/), - baggage: 'sentry-environment=staging,sentry-public_key=key,sentry-trace_id=12345678901234567890123456789012', + const span = new SentrySpan({ + traceId: '12345678901234567890123456789012', + spanId: '1234567890123456', + sampled: true, }); - }); - it('returns only the `sentry-trace` value if no DSC is available', () => { - jest.spyOn(SentryCoreTracing, 'getDynamicSamplingContextFromClient').mockReturnValueOnce({ - trace_id: '', - public_key: undefined, + const customFn = jest.fn((options?: { span?: Span }) => { + expect(options).toEqual({ span }); + return { + 'sentry-trace': 'abc', + baggage: 'xyz', + }; + }) as typeof getTraceData; + + const acs = { + ...getAsyncContextStrategy(carrier), + getTraceData: customFn, + }; + setAsyncContextStrategy(acs); + + const data = getTraceData({ span }); + + expect(data).toEqual({ + 'sentry-trace': 'abc', + baggage: 'xyz', }); + }); - // @ts-expect-error - we don't need to provide all the properties - jest.spyOn(SentryCoreSpanUtils, 'getActiveSpan').mockImplementationOnce(() => ({ - isRecording: () => true, - spanContext: () => { - return { - traceId: '12345678901234567890123456789012', - spanId: '1234567890123456', - traceFlags: TRACE_FLAG_SAMPLED, - }; - }, - })); + it('returns the tracing data from the span, if a span is available', () => { + setupClient(); - jest.spyOn(SentryCoreCurrentScopes, 'getCurrentScope').mockImplementationOnce(() => mockedScope); - jest.spyOn(SentryCoreCurrentScopes, 'getClient').mockImplementationOnce(() => mockedClient); + const span = new SentrySpan({ + traceId: '12345678901234567890123456789012', + spanId: '1234567890123456', + sampled: true, + }); - const traceData = getTraceData(); + withActiveSpan(span, () => { + const data = getTraceData(); - expect(traceData).toEqual({ - 'sentry-trace': '12345678901234567890123456789012-1234567890123456-1', + expect(data).toEqual({ + 'sentry-trace': '12345678901234567890123456789012-1234567890123456-1', + baggage: + 'sentry-environment=production,sentry-public_key=123,sentry-trace_id=12345678901234567890123456789012,sentry-sampled=true', + }); }); }); - it('returns only the `sentry-trace` tag if no DSC is available without a client', () => { - jest.spyOn(SentryCoreTracing, 'getDynamicSamplingContextFromClient').mockReturnValueOnce({ - trace_id: '', - public_key: undefined, + it('allows to pass a span directly', () => { + setupClient(); + + const span = new SentrySpan({ + traceId: '12345678901234567890123456789012', + spanId: '1234567890123456', + sampled: true, + }); + + const data = getTraceData({ span }); + + expect(data).toEqual({ + 'sentry-trace': '12345678901234567890123456789012-1234567890123456-1', + baggage: + 'sentry-environment=production,sentry-public_key=123,sentry-trace_id=12345678901234567890123456789012,sentry-sampled=true', }); + }); - // @ts-expect-error - we don't need to provide all the properties - jest.spyOn(SentryCoreSpanUtils, 'getActiveSpan').mockImplementationOnce(() => ({ - isRecording: () => true, - spanContext: () => { - return { - traceId: '12345678901234567890123456789012', - spanId: '1234567890123456', - traceFlags: TRACE_FLAG_SAMPLED, - }; + it('returns propagationContext DSC data if no span is available', () => { + setupClient(); + + getCurrentScope().setPropagationContext({ + traceId: '12345678901234567890123456789012', + sampled: true, + spanId: '1234567890123456', + dsc: { + environment: 'staging', + public_key: 'key', + trace_id: '12345678901234567890123456789012', }, - })); - jest.spyOn(SentryCoreCurrentScopes, 'getCurrentScope').mockImplementationOnce(() => mockedScope); - jest.spyOn(SentryCoreCurrentScopes, 'getClient').mockImplementationOnce(() => undefined); + }); const traceData = getTraceData(); expect(traceData).toEqual({ 'sentry-trace': '12345678901234567890123456789012-1234567890123456-1', + baggage: 'sentry-environment=staging,sentry-public_key=key,sentry-trace_id=12345678901234567890123456789012', }); - expect('baggage' in traceData).toBe(false); }); - it('returns an empty object if the `sentry-trace` value is invalid', () => { - // @ts-expect-error - we don't need to provide all the properties - jest.spyOn(SentryCoreSpanUtils, 'getActiveSpan').mockImplementationOnce(() => ({ - isRecording: () => true, - spanContext: () => { - return { - traceId: '1234567890123456789012345678901+', - spanId: '1234567890123456', - traceFlags: TRACE_FLAG_SAMPLED, - }; - }, - })); + it('returns frozen DSC from SentrySpan if available', () => { + setupClient(); + + const span = new SentrySpan({ + traceId: '12345678901234567890123456789012', + spanId: '1234567890123456', + sampled: true, + }); + + freezeDscOnSpan(span, { + environment: 'test-dev', + public_key: '456', + trace_id: '12345678901234567890123456789088', + }); + + withActiveSpan(span, () => { + const data = getTraceData(); + + expect(data).toEqual({ + 'sentry-trace': '12345678901234567890123456789012-1234567890123456-1', + baggage: 'sentry-environment=test-dev,sentry-public_key=456,sentry-trace_id=12345678901234567890123456789088', + }); + }); + }); + + it('works with an OTEL span with frozen DSC in traceState', () => { + setupClient(); + + const traceId = '12345678901234567890123456789099'; + const spanId = '1234567890123499'; + const span = new SentrySpan({ + traceId, + spanId, + sampled: true, + }); + + span.spanContext = () => { + const traceState = { + set: () => traceState, + unset: () => traceState, + get: (key: string) => { + if (key === 'sentry.dsc') { + return 'sentry-environment=test-dev,sentry-public_key=456,sentry-trace_id=12345678901234567890123456789088'; + } + return undefined; + }, + serialize: () => '', + }; + + return { + traceId, + spanId, + sampled: true, + traceFlags: 1, + traceState, + }; + }; + + withActiveSpan(span, () => { + const data = getTraceData(); + + expect(data).toEqual({ + 'sentry-trace': '12345678901234567890123456789099-1234567890123499-1', + baggage: 'sentry-environment=test-dev,sentry-public_key=456,sentry-trace_id=12345678901234567890123456789088', + }); + }); + }); + + it('returns empty object without a client', () => { const traceData = getTraceData(); expect(traceData).toEqual({}); }); + it('returns an empty object if the `sentry-trace` value is invalid', () => { + // Invalid traceID + const traceId = '1234567890123456789012345678901+'; + const spanId = '1234567890123499'; + + const span = new SentrySpan({ + traceId, + spanId, + sampled: true, + }); + + withActiveSpan(span, () => { + const data = getTraceData(); + expect(data).toEqual({}); + }); + }); + it('returns an empty object if the SDK is disabled', () => { - jest.spyOn(SentryCoreExports, 'isEnabled').mockReturnValueOnce(false); + setupClient({ dsn: undefined }); const traceData = getTraceData(); diff --git a/packages/node/src/sdk/client.ts b/packages/node/src/sdk/client.ts index 8179d40e2819..b4730ac0f07c 100644 --- a/packages/node/src/sdk/client.ts +++ b/packages/node/src/sdk/client.ts @@ -2,9 +2,10 @@ import * as os from 'node:os'; import type { Tracer } from '@opentelemetry/api'; import { trace } from '@opentelemetry/api'; import type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; -import type { ServerRuntimeClientOptions } from '@sentry/core'; -import { SDK_VERSION, ServerRuntimeClient, applySdkMetadata } from '@sentry/core'; -import { logger } from '@sentry/core'; +import type { Scope, ServerRuntimeClientOptions } from '@sentry/core'; +import { SDK_VERSION, ServerRuntimeClient, applySdkMetadata, logger } from '@sentry/core'; +import { getTraceContextForScope } from '@sentry/opentelemetry'; +import type { DynamicSamplingContext, TraceContext } from '@sentry/types'; import { isMainThread, threadId } from 'worker_threads'; import { DEBUG_BUILD } from '../debug-build'; import type { NodeClientOptions } from '../types'; @@ -115,4 +116,15 @@ export class NodeClient extends ServerRuntimeClient { process.on('beforeExit', this._clientReportOnExitFlushListener); } } + + /** Custom implementation for OTEL, so we can handle scope-span linking. */ + protected _getTraceInfoFromScope( + scope: Scope | undefined, + ): [dynamicSamplingContext: Partial | undefined, traceContext: TraceContext | undefined] { + if (!scope) { + return [undefined, undefined]; + } + + return getTraceContextForScope(this, scope); + } } diff --git a/packages/opentelemetry/src/index.ts b/packages/opentelemetry/src/index.ts index 98460b575c8d..55f657061989 100644 --- a/packages/opentelemetry/src/index.ts +++ b/packages/opentelemetry/src/index.ts @@ -27,7 +27,14 @@ export { enhanceDscWithOpenTelemetryRootSpanName } from './utils/enhanceDscWithO export { generateSpanContextForPropagationContext } from './utils/generateSpanContextForPropagationContext'; export { getActiveSpan } from './utils/getActiveSpan'; -export { startSpan, startSpanManual, startInactiveSpan, withActiveSpan, continueTrace } from './trace'; +export { + startSpan, + startSpanManual, + startInactiveSpan, + withActiveSpan, + continueTrace, + getTraceContextForScope, +} from './trace'; export { suppressTracing } from './utils/suppressTracing'; diff --git a/packages/opentelemetry/src/propagator.ts b/packages/opentelemetry/src/propagator.ts index 054a348fd7b5..6c4009888416 100644 --- a/packages/opentelemetry/src/propagator.ts +++ b/packages/opentelemetry/src/propagator.ts @@ -5,15 +5,10 @@ import { propagation, trace } from '@opentelemetry/api'; import { W3CBaggagePropagator, isTracingSuppressed } from '@opentelemetry/core'; import { ATTR_URL_FULL, SEMATTRS_HTTP_URL } from '@opentelemetry/semantic-conventions'; import type { continueTrace } from '@sentry/core'; +import { getDynamicSamplingContextFromScope } from '@sentry/core'; import { getRootSpan } from '@sentry/core'; import { spanToJSON } from '@sentry/core'; -import { - getClient, - getCurrentScope, - getDynamicSamplingContextFromClient, - getDynamicSamplingContextFromSpan, - getIsolationScope, -} from '@sentry/core'; +import { getClient, getCurrentScope, getDynamicSamplingContextFromSpan, getIsolationScope } from '@sentry/core'; import { LRUMap, SENTRY_BAGGAGE_KEY_PREFIX, @@ -191,7 +186,10 @@ export class SentryPropagator extends W3CBaggagePropagator { } } -function getInjectionData(context: Context): { +/** + * Get propagation injection data for the given context. + */ +export function getInjectionData(context: Context): { dynamicSamplingContext: Partial | undefined; traceId: string | undefined; spanId: string | undefined; @@ -204,8 +202,7 @@ function getInjectionData(context: Context): { if (span && !spanIsRemote) { const spanContext = span.spanContext(); - const propagationContext = getPropagationContextFromSpan(span); - const dynamicSamplingContext = getDynamicSamplingContext(propagationContext, spanContext.traceId); + const dynamicSamplingContext = getDynamicSamplingContextFromSpan(span); return { dynamicSamplingContext, traceId: spanContext.traceId, @@ -216,9 +213,10 @@ function getInjectionData(context: Context): { // Else we try to use the propagation context from the scope const scope = getScopesFromContext(context)?.scope || getCurrentScope(); + const client = getClient(); const propagationContext = scope.getPropagationContext(); - const dynamicSamplingContext = getDynamicSamplingContext(propagationContext, propagationContext.traceId); + const dynamicSamplingContext = client ? getDynamicSamplingContextFromScope(client, scope) : undefined; return { dynamicSamplingContext, traceId: propagationContext.traceId, @@ -227,26 +225,6 @@ function getInjectionData(context: Context): { }; } -/** Get the DSC from a context, or fall back to use the one from the client. */ -function getDynamicSamplingContext( - propagationContext: PropagationContext, - traceId: string | undefined, -): Partial | undefined { - // If we have a DSC on the propagation context, we just use it - if (propagationContext?.dsc) { - return propagationContext.dsc; - } - - // Else, we try to generate a new one - const client = getClient(); - - if (client) { - return getDynamicSamplingContextFromClient(traceId || propagationContext.traceId, client); - } - - return undefined; -} - function getContextWithRemoteActiveSpan( ctx: Context, { sentryTrace, baggage }: Parameters[0], diff --git a/packages/opentelemetry/src/trace.ts b/packages/opentelemetry/src/trace.ts index 1ed0fa6a1322..3fa994a74de8 100644 --- a/packages/opentelemetry/src/trace.ts +++ b/packages/opentelemetry/src/trace.ts @@ -7,12 +7,15 @@ import { continueTrace as baseContinueTrace, getClient, getCurrentScope, + getDynamicSamplingContextFromScope, getDynamicSamplingContextFromSpan, getRootSpan, + getTraceContextFromScope, handleCallbackErrors, spanToJSON, + spanToTraceContext, } from '@sentry/core'; -import type { Client, Scope, Span as SentrySpan } from '@sentry/types'; +import type { Client, DynamicSamplingContext, Scope, Span as SentrySpan, TraceContext } from '@sentry/types'; import { continueTraceAsRemoteSpan } from './propagator'; import type { OpenTelemetryClient, OpenTelemetrySpanContext } from './types'; @@ -279,6 +282,25 @@ export function continueTrace(options: Parameters[0 }); } +/** + * Get the trace context for a given scope. + * We have a custom implemention here because we need an OTEL-specific way to get the span from a scope. + */ +export function getTraceContextForScope( + client: Client, + scope: Scope, +): [dynamicSamplingContext: Partial, traceContext: TraceContext] { + const ctx = getContextFromScope(scope); + const span = ctx && trace.getSpan(ctx); + + const traceContext = span ? spanToTraceContext(span) : getTraceContextFromScope(scope); + + const dynamicSamplingContext = span + ? getDynamicSamplingContextFromSpan(span) + : getDynamicSamplingContextFromScope(client, scope); + return [dynamicSamplingContext, traceContext]; +} + function getActiveSpanWrapper(parentSpan: Span | SentrySpan | undefined | null): (callback: () => T) => T { return parentSpan !== undefined ? (callback: () => T) => { diff --git a/packages/opentelemetry/src/utils/getTraceData.ts b/packages/opentelemetry/src/utils/getTraceData.ts index 8d91c74bd294..c79fc2a6e957 100644 --- a/packages/opentelemetry/src/utils/getTraceData.ts +++ b/packages/opentelemetry/src/utils/getTraceData.ts @@ -1,22 +1,30 @@ import * as api from '@opentelemetry/api'; -import { dropUndefinedKeys } from '@sentry/core'; -import type { SerializedTraceData } from '@sentry/types'; +import { + dynamicSamplingContextToSentryBaggageHeader, + generateSentryTraceHeader, + getCapturedScopesOnSpan, +} from '@sentry/core'; +import type { SerializedTraceData, Span } from '@sentry/types'; +import { getInjectionData } from '../propagator'; +import { getContextFromScope } from './contextData'; /** * Otel-specific implementation of `getTraceData`. * @see `@sentry/core` version of `getTraceData` for more information */ -export function getTraceData(): SerializedTraceData { - const headersObject: Record = {}; +export function getTraceData({ span }: { span?: Span } = {}): SerializedTraceData { + let ctx = api.context.active(); - api.propagation.inject(api.context.active(), headersObject); - - if (!headersObject['sentry-trace']) { - return {}; + if (span) { + const { scope } = getCapturedScopesOnSpan(span); + // fall back to current context if for whatever reason we can't find the one of the span + ctx = (scope && getContextFromScope(scope)) || api.trace.setSpan(api.context.active(), span); } - return dropUndefinedKeys({ - 'sentry-trace': headersObject['sentry-trace'], - baggage: headersObject.baggage, - }); + const { traceId, spanId, sampled, dynamicSamplingContext } = getInjectionData(ctx); + + return { + 'sentry-trace': generateSentryTraceHeader(traceId, spanId, sampled), + baggage: dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext), + }; } diff --git a/packages/opentelemetry/test/utils/getTraceData.test.ts b/packages/opentelemetry/test/utils/getTraceData.test.ts new file mode 100644 index 000000000000..e0f2270d8e22 --- /dev/null +++ b/packages/opentelemetry/test/utils/getTraceData.test.ts @@ -0,0 +1,93 @@ +import { context, trace } from '@opentelemetry/api'; +import { getCurrentScope, setAsyncContextStrategy } from '@sentry/core'; +import { getTraceData } from '../../src/utils/getTraceData'; +import { makeTraceState } from '../../src/utils/makeTraceState'; +import { cleanupOtel, mockSdkInit } from '../helpers/mockSdkInit'; + +describe('getTraceData', () => { + beforeEach(() => { + setAsyncContextStrategy(undefined); + mockSdkInit(); + }); + + afterEach(() => { + cleanupOtel(); + jest.clearAllMocks(); + }); + + it('returns the tracing data from the span, if a span is available', () => { + const ctx = trace.setSpanContext(context.active(), { + traceId: '12345678901234567890123456789012', + spanId: '1234567890123456', + traceFlags: 1, + }); + + context.with(ctx, () => { + const data = getTraceData(); + + expect(data).toEqual({ + 'sentry-trace': '12345678901234567890123456789012-1234567890123456-1', + baggage: + 'sentry-environment=production,sentry-public_key=username,sentry-trace_id=12345678901234567890123456789012,sentry-sampled=true', + }); + }); + }); + + it('allows to pass a span directly', () => { + const ctx = trace.setSpanContext(context.active(), { + traceId: '12345678901234567890123456789012', + spanId: '1234567890123456', + traceFlags: 1, + }); + + const span = trace.getSpan(ctx)!; + + const data = getTraceData({ span }); + + expect(data).toEqual({ + 'sentry-trace': '12345678901234567890123456789012-1234567890123456-1', + baggage: + 'sentry-environment=production,sentry-public_key=username,sentry-trace_id=12345678901234567890123456789012,sentry-sampled=true', + }); + }); + + it('returns propagationContext DSC data if no span is available', () => { + getCurrentScope().setPropagationContext({ + traceId: '12345678901234567890123456789012', + sampled: true, + spanId: '1234567890123456', + dsc: { + environment: 'staging', + public_key: 'key', + trace_id: '12345678901234567890123456789012', + }, + }); + + const traceData = getTraceData(); + + expect(traceData).toEqual({ + 'sentry-trace': '12345678901234567890123456789012-1234567890123456-1', + baggage: 'sentry-environment=staging,sentry-public_key=key,sentry-trace_id=12345678901234567890123456789012', + }); + }); + + it('works with an span with frozen DSC in traceState', () => { + const ctx = trace.setSpanContext(context.active(), { + traceId: '12345678901234567890123456789012', + spanId: '1234567890123456', + traceFlags: 1, + traceState: makeTraceState({ + dsc: { environment: 'test-dev', public_key: '456', trace_id: '12345678901234567890123456789088' }, + }), + }); + + context.with(ctx, () => { + const data = getTraceData(); + + expect(data).toEqual({ + 'sentry-trace': '12345678901234567890123456789012-1234567890123456-1', + baggage: 'sentry-environment=test-dev,sentry-public_key=456,sentry-trace_id=12345678901234567890123456789088', + }); + }); + }); +}); diff --git a/packages/remix/src/utils/instrumentServer.ts b/packages/remix/src/utils/instrumentServer.ts index ac1382bc5e00..fbd874a12df9 100644 --- a/packages/remix/src/utils/instrumentServer.ts +++ b/packages/remix/src/utils/instrumentServer.ts @@ -6,16 +6,16 @@ import { getActiveSpan, getClient, getRootSpan, + getTraceData, hasTracingEnabled, setHttpStatus, spanToJSON, - spanToTraceHeader, startSpan, winterCGRequestToRequestData, withIsolationScope, } from '@sentry/core'; -import { dynamicSamplingContextToSentryBaggageHeader, fill, isNodeEnv, loadModule, logger } from '@sentry/core'; -import { continueTrace, getDynamicSamplingContextFromSpan } from '@sentry/opentelemetry'; +import { fill, isNodeEnv, loadModule, logger } from '@sentry/core'; +import { continueTrace } from '@sentry/opentelemetry'; import type { RequestEventData, TransactionSource, WrappedFunction } from '@sentry/types'; import type { Span } from '@sentry/types'; @@ -204,18 +204,13 @@ function getTraceAndBaggage(): { sentryTrace?: string; sentryBaggage?: string; } { - if (isNodeEnv() && hasTracingEnabled()) { - const span = getActiveSpan(); - const rootSpan = span && getRootSpan(span); + if (isNodeEnv()) { + const traceData = getTraceData(); - if (rootSpan) { - const dynamicSamplingContext = getDynamicSamplingContextFromSpan(rootSpan); - - return { - sentryTrace: spanToTraceHeader(span), - sentryBaggage: dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext), - }; - } + return { + sentryTrace: traceData['sentry-trace'], + sentryBaggage: traceData.baggage, + }; } return {}; diff --git a/packages/remix/test/integration/test/client/root-loader.test.ts b/packages/remix/test/integration/test/client/root-loader.test.ts index 53b7648756e5..431195e8eab7 100644 --- a/packages/remix/test/integration/test/client/root-loader.test.ts +++ b/packages/remix/test/integration/test/client/root-loader.test.ts @@ -25,8 +25,8 @@ test('should inject `sentry-trace` and `baggage` into root loader returning an e const { sentryTrace, sentryBaggage } = await extractTraceAndBaggageFromMeta(page); - expect(sentryTrace).toEqual(expect.any(String)); - expect(sentryBaggage).toEqual(expect.any(String)); + expect(sentryTrace).toMatch(/.+/); + expect(sentryBaggage).toMatch(/.+/); const rootData = (await getRouteData(page))['root']; @@ -41,8 +41,8 @@ test('should inject `sentry-trace` and `baggage` into root loader returning a pl const { sentryTrace, sentryBaggage } = await extractTraceAndBaggageFromMeta(page); - expect(sentryTrace).toEqual(expect.any(String)); - expect(sentryBaggage).toEqual(expect.any(String)); + expect(sentryTrace).toMatch(/.+/); + expect(sentryBaggage).toMatch(/.+/); const rootData = (await getRouteData(page))['root']; @@ -59,8 +59,8 @@ test('should inject `sentry-trace` and `baggage` into root loader returning a `J const { sentryTrace, sentryBaggage } = await extractTraceAndBaggageFromMeta(page); - expect(sentryTrace).toEqual(expect.any(String)); - expect(sentryBaggage).toEqual(expect.any(String)); + expect(sentryTrace).toMatch(/.+/); + expect(sentryBaggage).toMatch(/.+/); const rootData = (await getRouteData(page))['root']; @@ -77,8 +77,8 @@ test('should inject `sentry-trace` and `baggage` into root loader returning a de const { sentryTrace, sentryBaggage } = await extractTraceAndBaggageFromMeta(page); - expect(sentryTrace).toEqual(expect.any(String)); - expect(sentryBaggage).toEqual(expect.any(String)); + expect(sentryTrace).toMatch(/.+/); + expect(sentryBaggage).toMatch(/.+/); const rootData = (await getRouteData(page))['root']; @@ -93,8 +93,8 @@ test('should inject `sentry-trace` and `baggage` into root loader returning `nul const { sentryTrace, sentryBaggage } = await extractTraceAndBaggageFromMeta(page); - expect(sentryTrace).toEqual(expect.any(String)); - expect(sentryBaggage).toEqual(expect.any(String)); + expect(sentryTrace).toMatch(/.+/); + expect(sentryBaggage).toMatch(/.+/); const rootData = (await getRouteData(page))['root']; @@ -109,8 +109,8 @@ test('should inject `sentry-trace` and `baggage` into root loader returning `und const { sentryTrace, sentryBaggage } = await extractTraceAndBaggageFromMeta(page); - expect(sentryTrace).toEqual(expect.any(String)); - expect(sentryBaggage).toEqual(expect.any(String)); + expect(sentryTrace).toMatch(/.+/); + expect(sentryBaggage).toMatch(/.+/); const rootData = (await getRouteData(page))['root']; @@ -130,8 +130,8 @@ test('should inject `sentry-trace` and `baggage` into root loader throwing a red const { sentryTrace, sentryBaggage } = await extractTraceAndBaggageFromMeta(page); - expect(sentryTrace).toEqual(expect.any(String)); - expect(sentryBaggage).toEqual(expect.any(String)); + expect(sentryTrace).toMatch(/.+/); + expect(sentryBaggage).toMatch(/.+/); const rootData = (await getRouteData(page))['root']; @@ -151,8 +151,8 @@ test('should inject `sentry-trace` and `baggage` into root loader returning a re const { sentryTrace, sentryBaggage } = await extractTraceAndBaggageFromMeta(page); - expect(sentryTrace).toEqual(expect.any(String)); - expect(sentryBaggage).toEqual(expect.any(String)); + expect(sentryTrace).toMatch(/.+/); + expect(sentryBaggage).toMatch(/.+/); const rootData = (await getRouteData(page))['root'];