diff --git a/.size-limit.js b/.size-limit.js index 61fb027289d3..dc8e4f9df560 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -120,7 +120,7 @@ module.exports = [ import: createImport('init', 'ErrorBoundary', 'reactRouterV6BrowserTracingIntegration'), ignore: ['react/jsx-runtime'], gzip: true, - limit: '41 KB', + limit: '42 KB', }, // Vue SDK (ESM) { diff --git a/packages/core/src/tracing/trace.ts b/packages/core/src/tracing/trace.ts index 7a2229090e97..bbb956acf042 100644 --- a/packages/core/src/tracing/trace.ts +++ b/packages/core/src/tracing/trace.ts @@ -58,7 +58,7 @@ export function startSpan(options: StartSpanOptions, callback: (span: Span) = return wrapper(() => { const scope = getCurrentScope(); - const parentSpan = getParentSpan(scope); + const parentSpan = getParentSpan(scope, customParentSpan); const shouldSkipSpan = options.onlyIfParent && !parentSpan; const activeSpan = shouldSkipSpan @@ -116,7 +116,7 @@ export function startSpanManual(options: StartSpanOptions, callback: (span: S return wrapper(() => { const scope = getCurrentScope(); - const parentSpan = getParentSpan(scope); + const parentSpan = getParentSpan(scope, customParentSpan); const shouldSkipSpan = options.onlyIfParent && !parentSpan; const activeSpan = shouldSkipSpan @@ -176,7 +176,7 @@ export function startInactiveSpan(options: StartSpanOptions): Span { return wrapper(() => { const scope = getCurrentScope(); - const parentSpan = getParentSpan(scope); + const parentSpan = getParentSpan(scope, customParentSpan); const shouldSkipSpan = options.onlyIfParent && !parentSpan; @@ -489,7 +489,17 @@ function _startChildSpan(parentSpan: Span, scope: Scope, spanArguments: SentrySp return childSpan; } -function getParentSpan(scope: Scope): SentrySpan | undefined { +function getParentSpan(scope: Scope, customParentSpan: Span | null | undefined): SentrySpan | undefined { + // always use the passed in span directly + if (customParentSpan) { + return customParentSpan as SentrySpan; + } + + // This is different from `undefined` as it means the user explicitly wants no parent span + if (customParentSpan === null) { + return undefined; + } + const span = _getSpanForScope(scope) as SentrySpan | undefined; if (!span) { diff --git a/packages/core/test/lib/tracing/trace.test.ts b/packages/core/test/lib/tracing/trace.test.ts index 6d25afe13d3e..eccbb57f1610 100644 --- a/packages/core/test/lib/tracing/trace.test.ts +++ b/packages/core/test/lib/tracing/trace.test.ts @@ -620,6 +620,44 @@ describe('startSpan', () => { }); }); }); + + it('explicit parentSpan takes precedence over parentSpanIsAlwaysRootSpan=true', () => { + const options = getDefaultTestClientOptions({ + tracesSampleRate: 1, + parentSpanIsAlwaysRootSpan: true, + }); + client = new TestClient(options); + setCurrentClient(client); + client.init(); + + const parentSpan = startInactiveSpan({ name: 'parent span' }); + + startSpan({ name: 'parent span' }, () => { + startSpan({ name: 'child span' }, () => { + startSpan({ name: 'grand child span', parentSpan }, grandChildSpan => { + expect(spanToJSON(grandChildSpan).parent_span_id).toBe(parentSpan.spanContext().spanId); + }); + }); + }); + }); + + it('explicit parentSpan=null takes precedence over parentSpanIsAlwaysRootSpan=true', () => { + const options = getDefaultTestClientOptions({ + tracesSampleRate: 1, + parentSpanIsAlwaysRootSpan: true, + }); + client = new TestClient(options); + setCurrentClient(client); + client.init(); + + startSpan({ name: 'parent span' }, () => { + startSpan({ name: 'child span' }, () => { + startSpan({ name: 'grand child span', parentSpan: null }, grandChildSpan => { + expect(spanToJSON(grandChildSpan).parent_span_id).toBe(undefined); + }); + }); + }); + }); }); it('samples with a tracesSampler', () => { @@ -1174,6 +1212,46 @@ describe('startSpanManual', () => { span.end(); }); }); + + it('explicit parentSpan takes precedence over parentSpanIsAlwaysRootSpan=true', () => { + const options = getDefaultTestClientOptions({ + tracesSampleRate: 1, + parentSpanIsAlwaysRootSpan: true, + }); + client = new TestClient(options); + setCurrentClient(client); + client.init(); + + const parentSpan = startInactiveSpan({ name: 'parent span' }); + + startSpan({ name: 'parent span' }, () => { + startSpan({ name: 'child span' }, () => { + startSpanManual({ name: 'grand child span', parentSpan }, grandChildSpan => { + expect(spanToJSON(grandChildSpan).parent_span_id).toBe(parentSpan.spanContext().spanId); + grandChildSpan.end(); + }); + }); + }); + }); + + it('explicit parentSpan=null takes precedence over parentSpanIsAlwaysRootSpan=true', () => { + const options = getDefaultTestClientOptions({ + tracesSampleRate: 1, + parentSpanIsAlwaysRootSpan: true, + }); + client = new TestClient(options); + setCurrentClient(client); + client.init(); + + startSpan({ name: 'parent span' }, () => { + startSpan({ name: 'child span' }, () => { + startSpanManual({ name: 'grand child span', parentSpan: null }, grandChildSpan => { + expect(spanToJSON(grandChildSpan).parent_span_id).toBe(undefined); + grandChildSpan.end(); + }); + }); + }); + }); }); it('sets a child span reference on the parent span', () => { @@ -1543,6 +1621,44 @@ describe('startInactiveSpan', () => { }); }); }); + + it('explicit parentSpan takes precedence over parentSpanIsAlwaysRootSpan=true', () => { + const options = getDefaultTestClientOptions({ + tracesSampleRate: 1, + parentSpanIsAlwaysRootSpan: true, + }); + client = new TestClient(options); + setCurrentClient(client); + client.init(); + + const parentSpan = startInactiveSpan({ name: 'parent span' }); + + startSpan({ name: 'parent span' }, () => { + startSpan({ name: 'child span' }, () => { + const grandChildSpan = startInactiveSpan({ name: 'grand child span', parentSpan }); + expect(spanToJSON(grandChildSpan).parent_span_id).toBe(parentSpan.spanContext().spanId); + grandChildSpan.end(); + }); + }); + }); + + it('explicit parentSpan=null takes precedence over parentSpanIsAlwaysRootSpan=true', () => { + const options = getDefaultTestClientOptions({ + tracesSampleRate: 1, + parentSpanIsAlwaysRootSpan: true, + }); + client = new TestClient(options); + setCurrentClient(client); + client.init(); + + startSpan({ name: 'parent span' }, () => { + startSpan({ name: 'child span' }, () => { + const grandChildSpan = startInactiveSpan({ name: 'grand child span', parentSpan: null }); + expect(spanToJSON(grandChildSpan).parent_span_id).toBe(undefined); + grandChildSpan.end(); + }); + }); + }); }); it('includes the scope at the time the span was started when finished', async () => {