From e761fd3db02dad19e62cac4c1db90e4e866fbf54 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 7 Jan 2025 13:23:42 +0100 Subject: [PATCH 1/5] feat!: Remove `spanId` from propagation context Closes https://github.com/getsentry/sentry-javascript/issues/12385 This also deprecates `getPropagationContextFromSpan` as it is no longer used/needed. We may think about removing this in v9, but IMHO we can also just leave this for v9, it does not hurt too much to have it in there... fix tests remove unneeded test fix stuff fix tests better test fix test --- .../subject.js | 1 - .../startSpan/parallel-root-spans/scenario.ts | 1 - .../scenario.ts | 1 - .../tracing/meta-tags-twp-errors/test.ts | 11 +- .../tracing/browserTracingIntegration.test.ts | 10 - packages/core/src/currentScopes.ts | 7 +- packages/core/src/scope.ts | 13 +- packages/core/src/types-hoist/tracing.ts | 9 - .../src/utils-hoist/propagationContext.ts | 1 - packages/core/src/utils-hoist/tracing.ts | 5 +- packages/core/src/utils/traceData.ts | 7 +- packages/core/test/lib/feedback.test.ts | 5 +- packages/core/test/lib/prepareEvent.test.ts | 3 +- packages/core/test/lib/scope.test.ts | 11 +- packages/core/test/lib/tracing/trace.test.ts | 7 - .../lib/utils/applyScopeDataToEvent.test.ts | 20 +- .../core/test/lib/utils/traceData.test.ts | 12 +- .../utils-hoist/proagationContext.test.ts | 1 - .../core/test/utils-hoist/tracing.test.ts | 10 +- .../wrapGenerationFunctionWithSentry.ts | 2 - .../common/wrapServerComponentWithSentry.ts | 2 - packages/node/src/integrations/anr/worker.ts | 7 +- packages/node/test/sdk/scope.test.ts | 230 ------------------ packages/opentelemetry/src/index.ts | 1 + packages/opentelemetry/src/propagator.ts | 17 +- packages/opentelemetry/src/sampler.ts | 11 +- .../opentelemetry/test/propagator.test.ts | 11 +- packages/opentelemetry/test/trace.test.ts | 34 +-- .../test/utils/getTraceData.test.ts | 9 +- 29 files changed, 62 insertions(+), 397 deletions(-) delete mode 100644 packages/node/test/sdk/scope.test.ts diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/parallel-root-spans-with-parentSpanId/subject.js b/dev-packages/browser-integration-tests/suites/public-api/startSpan/parallel-root-spans-with-parentSpanId/subject.js index 56c0e05a269c..85a9847e1c3f 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/parallel-root-spans-with-parentSpanId/subject.js +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/parallel-root-spans-with-parentSpanId/subject.js @@ -1,6 +1,5 @@ Sentry.getCurrentScope().setPropagationContext({ parentSpanId: '1234567890123456', - spanId: '123456789012345x', traceId: '12345678901234567890123456789012', }); diff --git a/dev-packages/node-integration-tests/suites/public-api/startSpan/parallel-root-spans/scenario.ts b/dev-packages/node-integration-tests/suites/public-api/startSpan/parallel-root-spans/scenario.ts index e352fff5c02c..9275f9fe4505 100644 --- a/dev-packages/node-integration-tests/suites/public-api/startSpan/parallel-root-spans/scenario.ts +++ b/dev-packages/node-integration-tests/suites/public-api/startSpan/parallel-root-spans/scenario.ts @@ -10,7 +10,6 @@ Sentry.init({ Sentry.getCurrentScope().setPropagationContext({ parentSpanId: '1234567890123456', - spanId: '123456789012345x', traceId: '12345678901234567890123456789012', }); diff --git a/dev-packages/node-integration-tests/suites/public-api/startSpan/parallel-spans-in-scope-with-parentSpanId/scenario.ts b/dev-packages/node-integration-tests/suites/public-api/startSpan/parallel-spans-in-scope-with-parentSpanId/scenario.ts index 7c4f702f5df8..cbd2dd023f37 100644 --- a/dev-packages/node-integration-tests/suites/public-api/startSpan/parallel-spans-in-scope-with-parentSpanId/scenario.ts +++ b/dev-packages/node-integration-tests/suites/public-api/startSpan/parallel-spans-in-scope-with-parentSpanId/scenario.ts @@ -11,7 +11,6 @@ Sentry.init({ Sentry.withScope(scope => { scope.setPropagationContext({ parentSpanId: '1234567890123456', - spanId: '123456789012345x', traceId: '12345678901234567890123456789012', }); diff --git a/dev-packages/node-integration-tests/suites/tracing/meta-tags-twp-errors/test.ts b/dev-packages/node-integration-tests/suites/tracing/meta-tags-twp-errors/test.ts index 9abb7b1a631c..049723e4f5a9 100644 --- a/dev-packages/node-integration-tests/suites/tracing/meta-tags-twp-errors/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/meta-tags-twp-errors/test.ts @@ -41,11 +41,18 @@ describe('errors in TwP mode have same trace in trace context and getTraceData() const traceData = contexts?.traceData || {}; - expect(traceData['sentry-trace']).toEqual(`${trace_id}-${span_id}`); + expect(traceData['sentry-trace']).toMatch(/^[a-f0-9]{32}-[a-f0-9]{16}$/); + expect(traceData['sentry-trace']).toContain(`${trace_id}-`); + // span_id is a random span ID + expect(traceData['sentry-trace']).not.toContain(span_id); + expect(traceData.baggage).toContain(`sentry-trace_id=${trace_id}`); expect(traceData.baggage).not.toContain('sentry-sampled='); - expect(traceData.metaTags).toContain(``); + expect(traceData.metaTags).toMatch(//); + expect(traceData.metaTags).toContain(` { scope.setPropagationContext({ traceId: 'd4cda95b652f4a1592b449d5929fda1b', parentSpanId: '6e0c63257de34c93', - spanId: '6e0c63257de34c92', sampled: true, }); @@ -59,7 +58,7 @@ describe('SentryPropagator', () => { 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b', ].sort(), ); - expect(carrier[SENTRY_TRACE_HEADER]).toBe('d4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-1'); + expect(carrier[SENTRY_TRACE_HEADER]).toMatch(/d4cda95b652f4a1592b449d5929fda1b-[a-f0-9]{16}-1/); }); }); @@ -68,7 +67,6 @@ describe('SentryPropagator', () => { scope.setPropagationContext({ traceId: 'd4cda95b652f4a1592b449d5929fda1b', parentSpanId: '6e0c63257de34c93', - spanId: '6e0c63257de34c92', sampled: true, dsc: { transaction: 'sampled-transaction', @@ -96,7 +94,7 @@ describe('SentryPropagator', () => { 'sentry-replay_id=dsc_replay_id', ].sort(), ); - expect(carrier[SENTRY_TRACE_HEADER]).toBe('d4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-1'); + expect(carrier[SENTRY_TRACE_HEADER]).toMatch(/d4cda95b652f4a1592b449d5929fda1b-[a-f0-9]{16}-1/); }); }); @@ -322,7 +320,6 @@ describe('SentryPropagator', () => { scope.setPropagationContext({ traceId: 'TRACE_ID', parentSpanId: 'PARENT_SPAN_ID', - spanId: 'SPAN_ID', sampled: true, }); @@ -362,7 +359,6 @@ describe('SentryPropagator', () => { scope.setPropagationContext({ traceId: 'TRACE_ID', parentSpanId: 'PARENT_SPAN_ID', - spanId: 'SPAN_ID', sampled: true, }); @@ -399,7 +395,6 @@ describe('SentryPropagator', () => { scope.setPropagationContext({ traceId: 'TRACE_ID', parentSpanId: 'PARENT_SPAN_ID', - spanId: 'SPAN_ID', sampled: true, }); @@ -601,7 +596,6 @@ describe('SentryPropagator', () => { const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter); expect(trace.getSpanContext(context)).toEqual(undefined); expect(getCurrentScope().getPropagationContext()).toEqual({ - spanId: expect.stringMatching(/[a-f0-9]{16}/), traceId: expect.stringMatching(/[a-f0-9]{32}/), }); }); @@ -652,7 +646,6 @@ describe('SentryPropagator', () => { const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter); expect(trace.getSpanContext(context)).toEqual(undefined); expect(getCurrentScope().getPropagationContext()).toEqual({ - spanId: expect.stringMatching(/[a-f0-9]{16}/), traceId: expect.stringMatching(/[a-f0-9]{32}/), }); }); diff --git a/packages/opentelemetry/test/trace.test.ts b/packages/opentelemetry/test/trace.test.ts index 6852b8b40988..cbded44a6139 100644 --- a/packages/opentelemetry/test/trace.test.ts +++ b/packages/opentelemetry/test/trace.test.ts @@ -1545,8 +1545,6 @@ describe('continueTrace', () => { }); expect(scope.getPropagationContext()).toEqual({ - sampled: undefined, - spanId: expect.any(String), traceId: expect.any(String), }); @@ -1554,7 +1552,7 @@ describe('continueTrace', () => { }); it('works with trace data', () => { - const scope = continueTrace( + continueTrace( { sentryTrace: '12312012123120121231201212312012-1121201211212012-0', baggage: undefined, @@ -1570,21 +1568,12 @@ describe('continueTrace', () => { }); expect(getSamplingDecision(span.spanContext())).toBe(false); expect(spanIsSampled(span)).toBe(false); - - return getCurrentScope(); }, ); - - expect(scope.getPropagationContext()).toEqual({ - spanId: expect.any(String), - traceId: expect.any(String), - }); - - expect(scope.getScopeData().sdkProcessingMetadata).toEqual({}); }); it('works with trace & baggage data', () => { - const scope = continueTrace( + continueTrace( { sentryTrace: '12312012123120121231201212312012-1121201211212012-1', baggage: 'sentry-version=1.0,sentry-environment=production', @@ -1600,21 +1589,12 @@ describe('continueTrace', () => { }); expect(getSamplingDecision(span.spanContext())).toBe(true); expect(spanIsSampled(span)).toBe(true); - - return getCurrentScope(); }, ); - - expect(scope.getPropagationContext()).toEqual({ - spanId: expect.any(String), - traceId: expect.any(String), - }); - - expect(scope.getScopeData().sdkProcessingMetadata).toEqual({}); }); it('works with trace & 3rd party baggage data', () => { - const scope = continueTrace( + continueTrace( { sentryTrace: '12312012123120121231201212312012-1121201211212012-1', baggage: 'sentry-version=1.0,sentry-environment=production,dogs=great,cats=boring', @@ -1630,16 +1610,8 @@ describe('continueTrace', () => { }); expect(getSamplingDecision(span.spanContext())).toBe(true); expect(spanIsSampled(span)).toBe(true); - - return getCurrentScope(); }, ); - - expect(scope.getPropagationContext()).toEqual({ - spanId: expect.any(String), - traceId: expect.any(String), - }); - expect(scope.getScopeData().sdkProcessingMetadata).toEqual({}); }); it('returns response of callback', () => { diff --git a/packages/opentelemetry/test/utils/getTraceData.test.ts b/packages/opentelemetry/test/utils/getTraceData.test.ts index e0f2270d8e22..f18f1c307639 100644 --- a/packages/opentelemetry/test/utils/getTraceData.test.ts +++ b/packages/opentelemetry/test/utils/getTraceData.test.ts @@ -55,7 +55,6 @@ describe('getTraceData', () => { getCurrentScope().setPropagationContext({ traceId: '12345678901234567890123456789012', sampled: true, - spanId: '1234567890123456', dsc: { environment: 'staging', public_key: 'key', @@ -65,10 +64,10 @@ describe('getTraceData', () => { const traceData = getTraceData(); - expect(traceData).toEqual({ - 'sentry-trace': '12345678901234567890123456789012-1234567890123456-1', - baggage: 'sentry-environment=staging,sentry-public_key=key,sentry-trace_id=12345678901234567890123456789012', - }); + expect(traceData['sentry-trace']).toMatch(/^12345678901234567890123456789012-[a-f0-9]{16}-1$/); + expect(traceData.baggage).toEqual( + 'sentry-environment=staging,sentry-public_key=key,sentry-trace_id=12345678901234567890123456789012', + ); }); it('works with an span with frozen DSC in traceState', () => { From 80182b5449f5dd9c82b4ee68455999351171ab89 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 7 Jan 2025 16:51:12 +0100 Subject: [PATCH 2/5] fixes... --- packages/core/src/currentScopes.ts | 4 ++-- packages/core/src/types-hoist/tracing.ts | 10 ++++++++++ packages/core/src/utils-hoist/tracing.ts | 4 ++-- packages/core/src/utils/spanUtils.ts | 5 ++++- packages/core/src/utils/traceData.ts | 5 ++--- packages/node/src/integrations/anr/worker.ts | 4 ++-- .../src/integrations/http/SentryHttpInstrumentation.ts | 8 +++++++- packages/opentelemetry/src/propagator.ts | 2 +- 8 files changed, 30 insertions(+), 12 deletions(-) diff --git a/packages/core/src/currentScopes.ts b/packages/core/src/currentScopes.ts index 3000413824c2..6fab81298530 100644 --- a/packages/core/src/currentScopes.ts +++ b/packages/core/src/currentScopes.ts @@ -128,11 +128,11 @@ export function getClient(): C | undefined { export function getTraceContextFromScope(scope: Scope): TraceContext { const propagationContext = scope.getPropagationContext(); - const { traceId, parentSpanId } = propagationContext; + const { traceId, parentSpanId, propagationSpanId } = propagationContext; const traceContext: TraceContext = dropUndefinedKeys({ trace_id: traceId, - span_id: generateSpanId(), + span_id: propagationSpanId || generateSpanId(), parent_span_id: parentSpanId, }); diff --git a/packages/core/src/types-hoist/tracing.ts b/packages/core/src/types-hoist/tracing.ts index ac5f1a2d588f..466635793c42 100644 --- a/packages/core/src/types-hoist/tracing.ts +++ b/packages/core/src/types-hoist/tracing.ts @@ -15,12 +15,14 @@ export interface PropagationContext { * Either represents the incoming `traceId` or the `traceId` generated by the current SDK, if there was no incoming trace. */ traceId: string; + /** * Represents the sampling decision of the incoming trace. * * The current SDK should not modify this value! */ sampled?: boolean; + /** * The `parentSpanId` denotes the ID of the incoming client span. If there is no `parentSpanId` on the propagation * context, it means that the the incoming trace didn't come from a span. @@ -28,6 +30,14 @@ export interface PropagationContext { * The current SDK should not modify this value! */ parentSpanId?: string; + + /** + * A span ID to be used when using tracing without performance (without an active span). + * This is only set/used when the SDK wants to ensure to use the same span ID for propagation. + * If this is empty, a random span ID will be generated. + */ + propagationSpanId?: string; + /** * An undefined dsc in the propagation context means that the current SDK invocation is the head of trace and still free to modify and set the DSC for outgoing requests. * diff --git a/packages/core/src/utils-hoist/tracing.ts b/packages/core/src/utils-hoist/tracing.ts index 1fd983bde105..b0dc6a1a6cdc 100644 --- a/packages/core/src/utils-hoist/tracing.ts +++ b/packages/core/src/utils-hoist/tracing.ts @@ -72,8 +72,8 @@ export function propagationContextFromHeaders( * Create sentry-trace header from span context values. */ export function generateSentryTraceHeader( - traceId: string = generateTraceId(), - spanId: string = generateSpanId(), + traceId: string | undefined = generateTraceId(), + spanId: string | undefined = generateSpanId(), sampled?: boolean, ): string { let sampledString = ''; diff --git a/packages/core/src/utils/spanUtils.ts b/packages/core/src/utils/spanUtils.ts index c4088fba4942..90a232e27a22 100644 --- a/packages/core/src/utils/spanUtils.ts +++ b/packages/core/src/utils/spanUtils.ts @@ -7,6 +7,7 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, } from '../semanticAttributes'; +import { getCapturedScopesOnSpan } from '../tracing'; import type { SentrySpan } from '../tracing/sentrySpan'; import { SPAN_STATUS_OK, SPAN_STATUS_UNSET } from '../tracing/spanstatus'; import type { @@ -61,7 +62,9 @@ export function spanToTraceContext(span: Span): TraceContext { // If the span is remote, we use a random/virtual span as span_id to the trace context, // and the remote span as parent_span_id const parent_span_id = isRemote ? spanId : spanToJSON(span).parent_span_id; - const span_id = isRemote ? generateSpanId() : spanId; + const scope = getCapturedScopesOnSpan(span).scope; + + const span_id = isRemote ? scope?.getPropagationContext().propagationSpanId || generateSpanId() : spanId; return dropUndefinedKeys({ parent_span_id, diff --git a/packages/core/src/utils/traceData.ts b/packages/core/src/utils/traceData.ts index 6cfa71ce239c..56968a27c357 100644 --- a/packages/core/src/utils/traceData.ts +++ b/packages/core/src/utils/traceData.ts @@ -5,7 +5,6 @@ import { isEnabled } from '../exports'; import type { Scope } from '../scope'; import { getDynamicSamplingContextFromScope, getDynamicSamplingContextFromSpan } from '../tracing'; import type { SerializedTraceData, Span } from '../types-hoist'; -import { generateSpanId } from '../utils-hoist'; import { dynamicSamplingContextToSentryBaggageHeader } from '../utils-hoist/baggage'; import { logger } from '../utils-hoist/logger'; import { TRACEPARENT_REGEXP, generateSentryTraceHeader } from '../utils-hoist/tracing'; @@ -56,6 +55,6 @@ export function getTraceData(options: { span?: Span } = {}): SerializedTraceData * Get a sentry-trace header value for the given scope. */ function scopeToTraceHeader(scope: Scope): string { - const { traceId, sampled } = scope.getPropagationContext(); - return generateSentryTraceHeader(traceId, generateSpanId(), sampled); + const { traceId, sampled, propagationSpanId } = scope.getPropagationContext(); + return generateSentryTraceHeader(traceId, propagationSpanId, sampled); } diff --git a/packages/node/src/integrations/anr/worker.ts b/packages/node/src/integrations/anr/worker.ts index b80c2816629c..59cf0183864b 100644 --- a/packages/node/src/integrations/anr/worker.ts +++ b/packages/node/src/integrations/anr/worker.ts @@ -127,11 +127,11 @@ function applyScopeToEvent(event: Event, scope: ScopeData): void { applyScopeDataToEvent(event, scope); if (!event.contexts?.trace) { - const { traceId, parentSpanId } = scope.propagationContext; + const { traceId, parentSpanId, propagationSpanId } = scope.propagationContext; event.contexts = { trace: { trace_id: traceId, - span_id: generateSpanId(), + span_id: propagationSpanId || generateSpanId(), parent_span_id: parentSpanId, }, ...event.contexts, diff --git a/packages/node/src/integrations/http/SentryHttpInstrumentation.ts b/packages/node/src/integrations/http/SentryHttpInstrumentation.ts index 46b5da5fcda7..a50691ab291f 100644 --- a/packages/node/src/integrations/http/SentryHttpInstrumentation.ts +++ b/packages/node/src/integrations/http/SentryHttpInstrumentation.ts @@ -9,6 +9,7 @@ import { InstrumentationBase, InstrumentationNodeModuleDefinition } from '@opent import type { AggregationCounts, Client, RequestEventData, SanitizedRequestData, Scope } from '@sentry/core'; import { addBreadcrumb, + generateSpanId, getBreadcrumbLogLevelFromHttpStatusCode, getClient, getIsolationScope, @@ -18,6 +19,7 @@ import { parseUrl, stripUrlQueryAndFragment, withIsolationScope, + withScope, } from '@sentry/core'; import { DEBUG_BUILD } from '../../debug-build'; import { getRequestUrl } from '../../utils/getRequestUrl'; @@ -187,7 +189,11 @@ export class SentryHttpInstrumentation extends InstrumentationBase { - return original.apply(this, [event, ...args]); + return withScope(scope => { + // Set a new propagationSpanId for this request + scope.getPropagationContext().propagationSpanId = generateSpanId(); + return original.apply(this, [event, ...args]); + }); }); }; }; diff --git a/packages/opentelemetry/src/propagator.ts b/packages/opentelemetry/src/propagator.ts index 7b73a347725f..b73a37b25a67 100644 --- a/packages/opentelemetry/src/propagator.ts +++ b/packages/opentelemetry/src/propagator.ts @@ -233,7 +233,7 @@ export function getInjectionData(context: Context): { return { dynamicSamplingContext, traceId: propagationContext.traceId, - spanId: undefined, + spanId: propagationContext.propagationSpanId, sampled: propagationContext.sampled, }; } From dc4e1b4ddf0549a05c7eae902f68e09814295497 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 8 Jan 2025 09:28:59 +0100 Subject: [PATCH 3/5] fix circular dep check --- packages/core/src/utils/spanUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/utils/spanUtils.ts b/packages/core/src/utils/spanUtils.ts index 90a232e27a22..b42935674bbb 100644 --- a/packages/core/src/utils/spanUtils.ts +++ b/packages/core/src/utils/spanUtils.ts @@ -7,7 +7,7 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, } from '../semanticAttributes'; -import { getCapturedScopesOnSpan } from '../tracing'; +import { getCapturedScopesOnSpan } from '../tracing/utils'; import type { SentrySpan } from '../tracing/sentrySpan'; import { SPAN_STATUS_OK, SPAN_STATUS_UNSET } from '../tracing/spanstatus'; import type { From 7cb9c8642ca4908cbeb4ebbbcaf38d1885d6b016 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 8 Jan 2025 11:03:12 +0100 Subject: [PATCH 4/5] add comments & tests --- .../twp-errors-meta/init.js | 12 +++++++ .../twp-errors-meta/subject.js | 2 ++ .../twp-errors-meta/template.html | 11 ++++++ .../twp-errors-meta/test.ts | 35 +++++++++++++++++++ .../twp-errors/init.js | 12 +++++++ .../twp-errors/subject.js | 2 ++ .../twp-errors/test.ts | 30 ++++++++++++++++ .../tracing/meta-tags-twp-errors/test.ts | 2 ++ packages/core/src/utils/spanUtils.ts | 2 +- 9 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/twp-errors-meta/init.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/twp-errors-meta/subject.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/twp-errors-meta/template.html create mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/twp-errors-meta/test.ts create mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/twp-errors/init.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/twp-errors/subject.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/twp-errors/test.ts diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/twp-errors-meta/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/twp-errors-meta/init.js new file mode 100644 index 000000000000..c026daa1eed9 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/twp-errors-meta/init.js @@ -0,0 +1,12 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: integrations => { + integrations.push(Sentry.browserTracingIntegration()); + return integrations.filter(i => i.name !== 'BrowserSession'); + }, + tracesSampleRate: 0, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/twp-errors-meta/subject.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/twp-errors-meta/subject.js new file mode 100644 index 000000000000..b7d62f8cfb95 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/twp-errors-meta/subject.js @@ -0,0 +1,2 @@ +Sentry.captureException(new Error('test error')); +Sentry.captureException(new Error('test error 2')); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/twp-errors-meta/template.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/twp-errors-meta/template.html new file mode 100644 index 000000000000..22d155bf8648 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/twp-errors-meta/template.html @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/twp-errors-meta/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/twp-errors-meta/test.ts new file mode 100644 index 000000000000..5bed055dbc0a --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/twp-errors-meta/test.ts @@ -0,0 +1,35 @@ +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/browser'; +import { sentryTest } from '../../../../utils/fixtures'; +import { getMultipleSentryEnvelopeRequests, shouldSkipTracingTest } from '../../../../utils/helpers'; + +sentryTest('errors in TwP mode have same trace ID & span IDs', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const traceId = '12312012123120121231201212312012'; + const spanId = '1121201211212012'; + + const url = await getLocalTestUrl({ testDir: __dirname }); + const [event1, event2] = await getMultipleSentryEnvelopeRequests(page, 2, { url }); + + // Ensure these are the actual errors we care about + expect(event1.exception?.values?.[0].value).toContain('test error'); + expect(event2.exception?.values?.[0].value).toContain('test error'); + + const contexts1 = event1.contexts; + const { trace_id: traceId1, span_id: spanId1 } = contexts1?.trace || {}; + expect(traceId1).toEqual(traceId); + + // Span ID is a virtual span, not the propagated one + expect(spanId1).not.toEqual(spanId); + expect(spanId1).toMatch(/^[a-f0-9]{16}$/); + + const contexts2 = event2.contexts; + const { trace_id: traceId2, span_id: spanId2 } = contexts2?.trace || {}; + expect(traceId2).toEqual(traceId); + expect(spanId2).toMatch(/^[a-f0-9]{16}$/); + + expect(spanId2).toEqual(spanId1); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/twp-errors/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/twp-errors/init.js new file mode 100644 index 000000000000..c026daa1eed9 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/twp-errors/init.js @@ -0,0 +1,12 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: integrations => { + integrations.push(Sentry.browserTracingIntegration()); + return integrations.filter(i => i.name !== 'BrowserSession'); + }, + tracesSampleRate: 0, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/twp-errors/subject.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/twp-errors/subject.js new file mode 100644 index 000000000000..b7d62f8cfb95 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/twp-errors/subject.js @@ -0,0 +1,2 @@ +Sentry.captureException(new Error('test error')); +Sentry.captureException(new Error('test error 2')); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/twp-errors/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/twp-errors/test.ts new file mode 100644 index 000000000000..3048de92b2f1 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/twp-errors/test.ts @@ -0,0 +1,30 @@ +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/browser'; +import { sentryTest } from '../../../../utils/fixtures'; +import { getMultipleSentryEnvelopeRequests, shouldSkipTracingTest } from '../../../../utils/helpers'; + +sentryTest('errors in TwP mode have same trace ID & span IDs', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + const [event1, event2] = await getMultipleSentryEnvelopeRequests(page, 2, { url }); + + // Ensure these are the actual errors we care about + expect(event1.exception?.values?.[0].value).toContain('test error'); + expect(event2.exception?.values?.[0].value).toContain('test error'); + + const contexts1 = event1.contexts; + const { trace_id: traceId1, span_id: spanId1 } = contexts1?.trace || {}; + expect(traceId1).toMatch(/^[a-f0-9]{32}$/); + expect(spanId1).toMatch(/^[a-f0-9]{16}$/); + + const contexts2 = event2.contexts; + const { trace_id: traceId2, span_id: spanId2 } = contexts2?.trace || {}; + expect(traceId2).toMatch(/^[a-f0-9]{32}$/); + expect(spanId2).toMatch(/^[a-f0-9]{16}$/); + + expect(traceId2).toEqual(traceId1); + expect(spanId2).toEqual(spanId1); +}); diff --git a/dev-packages/node-integration-tests/suites/tracing/meta-tags-twp-errors/test.ts b/dev-packages/node-integration-tests/suites/tracing/meta-tags-twp-errors/test.ts index 049723e4f5a9..d4447255bf51 100644 --- a/dev-packages/node-integration-tests/suites/tracing/meta-tags-twp-errors/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/meta-tags-twp-errors/test.ts @@ -5,6 +5,7 @@ describe('errors in TwP mode have same trace in trace context and getTraceData() cleanupChildProcesses(); }); + // In a request handler, the spanId is consistent inside of the request test('in incoming request', done => { createRunner(__dirname, 'server.js') .expect({ @@ -30,6 +31,7 @@ describe('errors in TwP mode have same trace in trace context and getTraceData() .makeRequest('get', '/test'); }); + // Outside of a request handler, the spanId is random test('outside of a request handler', done => { createRunner(__dirname, 'no-server.js') .expect({ diff --git a/packages/core/src/utils/spanUtils.ts b/packages/core/src/utils/spanUtils.ts index b42935674bbb..b78e2d5ef989 100644 --- a/packages/core/src/utils/spanUtils.ts +++ b/packages/core/src/utils/spanUtils.ts @@ -7,9 +7,9 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, } from '../semanticAttributes'; -import { getCapturedScopesOnSpan } from '../tracing/utils'; import type { SentrySpan } from '../tracing/sentrySpan'; import { SPAN_STATUS_OK, SPAN_STATUS_UNSET } from '../tracing/spanstatus'; +import { getCapturedScopesOnSpan } from '../tracing/utils'; import type { Span, SpanAttributes, From a57be3a623bf6526da05f7f9c619e8b44912912b Mon Sep 17 00:00:00 2001 From: Francesco Gringl-Novy Date: Wed, 8 Jan 2025 12:21:46 +0100 Subject: [PATCH 5/5] Update packages/core/src/types-hoist/tracing.ts Co-authored-by: Luca Forstner --- packages/core/src/types-hoist/tracing.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/src/types-hoist/tracing.ts b/packages/core/src/types-hoist/tracing.ts index 466635793c42..a1c57e29b3bb 100644 --- a/packages/core/src/types-hoist/tracing.ts +++ b/packages/core/src/types-hoist/tracing.ts @@ -32,9 +32,9 @@ export interface PropagationContext { parentSpanId?: string; /** - * A span ID to be used when using tracing without performance (without an active span). - * This is only set/used when the SDK wants to ensure to use the same span ID for propagation. - * If this is empty, a random span ID will be generated. + * A span ID that should be used for the `trace` context of various event types, and for propagation of a `parentSpanId` to downstream services, when performance is disabled or when there is no active span. + * This value should be set by the SDK in an informed way when the same span ID should be used for one unit of execution (e.g. a request, usually tied to the isolation scope). + * If this value is undefined on the propagation context, the SDK will generate a random span ID for `trace` contexts and trace propagation. */ propagationSpanId?: string;