Skip to content

Commit 68736ea

Browse files
committed
feat(opentelemetry): Stop looking at propagation context for span creation
1 parent e2883df commit 68736ea

File tree

14 files changed

+103
-165
lines changed

14 files changed

+103
-165
lines changed

dev-packages/node-integration-tests/suites/aws-serverless/aws-integration/s3/test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,6 @@ describe('awsIntegration', () => {
2626
});
2727

2828
test('should auto-instrument aws-sdk v2 package.', done => {
29-
createRunner(__dirname, 'scenario.js').expect({ transaction: EXPECTED_TRANSCATION }).start(done);
29+
createRunner(__dirname, 'scenario.js').ignore('event').expect({ transaction: EXPECTED_TRANSCATION }).start(done);
3030
});
3131
});

dev-packages/node-integration-tests/suites/public-api/startSpan/parallel-spans-in-scope-with-parentSpanId/scenario.ts

+14-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,18 @@ Sentry.withScope(scope => {
1515
traceId: '12345678901234567890123456789012',
1616
});
1717

18-
Sentry.startSpan({ name: 'test_span_1' }, () => undefined);
19-
Sentry.startSpan({ name: 'test_span_2' }, () => undefined);
18+
const spanIdTraceId = Sentry.startSpan(
19+
{
20+
name: 'test_span_1',
21+
},
22+
span1 => span1.spanContext().traceId,
23+
);
24+
25+
Sentry.startSpan(
26+
{
27+
name: 'test_span_2',
28+
attributes: { spanIdTraceId },
29+
},
30+
() => undefined,
31+
);
2032
});

dev-packages/node-integration-tests/suites/public-api/startSpan/parallel-spans-in-scope-with-parentSpanId/test.ts

