Skip to content

Commit 1d98867

Browse files
authored
ref(core): Ensure non-recording root spans have frozen DSC (#14964)
Otherwise, parts of the DSC will be missing - we try to make it as complete as we can. Since the span cannot be updated anyhow (e.g. the name cannot be changed), we can safely freeze this at this time. Extracted out of #14955
1 parent 3ea500f commit 1d98867

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)