Skip to content

Commit 5800ce4

Browse files
committed
streamline span/trace ID generation
1 parent 68736ea commit 5800ce4

File tree

9 files changed

+88
-66
lines changed

9 files changed

+88
-66
lines changed

packages/core/src/tracing/sentryNonRecordingSpan.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type {
77
SpanStatus,
88
SpanTimeInput,
99
} from '@sentry/types';
10-
import { uuid4 } from '../utils-hoist/misc';
10+
import { generateSpanId, generateTraceId } from '../utils-hoist';
1111
import { TRACE_FLAG_NONE } from '../utils/spanUtils';
1212

1313
/**
@@ -18,8 +18,8 @@ export class SentryNonRecordingSpan implements Span {
1818
private _spanId: string;
1919

2020
public constructor(spanContext: SentrySpanArguments = {}) {
21-
this._traceId = spanContext.traceId || uuid4();
22-
this._spanId = spanContext.spanId || uuid4().substring(16);
21+
this._traceId = spanContext.traceId || generateTraceId();
22+
this._spanId = spanContext.spanId || generateSpanId();
2323
}
2424

2525
/** @inheritdoc */

packages/core/src/tracing/sentrySpan.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ import {
2525
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
2626
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
2727
} from '../semanticAttributes';
28+
import { generateSpanId, generateTraceId } from '../utils-hoist';
2829
import { logger } from '../utils-hoist/logger';
29-
import { uuid4 } from '../utils-hoist/misc';
3030
import { dropUndefinedKeys } from '../utils-hoist/object';
3131
import { timestampInSeconds } from '../utils-hoist/time';
3232
import {
@@ -76,8 +76,8 @@ export class SentrySpan implements Span {
7676
* @hidden
7777
*/
7878
public constructor(spanContext: SentrySpanArguments = {}) {
79-
this._traceId = spanContext.traceId || uuid4();
80-
this._spanId = spanContext.spanId || uuid4().substring(16);
79+
this._traceId = spanContext.traceId || generateTraceId();
80+
this._spanId = spanContext.spanId || generateSpanId();
8181
this._startTime = spanContext.startTimestamp || timestampInSeconds();
8282

8383
this._attributes = {};

packages/core/src/utils-hoist/tracing.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type { PropagationContext, TraceparentData } from '@sentry/types';
22

33
import { baggageHeaderToDynamicSamplingContext } from './baggage';
4-
import { uuid4 } from './misc';
54
import { generateSpanId, generateTraceId } from './propagationContext';
65

76
// eslint-disable-next-line @sentry-internal/sdk/no-regexp-constructor -- RegExp is used for readability here
@@ -76,8 +75,8 @@ export function propagationContextFromHeaders(
7675
* Create sentry-trace header from span context values.
7776
*/
7877
export function generateSentryTraceHeader(
79-
traceId: string = uuid4(),
80-
spanId: string = uuid4().substring(16),
78+
traceId: string = generateTraceId(),
79+
spanId: string = generateSpanId(),
8180
sampled?: boolean,
8281
): string {
8382
let sampledString = '';

packages/nextjs/src/common/wrapGenerationFunctionWithSentry.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import {
44
SPAN_STATUS_OK,
55
Scope,
66
captureException,
7+
generateSpanId,
8+
generateTraceId,
79
getActiveSpan,
810
getCapturedScopesOnSpan,
911
getClient,
@@ -14,7 +16,7 @@ import {
1416
withIsolationScope,
1517
withScope,
1618
} from '@sentry/core';
17-
import { propagationContextFromHeaders, uuid4, winterCGHeadersToDict } from '@sentry/core';
19+
import { propagationContextFromHeaders, winterCGHeadersToDict } from '@sentry/core';
1820
import type { RequestEventData, WebFetchHeaders } from '@sentry/types';
1921

2022
import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core';
@@ -87,8 +89,8 @@ export function wrapGenerationFunctionWithSentry<F extends (...args: any[]) => a
8789
headersDict?.['sentry-trace']
8890
? propagationContextFromHeaders(headersDict['sentry-trace'], headersDict['baggage'])
8991
: {
90-
traceId: requestTraceId || uuid4(),
91-
spanId: uuid4().substring(16),
92+
traceId: requestTraceId || generateTraceId(),
93+
spanId: generateSpanId(),
9294
},
9395
);
9496
scope.setPropagationContext(propagationContext);

packages/nextjs/src/common/wrapServerComponentWithSentry.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import {
44
SPAN_STATUS_OK,
55
Scope,
66
captureException,
7+
generateSpanId,
8+
generateTraceId,
79
getActiveSpan,
810
getCapturedScopesOnSpan,
911
getRootSpan,
@@ -13,7 +15,7 @@ import {
1315
withIsolationScope,
1416
withScope,
1517
} from '@sentry/core';
16-
import { propagationContextFromHeaders, uuid4, vercelWaitUntil, winterCGHeadersToDict } from '@sentry/core';
18+
import { propagationContextFromHeaders, vercelWaitUntil, winterCGHeadersToDict } from '@sentry/core';
1719
import type { RequestEventData } from '@sentry/types';
1820

1921
import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core';
@@ -65,8 +67,8 @@ export function wrapServerComponentWithSentry<F extends (...args: any[]) => any>
6567
headersDict?.['sentry-trace']
6668
? propagationContextFromHeaders(headersDict['sentry-trace'], headersDict['baggage'])
6769
: {
68-
traceId: requestTraceId || uuid4(),
69-
spanId: uuid4().substring(16),
70+
traceId: requestTraceId || generateTraceId(),
71+
spanId: generateSpanId(),
7072
},
7173
);
7274

packages/opentelemetry/src/propagator.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -206,13 +206,12 @@ export function getInjectionData(context: Context): {
206206
sampled: boolean | undefined;
207207
} {
208208
const span = trace.getSpan(context);
209-
const spanIsRemote = span?.spanContext().isRemote;
210209

211-
// If we have a local span, we can just pick everything from it
212-
if (span && !spanIsRemote) {
210+
// If we have a span - no matter if local or remote - we use it
211+
if (span) {
213212
const spanContext = span.spanContext();
214-
215213
const dynamicSamplingContext = getDynamicSamplingContextFromSpan(span);
214+
216215
return {
217216
dynamicSamplingContext,
218217
traceId: spanContext.traceId,
@@ -222,6 +221,7 @@ export function getInjectionData(context: Context): {
222221
}
223222

224223
// Else we try to use the propagation context from the scope
224+
// The only scenario where this should happen is when we neither have a span, nor an incoming trace
225225
const scope = getScopesFromContext(context)?.scope || getCurrentScope();
226226
const client = getClient();
227227

Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, spanToJSON } from '@sentry/core';
1+
import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, hasTracingEnabled, spanToJSON } from '@sentry/core';
22
import type { Client } from '@sentry/types';
3+
import { getSamplingDecision } from './getSamplingDecision';
34
import { parseSpanDescription } from './parseSpanDescription';
45
import { spanHasName } from './spanTypes';
56

@@ -9,20 +10,31 @@ import { spanHasName } from './spanTypes';
910
*/
1011
export function enhanceDscWithOpenTelemetryRootSpanName(client: Client): void {
1112
client.on('createDsc', (dsc, rootSpan) => {
13+
if (!rootSpan) {
14+
return;
15+
}
16+
1217
// We want to overwrite the transaction on the DSC that is created by default in core
1318
// The reason for this is that we want to infer the span name, not use the initial one
1419
// Otherwise, we'll get names like "GET" instead of e.g. "GET /foo"
1520
// `parseSpanDescription` takes the attributes of the span into account for the name
1621
// This mutates the passed-in DSC
17-
if (rootSpan) {
18-
const jsonSpan = spanToJSON(rootSpan);
19-
const attributes = jsonSpan.data || {};
20-
const source = attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE];
2122

22-
const { description } = spanHasName(rootSpan) ? parseSpanDescription(rootSpan) : { description: undefined };
23-
if (source !== 'url' && description) {
24-
dsc.transaction = description;
25-
}
23+
const jsonSpan = spanToJSON(rootSpan);
24+
const attributes = jsonSpan.data || {};
25+
const source = attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE];
26+
27+
const { description } = spanHasName(rootSpan) ? parseSpanDescription(rootSpan) : { description: undefined };
28+
if (source !== 'url' && description) {
29+
dsc.transaction = description;
30+
}
31+
32+
// Also ensure sampling decision is correctly inferred
33+
// In core, we use `spanIsSampled`, which just looks at the trace flags
34+
// but in OTEL, we use a slightly more complex logic to be able to differntiate between unsampled and deferred sampling
35+
if (hasTracingEnabled()) {
36+
const sampled = getSamplingDecision(rootSpan.spanContext());
37+
dsc.sampled = sampled == undefined ? undefined : String(sampled);
2638
}
2739
});
2840
}

packages/opentelemetry/test/helpers/initOtel.ts

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { SentryPropagator } from '../../src/propagator';
1616
import { SentrySampler } from '../../src/sampler';
1717
import { setupEventContextTrace } from '../../src/setupEventContextTrace';
1818
import { SentrySpanProcessor } from '../../src/spanProcessor';
19+
import { enhanceDscWithOpenTelemetryRootSpanName } from '../../src/utils/enhanceDscWithOpenTelemetryRootSpanName';
1920
import type { TestClientInterface } from './TestClient';
2021

2122
/**
@@ -44,6 +45,7 @@ export function initOtel(): void {
4445
}
4546

4647
setupEventContextTrace(client);
48+
enhanceDscWithOpenTelemetryRootSpanName(client);
4749

4850
const provider = setupOtel(client);
4951
client.traceProvider = provider;

packages/opentelemetry/test/propagator.test.ts

+43-38
Original file line numberDiff line numberDiff line change
@@ -101,39 +101,6 @@ describe('SentryPropagator', () => {
101101
});
102102
});
103103

104-
it('uses scope propagation context over remote spanContext', () => {
105-
context.with(
106-
trace.setSpanContext(ROOT_CONTEXT, {
107-
traceId: 'd4cda95b652f4a1592b449d5929fda1b',
108-
spanId: '6e0c63257de34c92',
109-
traceFlags: TraceFlags.NONE,
110-
isRemote: true,
111-
}),
112-
() => {
113-
withScope(scope => {
114-
scope.setPropagationContext({
115-
traceId: 'TRACE_ID',
116-
parentSpanId: 'PARENT_SPAN_ID',
117-
spanId: 'SPAN_ID',
118-
sampled: true,
119-
});
120-
121-
propagator.inject(context.active(), carrier, defaultTextMapSetter);
122-
123-
expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toEqual(
124-
[
125-
'sentry-environment=production',
126-
'sentry-release=1.0.0',
127-
'sentry-public_key=abc',
128-
'sentry-trace_id=TRACE_ID',
129-
].sort(),
130-
);
131-
expect(carrier[SENTRY_TRACE_HEADER]).toBe('TRACE_ID-SPAN_ID-1');
132-
});
133-
},
134-
);
135-
});
136-
137104
it('uses propagation data from current scope if no scope & span is found', () => {
138105
const scope = getCurrentScope();
139106
const traceId = scope.getPropagationContext().traceId;
@@ -380,8 +347,10 @@ describe('SentryPropagator', () => {
380347
});
381348
},
382349
);
350+
});
383351

384-
const carrier2: Record<string, string> = {};
352+
it('uses remote span with deferred sampling decision over propagation context', () => {
353+
const carrier: Record<string, string> = {};
385354
context.with(
386355
trace.setSpanContext(ROOT_CONTEXT, {
387356
traceId: 'd4cda95b652f4a1592b449d5929fda1b',
@@ -398,17 +367,53 @@ describe('SentryPropagator', () => {
398367
sampled: true,
399368
});
400369

401-
propagator.inject(context.active(), carrier2, defaultTextMapSetter);
370+
propagator.inject(context.active(), carrier, defaultTextMapSetter);
402371

403-
expect(baggageToArray(carrier2[SENTRY_BAGGAGE_HEADER])).toEqual(
372+
expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toEqual(
373+
[
374+
'sentry-environment=production',
375+
'sentry-release=1.0.0',
376+
'sentry-public_key=abc',
377+
'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b',
378+
].sort(),
379+
);
380+
expect(carrier[SENTRY_TRACE_HEADER]).toBe('d4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92');
381+
});
382+
},
383+
);
384+
});
385+
386+
it('uses remote span over propagation context', () => {
387+
const carrier: Record<string, string> = {};
388+
context.with(
389+
trace.setSpanContext(ROOT_CONTEXT, {
390+
traceId: 'd4cda95b652f4a1592b449d5929fda1b',
391+
spanId: '6e0c63257de34c92',
392+
traceFlags: TraceFlags.NONE,
393+
isRemote: true,
394+
traceState: makeTraceState({ sampled: false }),
395+
}),
396+
() => {
397+
withScope(scope => {
398+
scope.setPropagationContext({
399+
traceId: 'TRACE_ID',
400+
parentSpanId: 'PARENT_SPAN_ID',
401+
spanId: 'SPAN_ID',
402+
sampled: true,
403+
});
404+
405+
propagator.inject(context.active(), carrier, defaultTextMapSetter);
406+
407+
expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toEqual(
404408
[
405409
'sentry-environment=production',
406410
'sentry-release=1.0.0',
407411
'sentry-public_key=abc',
408-
'sentry-trace_id=TRACE_ID',
412+
'sentry-sampled=false',
413+
'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b',
409414
].sort(),
410415
);
411-
expect(carrier2[SENTRY_TRACE_HEADER]).toBe('TRACE_ID-SPAN_ID-1');
416+
expect(carrier[SENTRY_TRACE_HEADER]).toBe('d4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-0');
412417
});
413418
},
414419
);

0 commit comments

Comments
 (0)