diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/standalone-without-baggage/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/standalone-without-baggage/init.js new file mode 100644 index 000000000000..e2dd69ede824 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/standalone-without-baggage/init.js @@ -0,0 +1,10 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [Sentry.browserTracingIntegration()], + tracePropagationTargets: ['sentry-test-external.io'], + tracesSampleRate: 1, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/standalone-without-baggage/subject.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/standalone-without-baggage/subject.js new file mode 100644 index 000000000000..179c0203e14c --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/standalone-without-baggage/subject.js @@ -0,0 +1,3 @@ +Sentry.startSpan({ name: 'standalone_span', experimental: { standalone: true } }, () => { + fetch('http://sentry-test-external.io'); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/standalone-without-baggage/template.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/standalone-without-baggage/template.html new file mode 100644 index 000000000000..f98e1c83e643 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/standalone-without-baggage/template.html @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/standalone-without-baggage/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/standalone-without-baggage/test.ts new file mode 100644 index 000000000000..2700c8aa5077 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/standalone-without-baggage/test.ts @@ -0,0 +1,48 @@ +import { expect } from '@playwright/test'; +import type { SpanEnvelope } from '@sentry/core'; +import { sentryTest } from '../../../../utils/fixtures'; +import { + getMultipleSentryEnvelopeRequests, + properFullEnvelopeRequestParser, + shouldSkipTracingTest, +} from '../../../../utils/helpers'; + +const TRACE_ID = '12345678901234567890123456789012'; +const OUTGOING_REQUEST_URL = 'http://sentry-test-external.io'; + +sentryTest( + 'omits the trace envelope header when a standalone span continues a trace without baggage', + async ({ getLocalTestUrl, page }) => { + sentryTest.skip(shouldSkipTracingTest()); + + const url = await getLocalTestUrl({ testDir: __dirname }); + await page.route(OUTGOING_REQUEST_URL, route => route.fulfill({ status: 200, body: 'ok' })); + const outgoingRequestPromise = page.waitForRequest(OUTGOING_REQUEST_URL); + + const [spanEnvelope] = await getMultipleSentryEnvelopeRequests( + page, + 1, + { url, envelopeType: 'span' }, + properFullEnvelopeRequestParser, + ); + const outgoingRequest = await outgoingRequestPromise; + + expect(spanEnvelope[0]).toEqual({ + sent_at: expect.any(String), + }); + + // To be clear: This is _expected_ behavior, not a bug. + // SDKs must assume that an incoming `sentry-trace` but no `baggage` meta tag means that the + // trace was started from an SDK that's not yet compatible with the DSC or baggage propagation. + // The test demonstrates that the SDK as expected continues the trace but does not send a `trace` + // header, nor a baggage header. + expect(spanEnvelope[0].trace).toBeUndefined(); + + expect(spanEnvelope[1]).toHaveLength(1); + expect(spanEnvelope[1][0][1].trace_id).toBe(TRACE_ID); + + const outgoingRequestHeaders = outgoingRequest.headers(); + expect(outgoingRequestHeaders['sentry-trace']).toMatch(new RegExp(`^${TRACE_ID}-[\\da-f]{16}-1$`)); + expect(outgoingRequestHeaders['baggage']).toBeUndefined(); + }, +); diff --git a/packages/core/src/fetch.ts b/packages/core/src/fetch.ts index bfe78d499260..bb69a1b0025e 100644 --- a/packages/core/src/fetch.ts +++ b/packages/core/src/fetch.ts @@ -222,7 +222,11 @@ export function _INTERNAL_getTracingHeadersForFetchRequest( const originalHeaders = fetchOptionsObj.headers || (isRequest(request) ? request.headers : undefined); if (!originalHeaders) { - return { ...traceHeaders }; + return { + 'sentry-trace': sentryTrace, + ...(baggage && { baggage }), + ...(traceparent && { traceparent }), + }; } else if (isHeaders(originalHeaders)) { const newHeaders = new Headers(originalHeaders); @@ -292,11 +296,11 @@ export function _INTERNAL_getTracingHeadersForFetchRequest( const newHeaders: { 'sentry-trace': string; - baggage: string | undefined; + baggage?: string; traceparent?: string; } = Object.assign({}, originalHeaders, { 'sentry-trace': (existingSentryTraceHeader as string | undefined) ?? sentryTrace, - baggage: newBaggageHeaders.length > 0 ? newBaggageHeaders.join(',') : undefined, + ...(newBaggageHeaders.length > 0 && { baggage: newBaggageHeaders.join(',') }), }); if (propagateTraceparent && traceparent && !existingTraceparentHeader) { diff --git a/packages/core/test/lib/fetch.test.ts b/packages/core/test/lib/fetch.test.ts index 6cfdb74e8a19..b09886172969 100644 --- a/packages/core/test/lib/fetch.test.ts +++ b/packages/core/test/lib/fetch.test.ts @@ -69,6 +69,21 @@ describe('_INTERNAL_getTracingHeadersForFetchRequest', () => { }); }); + it('omits baggage from headers object when no baggage is available', () => { + vi.mocked(traceData.getTraceData).mockReturnValueOnce({ + 'sentry-trace': DEFAULT_SENTRY_TRACE, + }); + + const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest('/api/test', { + headers: { 'custom-header': 'custom-value' }, + }); + + expect(returnedHeaders).toStrictEqual({ + 'sentry-trace': DEFAULT_SENTRY_TRACE, + 'custom-header': 'custom-value', + }); + }); + it('attaches sentry headers to a Headers instance', () => { const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest('/api/test', { headers: new Headers({ 'custom-header': 'custom-value' }),