+12-21
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,19 @@ afterAll(() => {
66

77
test('should send manually started parallel root spans outside of root context with parentSpanId', done => {
88
createRunner(__dirname, 'scenario.ts')
9+
.expect({ transaction: { transaction: 'test_span_1' } })
910
.expect({
10-
transaction: {
11-
transaction: 'test_span_1',
12-
contexts: {
13-
trace: {
14-
span_id: expect.any(String),
15-
parent_span_id: '1234567890123456',
16-
trace_id: '12345678901234567890123456789012',
17-
},
18-
},
19-
},
20-
})
21-
.expect({
22-
transaction: {
23-
transaction: 'test_span_2',
24-
contexts: {
25-
trace: {
26-
span_id: expect.any(String),
27-
parent_span_id: '1234567890123456',
28-
trace_id: '12345678901234567890123456789012',
29-
},
30-
},
11+
transaction: transaction => {
12+
expect(transaction).toBeDefined();
13+
const traceId = transaction.contexts?.trace?.trace_id;
14+
expect(traceId).toBeDefined();
15+
expect(transaction.contexts?.trace?.parent_span_id).toBeUndefined();
16+
17+
const trace1Id = transaction.contexts?.trace?.data?.spanIdTraceId;
18+
expect(trace1Id).toBeDefined();
19+
20+
// Different trace ID as the first span
21+
expect(trace1Id).not.toBe(traceId);
3122
},
3223
})
3324
.start(done);

dev-packages/node-integration-tests/suites/public-api/startSpan/parallel-spans-in-scope/test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ test('should send manually started parallel root spans outside of root context',
1919
const trace1Id = transaction.contexts?.trace?.data?.spanIdTraceId;
2020
expect(trace1Id).toBeDefined();
2121

22-
// Same trace ID as the first span
23-
expect(trace1Id).toBe(traceId);
22+
// Different trace ID as the first span
23+
expect(trace1Id).not.toBe(traceId);
2424
},
2525
})
2626
.start(done);

packages/opentelemetry/src/constants.ts

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ export const SENTRY_TRACE_HEADER = 'sentry-trace';
44
export const SENTRY_BAGGAGE_HEADER = 'baggage';
55

66
export const SENTRY_TRACE_STATE_DSC = 'sentry.dsc';
7-
export const SENTRY_TRACE_STATE_PARENT_SPAN_ID = 'sentry.parent_span_id';
87
export const SENTRY_TRACE_STATE_SAMPLED_NOT_RECORDING = 'sentry.sampled_not_recording';
98
export const SENTRY_TRACE_STATE_URL = 'sentry.url';
109

packages/opentelemetry/src/propagator.ts

+27-12
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,14 @@ import {
2626
SENTRY_BAGGAGE_HEADER,
2727
SENTRY_TRACE_HEADER,
2828
SENTRY_TRACE_STATE_DSC,
29-
SENTRY_TRACE_STATE_PARENT_SPAN_ID,
3029
SENTRY_TRACE_STATE_URL,
3130
} from './constants';
3231
import { DEBUG_BUILD } from './debug-build';
3332
import { getScopesFromContext, setScopesOnContext } from './utils/contextData';
3433
import { getSamplingDecision } from './utils/getSamplingDecision';
3534
import { makeTraceState } from './utils/makeTraceState';
3635
import { setIsSetup } from './utils/setupCheck';
36+
import { spanHasParentId } from './utils/spanTypes';
3737

3838
/** Get the Sentry propagation context from a span context. */
3939
export function getPropagationContextFromSpan(span: Span): PropagationContext {
@@ -45,8 +45,7 @@ export function getPropagationContextFromSpan(span: Span): PropagationContext {
4545
const dscString = traceState ? traceState.get(SENTRY_TRACE_STATE_DSC) : undefined;
4646
const traceStateDsc = dscString ? baggageHeaderToDynamicSamplingContext(dscString) : undefined;
4747

48-
const parentSpanId = traceState ? traceState.get(SENTRY_TRACE_STATE_PARENT_SPAN_ID) || undefined : undefined;
49-
48+
const parentSpanId = spanHasParentId(span) ? span.parentSpanId : undefined;
5049
const sampled = getSamplingDecision(spanContext);
5150

5251
// No trace state? --> Take DSC from root span
@@ -244,7 +243,15 @@ function getContextWithRemoteActiveSpan(
244243
): Context {
245244
const propagationContext = propagationContextFromHeaders(sentryTrace, baggage);
246245

247-
const spanContext = generateSpanContextForPropagationContext(propagationContext);
246+
const { traceId, parentSpanId, sampled, dsc } = propagationContext;
247+
248+
// We only want to set the virtual span if we are continuing a concrete trace
249+
// Otherwise, we ignore the propagation context
250+
if (!parentSpanId) {
251+
return ctx;
252+
}
253+
254+
const spanContext = generateSpanContextForPropagationContext({ traceId, parentSpanId, sampled, dsc });
248255
return trace.setSpanContext(ctx, spanContext);
249256
}
250257

@@ -299,20 +306,28 @@ function getCurrentURL(span: Span): string | undefined {
299306
return undefined;
300307
}
301308

302-
// TODO: Adjust this behavior to avoid invalid spans
303-
function generateSpanContextForPropagationContext(propagationContext: PropagationContext): SpanContext {
309+
function generateSpanContextForPropagationContext({
310+
parentSpanId,
311+
traceId,
312+
sampled,
313+
dsc,
314+
}: {
315+
parentSpanId: string;
316+
traceId: string;
317+
sampled: boolean | undefined;
318+
dsc?: Partial<DynamicSamplingContext>;
319+
}): SpanContext {
304320
// We store the DSC as OTEL trace state on the span context
305321
const traceState = makeTraceState({
306-
parentSpanId: propagationContext.parentSpanId,
307-
dsc: propagationContext.dsc,
308-
sampled: propagationContext.sampled,
322+
dsc,
323+
sampled,
309324
});
310325

311326
const spanContext: SpanContext = {
312-
traceId: propagationContext.traceId,
313-
spanId: propagationContext.parentSpanId || '',
327+
traceId,
328+
spanId: parentSpanId,
314329
isRemote: true,
315-
traceFlags: propagationContext.sampled ? TraceFlags.SAMPLED : TraceFlags.NONE,
330+
traceFlags: sampled ? TraceFlags.SAMPLED : TraceFlags.NONE,
316331
traceState,
317332
};
318333

packages/opentelemetry/src/setupEventContextTrace.ts

+2-21
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1-
import { getDynamicSamplingContextFromSpan, getRootSpan } from '@sentry/core';
2-
import { dropUndefinedKeys } from '@sentry/core';
1+
import { getDynamicSamplingContextFromSpan, getRootSpan, spanToTraceContext } from '@sentry/core';
32
import type { Client } from '@sentry/types';
4-
import { SENTRY_TRACE_STATE_PARENT_SPAN_ID } from './constants';
53
import { getActiveSpan } from './utils/getActiveSpan';
6-
import { spanHasParentId } from './utils/spanTypes';
74

85
/** Ensure the `trace` context is set on all events. */
96
export function setupEventContextTrace(client: Client): void {
@@ -15,25 +12,9 @@ export function setupEventContextTrace(client: Client): void {
1512
return;
1613
}
1714

18-
const spanContext = span.spanContext();
19-
20-
// If we have a parent span id from trace state, use that ('' means no parent should be used)
21-
// Else, pick the one from the span
22-
const parentSpanIdFromTraceState = spanContext.traceState?.get(SENTRY_TRACE_STATE_PARENT_SPAN_ID);
23-
const parent_span_id =
24-
typeof parentSpanIdFromTraceState === 'string'
25-
? parentSpanIdFromTraceState || undefined
26-
: spanHasParentId(span)
27-
? span.parentSpanId
28-
: undefined;
29-
3015
// If event has already set `trace` context, use that one.
3116
event.contexts = {
32-
trace: dropUndefinedKeys({
33-
trace_id: spanContext.traceId,
34-
span_id: spanContext.spanId,
35-
parent_span_id,
36-
}),
17+
trace: spanToTraceContext(span),
3718
...event.contexts,
3819
};
3920

packages/opentelemetry/src/spanExporter.ts

+1-5
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import {
1919
} from '@sentry/core';
2020
import { dropUndefinedKeys, logger } from '@sentry/core';
2121
import type { SpanJSON, SpanOrigin, TraceContext, TransactionEvent, TransactionSource } from '@sentry/types';
22-
import { SENTRY_TRACE_STATE_PARENT_SPAN_ID } from './constants';
2322

2423
import { DEBUG_BUILD } from './debug-build';
2524
import { SEMANTIC_ATTRIBUTE_SENTRY_PARENT_IS_REMOTE } from './semanticAttributes';
@@ -242,15 +241,12 @@ function createTransactionForOtelSpan(span: ReadableSpan): TransactionEvent {
242241

243242
const { traceId: trace_id, spanId: span_id } = span.spanContext();
244243

245-
const parentSpanIdFromTraceState = span.spanContext().traceState?.get(SENTRY_TRACE_STATE_PARENT_SPAN_ID);
246-
247244
// If parentSpanIdFromTraceState is defined at all, we want it to take precedence
248245
// In that case, an empty string should be interpreted as "no parent span id",
249246
// even if `span.parentSpanId` is set
250247
// this is the case when we are starting a new trace, where we have a virtual span based on the propagationContext
251248
// We only want to continue the traceId in this case, but ignore the parent span
252-
const parent_span_id =
253-
typeof parentSpanIdFromTraceState === 'string' ? parentSpanIdFromTraceState || undefined : span.parentSpanId;
249+
const parent_span_id = span.parentSpanId;
254250

255251
const status = mapStatus(span);
256252

packages/opentelemetry/src/trace.ts

+4-34
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Context, Span, SpanContext, SpanOptions, Tracer } from '@opentelemetry/api';
2-
import { INVALID_SPANID, SpanStatusCode, TraceFlags, context, trace } from '@opentelemetry/api';
2+
import { SpanStatusCode, TraceFlags, context, trace } from '@opentelemetry/api';
33
import { suppressTracing } from '@opentelemetry/core';
44
import {
55
SDK_VERSION,
@@ -19,7 +19,7 @@ import type { Client, DynamicSamplingContext, Scope, Span as SentrySpan, TraceCo
1919
import { continueTraceAsRemoteSpan } from './propagator';
2020

2121
import type { OpenTelemetryClient, OpenTelemetrySpanContext } from './types';
22-
import { getContextFromScope, getScopesFromContext } from './utils/contextData';
22+
import { getContextFromScope } from './utils/contextData';
2323
import { getSamplingDecision } from './utils/getSamplingDecision';
2424
import { makeTraceState } from './utils/makeTraceState';
2525

@@ -179,40 +179,11 @@ function ensureTimestampInMilliseconds(timestamp: number): number {
179179

180180
function getContext(scope: Scope | undefined, forceTransaction: boolean | undefined): Context {
181181
const ctx = getContextForScope(scope);
182-
// Note: If the context is the ROOT_CONTEXT, no scope is attached
183-
// Thus we will not use the propagation context in this case, which is desired
184-
const actualScope = getScopesFromContext(ctx)?.scope;
185182
const parentSpan = trace.getSpan(ctx);
186183

187-
// In the case that we have no parent span, we need to "simulate" one to ensure the propagation context is correct
184+
// In the case that we have no parent span, we start a new trace
185+
// Note that if we continue a trace, we'll always have a remote parent span here anyhow
188186
if (!parentSpan) {
189-
const client = getClient();
190-
191-
if (actualScope && client) {
192-
const propagationContext = actualScope.getPropagationContext();
193-
194-
// We store the DSC as OTEL trace state on the span context
195-
const traceState = makeTraceState({
196-
parentSpanId: propagationContext.parentSpanId,
197-
// Not defined yet, we want to pick this up on-demand only
198-
dsc: undefined,
199-
sampled: propagationContext.sampled,
200-
});
201-
202-
const spanOptions: SpanContext = {
203-
traceId: propagationContext.traceId,
204-
// eslint-disable-next-line deprecation/deprecation
205-
spanId: propagationContext.parentSpanId || propagationContext.spanId,
206-
isRemote: true,
207-
traceFlags: propagationContext.sampled ? TraceFlags.SAMPLED : TraceFlags.NONE,
208-
traceState,
209-
};
210-
211-
// Add remote parent span context,
212-
return trace.setSpanContext(ctx, spanOptions);
213-
}
214-
215-
// if we have no scope or client, we just return the context as-is
216187
return ctx;
217188
}
218189

@@ -238,7 +209,6 @@ function getContext(scope: Scope | undefined, forceTransaction: boolean | undefi
238209

239210
const traceState = makeTraceState({
240211
dsc,
241-
parentSpanId: spanId !== INVALID_SPANID ? spanId : undefined,
242212
sampled,
243213
});
244214

packages/opentelemetry/src/utils/generateSpanContextForPropagationContext.ts

-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import { makeTraceState } from './makeTraceState';
1212
export function generateSpanContextForPropagationContext(propagationContext: PropagationContext): SpanContext {
1313
// We store the DSC as OTEL trace state on the span context
1414
const traceState = makeTraceState({
15-
parentSpanId: propagationContext.parentSpanId,
1615
dsc: propagationContext.dsc,
1716
sampled: propagationContext.sampled,
1817
});

packages/opentelemetry/src/utils/makeTraceState.ts

+2-12
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,22 @@
11
import { TraceState } from '@opentelemetry/core';
22
import { dynamicSamplingContextToSentryBaggageHeader } from '@sentry/core';
33
import type { DynamicSamplingContext } from '@sentry/types';
4-
import {
5-
SENTRY_TRACE_STATE_DSC,
6-
SENTRY_TRACE_STATE_PARENT_SPAN_ID,
7-
SENTRY_TRACE_STATE_SAMPLED_NOT_RECORDING,
8-
} from '../constants';
4+
import { SENTRY_TRACE_STATE_DSC, SENTRY_TRACE_STATE_SAMPLED_NOT_RECORDING } from '../constants';
95

106
/**
117
* Generate a TraceState for the given data.
128
*/
139
export function makeTraceState({
14-
parentSpanId,
1510
dsc,
1611
sampled,
1712
}: {
18-
parentSpanId?: string;
1913
dsc?: Partial<DynamicSamplingContext>;
2014
sampled?: boolean;
2115
}): TraceState {
2216
// We store the DSC as OTEL trace state on the span context
2317
const dscString = dsc ? dynamicSamplingContextToSentryBaggageHeader(dsc) : undefined;
2418

25-
// We _always_ set the parent span ID, even if it is empty
26-
// If we'd set this to 'undefined' we could not know if the trace state was set, but there was no parentSpanId,
27-
// vs the trace state was not set at all (in which case we want to do fallback handling)
28-
// If `''`, it should be considered "no parent"
29-
const traceStateBase = new TraceState().set(SENTRY_TRACE_STATE_PARENT_SPAN_ID, parentSpanId || '');
19+
const traceStateBase = new TraceState();
3020

3121
const traceStateWithDsc = dscString ? traceStateBase.set(SENTRY_TRACE_STATE_DSC, dscString) : traceStateBase;
3222

packages/opentelemetry/test/integration/transactions.test.ts

-1
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,6 @@ describe('Integration | Transactions', () => {
327327
const parentSpanId = '6e0c63257de34c92';
328328

329329
const traceState = makeTraceState({
330-
parentSpanId,
331330
dsc: undefined,
332331
sampled: true,
333332
});

0 commit comments

Comments
 (0)