Skip to content

Commit 555c009

Browse files
committed
ref(core): Ensure non-recording root spans have frozen DSC
Otherwise, parts of the DSC will be missing - we try to make it as complete as we can.
1 parent 6779dfe commit 555c009

File tree

4 files changed

+66
-4
lines changed

4 files changed

+66
-4
lines changed

packages/core/src/tracing/idleSpan.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { getClient, getCurrentScope } from '../currentScopes';
2-
import type { Span, StartSpanOptions } from '../types-hoist';
2+
import type { DynamicSamplingContext, Span, StartSpanOptions } from '../types-hoist';
33

44
import { DEBUG_BUILD } from '../debug-build';
55
import { SEMANTIC_ATTRIBUTE_SENTRY_IDLE_SPAN_FINISH_REASON } from '../semanticAttributes';
@@ -14,6 +14,7 @@ import {
1414
spanTimeInputToSeconds,
1515
spanToJSON,
1616
} from '../utils/spanUtils';
17+
import { freezeDscOnSpan, getDynamicSamplingContextFromSpan } from './dynamicSamplingContext';
1718
import { SentryNonRecordingSpan } from './sentryNonRecordingSpan';
1819
import { SPAN_STATUS_ERROR } from './spanstatus';
1920
import { startInactiveSpan } from './trace';
@@ -109,7 +110,16 @@ export function startIdleSpan(startSpanOptions: StartSpanOptions, options: Parti
109110
const client = getClient();
110111

111112
if (!client || !hasTracingEnabled()) {
112-
return new SentryNonRecordingSpan();
113+
const span = new SentryNonRecordingSpan();
114+
115+
const dsc = {
116+
sample_rate: '0',
117+
sampled: 'false',
118+
...getDynamicSamplingContextFromSpan(span),
119+
} satisfies Partial<DynamicSamplingContext>;
120+
freezeDscOnSpan(span, dsc);
121+
122+
return span;
113123
}
114124

115125
const scope = getCurrentScope();

packages/core/src/tracing/trace.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@
22

33
import type { AsyncContextStrategy } from '../asyncContext/types';
44
import { getMainCarrier } from '../carrier';
5-
import type { ClientOptions, SentrySpanArguments, Span, SpanTimeInput, StartSpanOptions } from '../types-hoist';
5+
import type {
6+
ClientOptions,
7+
DynamicSamplingContext,
8+
SentrySpanArguments,
9+
Span,
10+
SpanTimeInput,
11+
StartSpanOptions,
12+
} from '../types-hoist';
613

714
import { getClient, getCurrentScope, getIsolationScope, withScope } from '../currentScopes';
815

@@ -284,7 +291,21 @@ function createChildOrRootSpan({
284291
scope: Scope;
285292
}): Span {
286293
if (!hasTracingEnabled()) {
287-
return new SentryNonRecordingSpan();
294+
const span = new SentryNonRecordingSpan();
295+
296+
// If this is a root span, we ensure to freeze a DSC
297+
// So we can have at least partial data here
298+
if (forceTransaction || !parentSpan) {
299+
const dsc = {
300+
sampled: 'false',
301+
sample_rate: '0',
302+
transaction: spanArguments.name,
303+
...getDynamicSamplingContextFromSpan(span),
304+
} satisfies Partial<DynamicSamplingContext>;
305+
freezeDscOnSpan(span, dsc);
306+
}
307+
308+
return span;
288309
}
289310

290311
const isolationScope = getIsolationScope();

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
getActiveSpan,
88
getClient,
99
getCurrentScope,
10+
getDynamicSamplingContextFromSpan,
1011
getGlobalScope,
1112
getIsolationScope,
1213
setCurrentClient,
@@ -60,6 +61,14 @@ describe('startIdleSpan', () => {
6061
const idleSpan = startIdleSpan({ name: 'foo' });
6162
expect(idleSpan).toBeDefined();
6263
expect(idleSpan).toBeInstanceOf(SentryNonRecordingSpan);
64+
// DSC is still correctly set on the span
65+
expect(getDynamicSamplingContextFromSpan(idleSpan)).toEqual({
66+
environment: 'production',
67+
public_key: '123',
68+
sample_rate: '0',
69+
sampled: 'false',
70+
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
71+
});
6372

6473
// not set as active span, though
6574
expect(getActiveSpan()).toBe(undefined);

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { getAsyncContextStrategy } from '../../../src/asyncContext';
1515
import {
1616
SentrySpan,
1717
continueTrace,
18+
getDynamicSamplingContextFromSpan,
1819
registerSpanErrorInstrumentation,
1920
startInactiveSpan,
2021
startSpan,
@@ -217,6 +218,13 @@ describe('startSpan', () => {
217218

218219
expect(span).toBeDefined();
219220
expect(span).toBeInstanceOf(SentryNonRecordingSpan);
221+
expect(getDynamicSamplingContextFromSpan(span)).toEqual({
222+
environment: 'production',
223+
sample_rate: '0',
224+
sampled: 'false',
225+
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
226+
transaction: 'GET users/[id]',
227+
});
220228
});
221229

222230
it('creates & finishes span', async () => {
@@ -633,6 +641,13 @@ describe('startSpanManual', () => {
633641

634642
expect(span).toBeDefined();
635643
expect(span).toBeInstanceOf(SentryNonRecordingSpan);
644+
expect(getDynamicSamplingContextFromSpan(span)).toEqual({
645+
environment: 'production',
646+
sample_rate: '0',
647+
sampled: 'false',
648+
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
649+
transaction: 'GET users/[id]',
650+
});
636651
});
637652

638653
it('creates & finishes span', async () => {
@@ -971,6 +986,13 @@ describe('startInactiveSpan', () => {
971986

972987
expect(span).toBeDefined();
973988
expect(span).toBeInstanceOf(SentryNonRecordingSpan);
989+
expect(getDynamicSamplingContextFromSpan(span)).toEqual({
990+
environment: 'production',
991+
sample_rate: '0',
992+
sampled: 'false',
993+
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
994+
transaction: 'GET users/[id]',
995+
});
974996
});
975997

976998
it('creates & finishes span', async () => {

0 commit comments

Comments
 (0)