Skip to content

Commit d6bafef

Browse files
committed
fix(core): Restore previously active span when passing a custom scope to startSpan
1 parent 64d36a9 commit d6bafef

File tree

2 files changed

+89
-4
lines changed

2 files changed

+89
-4
lines changed

packages/core/src/tracing/trace.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,14 @@ import { propagationContextFromHeaders } from '../utils-hoist/tracing';
1616
import { handleCallbackErrors } from '../utils/handleCallbackErrors';
1717
import { hasTracingEnabled } from '../utils/hasTracingEnabled';
1818
import { _getSpanForScope, _setSpanForScope } from '../utils/spanOnScope';
19-
import { addChildSpanToSpan, getRootSpan, spanIsSampled, spanTimeInputToSeconds, spanToJSON } from '../utils/spanUtils';
19+
import {
20+
addChildSpanToSpan,
21+
getActiveSpan,
22+
getRootSpan,
23+
spanIsSampled,
24+
spanTimeInputToSeconds,
25+
spanToJSON,
26+
} from '../utils/spanUtils';
2027
import { freezeDscOnSpan, getDynamicSamplingContextFromSpan } from './dynamicSamplingContext';
2128
import { logSpanStart } from './logSpans';
2229
import { sampleSpan } from './sampling';
@@ -44,9 +51,11 @@ export function startSpan<T>(options: StartSpanOptions, callback: (span: Span) =
4451
}
4552

4653
const spanArguments = parseSentrySpanArguments(options);
47-
const { forceTransaction, parentSpan: customParentSpan } = options;
54+
const { forceTransaction, parentSpan: customParentSpan, scope: customScope } = options;
4855

49-
return withScope(options.scope, () => {
56+
const previousActiveSpanOnCustomScope = customScope && _getSpanForScope(customScope);
57+
58+
return withScope(customScope, () => {
5059
// If `options.parentSpan` is defined, we want to wrap the callback in `withActiveSpan`
5160
const wrapper = getActiveSpanWrapper<T>(customParentSpan);
5261

@@ -75,7 +84,14 @@ export function startSpan<T>(options: StartSpanOptions, callback: (span: Span) =
7584
activeSpan.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
7685
}
7786
},
78-
() => activeSpan.end(),
87+
() => {
88+
activeSpan.end();
89+
if (customScope) {
90+
// If a custom scope is passed, we don't fork the scope. Therefore, we need to reset the
91+
// active span on the scope to the previous active span (or to undefined if there was none)
92+
_setSpanForScope(customScope, previousActiveSpanOnCustomScope);
93+
}
94+
},
7995
);
8096
});
8197
});

packages/core/test/lib/tracing/trace.test.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,75 @@ describe('startSpan', () => {
269269
expect(getActiveSpan()).toBe(undefined);
270270
});
271271

272+
describe('handles multiple spans in sequence with a custom scope', () => {
273+
it('with parent span', () => {
274+
const initialScope = getCurrentScope();
275+
276+
const manualScope = initialScope.clone();
277+
const parentSpan = new SentrySpan({ spanId: 'parent-span-id', sampled: true });
278+
_setSpanForScope(manualScope, parentSpan);
279+
280+
startSpan({ name: 'span 1', scope: manualScope }, span1 => {
281+
expect(getCurrentScope()).not.toBe(initialScope);
282+
expect(getCurrentScope()).toBe(manualScope);
283+
expect(getActiveSpan()).toBe(span1);
284+
expect(spanToJSON(span1).parent_span_id).toBe('parent-span-id');
285+
});
286+
287+
withScope(manualScope, () => {
288+
expect(getActiveSpan()).toBe(parentSpan);
289+
});
290+
291+
startSpan({ name: 'span 2', scope: manualScope }, span2 => {
292+
expect(getCurrentScope()).not.toBe(initialScope);
293+
expect(getCurrentScope()).toBe(manualScope);
294+
expect(getActiveSpan()).toBe(span2);
295+
expect(spanToJSON(span2).parent_span_id).toBe('parent-span-id');
296+
});
297+
298+
withScope(manualScope, () => {
299+
expect(getActiveSpan()).toBe(parentSpan);
300+
});
301+
302+
expect(getCurrentScope()).toBe(initialScope);
303+
expect(getActiveSpan()).toBe(undefined);
304+
});
305+
306+
it('without parent span', () => {
307+
const initialScope = getCurrentScope();
308+
const manualScope = initialScope.clone();
309+
310+
const traceId = manualScope.getPropagationContext()?.traceId;
311+
312+
startSpan({ name: 'span 1', scope: manualScope }, span1 => {
313+
expect(getCurrentScope()).not.toBe(initialScope);
314+
expect(getCurrentScope()).toBe(manualScope);
315+
expect(getActiveSpan()).toBe(span1);
316+
expect(spanToJSON(span1).parent_span_id).toBe(undefined);
317+
expect(span1.spanContext().traceId).toBe(traceId);
318+
});
319+
320+
withScope(manualScope, () => {
321+
expect(getActiveSpan()).toBe(undefined);
322+
});
323+
324+
startSpan({ name: 'span 2', scope: manualScope }, span2 => {
325+
expect(getCurrentScope()).not.toBe(initialScope);
326+
expect(getCurrentScope()).toBe(manualScope);
327+
expect(getActiveSpan()).toBe(span2);
328+
expect(spanToJSON(span2).parent_span_id).toBe(undefined);
329+
expect(span2.spanContext().traceId).toBe(traceId);
330+
});
331+
332+
withScope(manualScope, () => {
333+
expect(getActiveSpan()).toBe(undefined);
334+
});
335+
336+
expect(getCurrentScope()).toBe(initialScope);
337+
expect(getActiveSpan()).toBe(undefined);
338+
});
339+
});
340+
272341
it('allows to pass a parentSpan', () => {
273342
const parentSpan = new SentrySpan({ spanId: 'parent-span-id', sampled: true, name: 'parent-span' });
274343

0 commit comments

Comments
 (0)