diff --git a/packages/core/src/exports.ts b/packages/core/src/exports.ts index 12744baffb7a..eb9a95652792 100644 --- a/packages/core/src/exports.ts +++ b/packages/core/src/exports.ts @@ -127,16 +127,18 @@ export function setUser(user: User | null): ReturnType { } /** - * Forks the current scope and sets the provided span as active span in the context of the provided callback. + * Forks the current scope and sets the provided span as active span in the context of the provided callback. Can be + * passed `null` to start an entirely new span tree. * - * @param span Spans started in the context of the provided callback will be children of this span. + * @param span Spans started in the context of the provided callback will be children of this span. If `null` is passed, + * spans started within the callback will not be attached to a parent span. * @param callback Execution context in which the provided span will be active. Is passed the newly forked scope. * @returns the value returned from the provided callback function. */ -export function withActiveSpan(span: Span, callback: (scope: ScopeInterface) => T): T { +export function withActiveSpan(span: Span | null, callback: (scope: ScopeInterface) => T): T { return withScope(scope => { // eslint-disable-next-line deprecation/deprecation - scope.setSpan(span); + scope.setSpan(span || undefined); return callback(scope); }); } diff --git a/packages/core/test/lib/scope.test.ts b/packages/core/test/lib/scope.test.ts index 27609f8214bf..360cd34938e5 100644 --- a/packages/core/test/lib/scope.test.ts +++ b/packages/core/test/lib/scope.test.ts @@ -582,4 +582,13 @@ describe('withActiveSpan()', () => { }); }); }); + + it('when `null` is passed, no span should be active within the callback', () => { + expect.assertions(1); + startSpan({ name: 'parent-span' }, () => { + withActiveSpan(null, () => { + expect(getActiveSpan()).toBeUndefined(); + }); + }); + }); }); diff --git a/packages/node-experimental/test/sdk/api.test.ts b/packages/node-experimental/test/sdk/api.test.ts index 730f1144e69d..5465d288def9 100644 --- a/packages/node-experimental/test/sdk/api.test.ts +++ b/packages/node-experimental/test/sdk/api.test.ts @@ -53,4 +53,46 @@ describe('withActiveSpan()', () => { expect.anything(), ); }); + + it('when `null` is passed, no span should be active within the callback', () => { + expect.assertions(1); + startSpan({ name: 'parent-span' }, () => { + withActiveSpan(null, () => { + expect(getActiveSpan()).toBeUndefined(); + }); + }); + }); + + it('when `null` is passed, should start a new trace for new spans', async () => { + const beforeSendTransaction = jest.fn(() => null); + mockSdkInit({ enableTracing: true, beforeSendTransaction }); + const client = getClient(); + + startSpan({ name: 'parent-span' }, () => { + withActiveSpan(null, () => { + startSpan({ name: 'child-span' }, () => {}); + }); + }); + + await client.flush(); + + expect(beforeSendTransaction).toHaveBeenCalledTimes(2); + + // The child span should be a child of the inactive span + expect(beforeSendTransaction).toHaveBeenCalledWith( + expect.objectContaining({ + transaction: 'parent-span', + spans: expect.not.arrayContaining([expect.objectContaining({ description: 'child-span' })]), + }), + expect.anything(), + ); + + // The floating span should be a separate transaction + expect(beforeSendTransaction).toHaveBeenCalledWith( + expect.objectContaining({ + transaction: 'child-span', + }), + expect.anything(), + ); + }); }); diff --git a/packages/opentelemetry/src/trace.ts b/packages/opentelemetry/src/trace.ts index 694f7a274e02..83f69d385dfc 100644 --- a/packages/opentelemetry/src/trace.ts +++ b/packages/opentelemetry/src/trace.ts @@ -102,14 +102,16 @@ export function startInactiveSpan(spanContext: OpenTelemetrySpanContext): Span { } /** - * Forks the current scope and sets the provided span as active span in the context of the provided callback. + * Forks the current scope and sets the provided span as active span in the context of the provided callback. Can be + * passed `null` to start an entirely new span tree. * - * @param span Spans started in the context of the provided callback will be children of this span. + * @param span Spans started in the context of the provided callback will be children of this span. If `null` is passed, + * spans started within the callback will not be attached to a parent span. * @param callback Execution context in which the provided span will be active. Is passed the newly forked scope. * @returns the value returned from the provided callback function. */ -export function withActiveSpan(span: Span, callback: (scope: Scope) => T): T { - const newContextWithActiveSpan = trace.setSpan(context.active(), span); +export function withActiveSpan(span: Span | null, callback: (scope: Scope) => T): T { + const newContextWithActiveSpan = span ? trace.setSpan(context.active(), span) : trace.deleteSpan(context.active()); return context.with(newContextWithActiveSpan, () => callback(getCurrentScope())); }