Skip to content

Commit 74603fe

Browse files
author
Luca Forstner
committed
ref: Read propagation context off of scope and isolation scope when propagating
1 parent 5f0b506 commit 74603fe

File tree

7 files changed

+140
-83
lines changed

7 files changed

+140
-83
lines changed

packages/core/src/baseclient.ts

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -638,21 +638,13 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
638638
return evt;
639639
}
640640

641-
// If a trace context is not set on the event, we use the propagationContext set on the event to
642-
// generate a trace context. If the propagationContext does not have a dynamic sampling context, we
643-
// also generate one for it.
644-
const { propagationContext } = evt.sdkProcessingMetadata || {};
645-
const trace = evt.contexts && evt.contexts.trace;
646-
if (!trace && propagationContext) {
647-
const { traceId: trace_id, spanId, parentSpanId, dsc } = propagationContext as PropagationContext;
648-
evt.contexts = {
649-
trace: {
650-
trace_id,
651-
span_id: spanId,
652-
parent_span_id: parentSpanId,
653-
},
654-
...evt.contexts,
655-
};
641+
const propagationContext = {
642+
...isolationScope.getPropagationContext(),
643+
...(scope ? scope.getPropagationContext() : undefined),
644+
};
645+
646+
if (propagationContext && (!evt.sdkProcessingMetadata || !evt.sdkProcessingMetadata.dynamicSamplingContext)) {
647+
const { traceId: trace_id, dsc } = propagationContext;
656648

657649
const dynamicSamplingContext = dsc ? dsc : getDynamicSamplingContextFromClient(trace_id, this, scope);
658650

packages/core/src/utils/applyScopeDataToEvent.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,16 @@ export function applyScopeDataToEvent(event: Event, data: ScopeData): void {
1616
// We want to set the trace context for normal events only if there isn't already
1717
// a trace context on the event. There is a product feature in place where we link
1818
// errors with transaction and it relies on that.
19+
// If there is a span, we use that to apply the trace data, if not, we use the propagation context as a fallback.
1920
if (span) {
2021
applySpanToEvent(event, span);
22+
} else {
23+
applyPropagationContextToEvent(event, propagationContext);
2124
}
2225

2326
applyFingerprintToEvent(event, fingerprint);
2427
applyBreadcrumbsToEvent(event, breadcrumbs);
25-
applySdkMetadataToEvent(event, sdkProcessingMetadata, propagationContext);
28+
applySdkMetadataToEvent(event, sdkProcessingMetadata);
2629
}
2730

2831
/** Merge data of two scopes together. */
@@ -163,15 +166,10 @@ function applyBreadcrumbsToEvent(event: Event, breadcrumbs: Breadcrumb[]): void
163166
event.breadcrumbs = mergedBreadcrumbs.length ? mergedBreadcrumbs : undefined;
164167
}
165168

166-
function applySdkMetadataToEvent(
167-
event: Event,
168-
sdkProcessingMetadata: ScopeData['sdkProcessingMetadata'],
169-
propagationContext: PropagationContext,
170-
): void {
169+
function applySdkMetadataToEvent(event: Event, sdkProcessingMetadata: ScopeData['sdkProcessingMetadata']): void {
171170
event.sdkProcessingMetadata = {
172171
...event.sdkProcessingMetadata,
173172
...sdkProcessingMetadata,
174-
propagationContext: propagationContext,
175173
};
176174
}
177175

@@ -190,6 +188,16 @@ function applySpanToEvent(event: Event, span: Span): void {
190188
}
191189
}
192190

191+
function applyPropagationContextToEvent(event: Event, propagationContext: PropagationContext): void {
192+
event.contexts = {
193+
trace: {
194+
trace_id: propagationContext.traceId,
195+
span_id: propagationContext.spanId,
196+
},
197+
...event.contexts,
198+
};
199+
}
200+
193201
/**
194202
* Applies fingerprint from the scope to the event if there's one,
195203
* uses message if there's one instead or get rid of empty fingerprint

packages/node/src/integrations/http.ts

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import type * as http from 'http';
22
import type * as https from 'https';
33
import type { Hub } from '@sentry/core';
4+
import { getIsolationScope } from '@sentry/core';
5+
import { getRootSpan } from '@sentry/core';
46
import {
57
addBreadcrumb,
68
getActiveSpan,
@@ -13,13 +15,7 @@ import {
1315
spanToJSON,
1416
spanToTraceHeader,
1517
} from '@sentry/core';
16-
import type {
17-
DynamicSamplingContext,
18-
EventProcessor,
19-
Integration,
20-
SanitizedRequestData,
21-
TracePropagationTargets,
22-
} from '@sentry/types';
18+
import type { EventProcessor, Integration, SanitizedRequestData, TracePropagationTargets } from '@sentry/types';
2319
import {
2420
LRUMap,
2521
dynamicSamplingContextToSentryBaggageHeader,
@@ -250,13 +246,15 @@ function _createWrappedRequestMethodFactory(
250246
// eslint-disable-next-line deprecation/deprecation
251247
const rawRequestUrl = extractRawUrl(requestOptions);
252248
const requestUrl = extractUrl(requestOptions);
249+
const client = getClient();
253250

254251
// we don't want to record requests to Sentry as either breadcrumbs or spans, so just use the original method
255-
if (isSentryRequestUrl(requestUrl, getClient())) {
252+
if (isSentryRequestUrl(requestUrl, client)) {
256253
return originalRequestMethod.apply(httpModule, requestArgs);
257254
}
258255

259256
const scope = getCurrentScope();
257+
const isolationScope = getIsolationScope();
260258
const parentSpan = getActiveSpan();
261259

262260
const data = getRequestSpanData(requestUrl, requestOptions);
@@ -271,19 +269,26 @@ function _createWrappedRequestMethodFactory(
271269
})
272270
: undefined;
273271

274-
if (shouldAttachTraceData(rawRequestUrl)) {
275-
if (requestSpan) {
276-
const sentryTraceHeader = spanToTraceHeader(requestSpan);
277-
const dynamicSamplingContext = getDynamicSamplingContextFromSpan(requestSpan);
278-
addHeadersToRequestOptions(requestOptions, requestUrl, sentryTraceHeader, dynamicSamplingContext);
279-
} else {
280-
const client = getClient();
281-
const { traceId, sampled, dsc } = scope.getPropagationContext();
282-
const sentryTraceHeader = generateSentryTraceHeader(traceId, undefined, sampled);
283-
const dynamicSamplingContext =
284-
dsc || (client ? getDynamicSamplingContextFromClient(traceId, client, scope) : undefined);
285-
addHeadersToRequestOptions(requestOptions, requestUrl, sentryTraceHeader, dynamicSamplingContext);
286-
}
272+
if (client && shouldAttachTraceData(rawRequestUrl)) {
273+
const transaction = requestSpan && getRootSpan(requestSpan);
274+
275+
const { traceId, spanId, sampled, dsc } = {
276+
...isolationScope.getPropagationContext(),
277+
...scope.getPropagationContext(),
278+
};
279+
280+
const sentryTraceHeader = requestSpan
281+
? spanToTraceHeader(requestSpan)
282+
: generateSentryTraceHeader(traceId, spanId, sampled);
283+
284+
const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader(
285+
dsc ||
286+
(transaction
287+
? getDynamicSamplingContextFromSpan(transaction)
288+
: getDynamicSamplingContextFromClient(traceId, client, scope)),
289+
);
290+
291+
addHeadersToRequestOptions(requestOptions, requestUrl, sentryTraceHeader, sentryBaggageHeader);
287292
} else {
288293
DEBUG_BUILD &&
289294
logger.log(
@@ -333,7 +338,7 @@ function addHeadersToRequestOptions(
333338
requestOptions: RequestOptions,
334339
requestUrl: string,
335340
sentryTraceHeader: string,
336-
dynamicSamplingContext: Partial<DynamicSamplingContext> | undefined,
341+
sentryBaggageHeader: string | undefined,
337342
): void {
338343
// Don't overwrite sentry-trace and baggage header if it's already set.
339344
const headers = requestOptions.headers || {};
@@ -343,15 +348,13 @@ function addHeadersToRequestOptions(
343348

344349
DEBUG_BUILD &&
345350
logger.log(`[Tracing] Adding sentry-trace header ${sentryTraceHeader} to outgoing request to "${requestUrl}": `);
346-
const sentryBaggage = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext);
347-
const sentryBaggageHeader =
348-
sentryBaggage && sentryBaggage.length > 0 ? normalizeBaggageHeader(requestOptions, sentryBaggage) : undefined;
349351

350352
requestOptions.headers = {
351353
...requestOptions.headers,
352354
'sentry-trace': sentryTraceHeader,
353355
// Setting a header to `undefined` will crash in node so we only set the baggage header when it's defined
354-
...(sentryBaggageHeader && { baggage: sentryBaggageHeader }),
356+
...(sentryBaggageHeader &&
357+
sentryBaggageHeader.length > 0 && { baggage: normalizeBaggageHeader(requestOptions, sentryBaggageHeader) }),
355358
};
356359
}
357360

packages/node/src/integrations/undici/index.ts

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import {
55
getCurrentScope,
66
getDynamicSamplingContextFromClient,
77
getDynamicSamplingContextFromSpan,
8+
getIsolationScope,
9+
getRootSpan,
810
isSentryRequestUrl,
911
spanToTraceHeader,
1012
} from '@sentry/core';
@@ -157,6 +159,7 @@ export class Undici implements Integration {
157159

158160
const clientOptions = client.getOptions();
159161
const scope = getCurrentScope();
162+
const isolationScope = getIsolationScope();
160163
const parentSpan = getActiveSpan();
161164

162165
const span = this._shouldCreateSpan(stringUrl) ? createRequestSpan(parentSpan, request, stringUrl) : undefined;
@@ -180,18 +183,23 @@ export class Undici implements Integration {
180183
};
181184

182185
if (shouldAttachTraceData(stringUrl)) {
183-
if (span) {
184-
const dynamicSamplingContext = getDynamicSamplingContextFromSpan(span);
185-
const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext);
186-
187-
setHeadersOnRequest(request, spanToTraceHeader(span), sentryBaggageHeader);
188-
} else {
189-
const { traceId, sampled, dsc } = scope.getPropagationContext();
190-
const sentryTrace = generateSentryTraceHeader(traceId, undefined, sampled);
191-
const dynamicSamplingContext = dsc || getDynamicSamplingContextFromClient(traceId, client, scope);
192-
const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext);
193-
setHeadersOnRequest(request, sentryTrace, sentryBaggageHeader);
194-
}
186+
const transaction = span && getRootSpan(span);
187+
188+
const { traceId, spanId, sampled, dsc } = {
189+
...isolationScope.getPropagationContext(),
190+
...scope.getPropagationContext(),
191+
};
192+
193+
const sentryTraceHeader = span ? spanToTraceHeader(span) : generateSentryTraceHeader(traceId, spanId, sampled);
194+
195+
const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader(
196+
dsc ||
197+
(transaction
198+
? getDynamicSamplingContextFromSpan(transaction)
199+
: getDynamicSamplingContextFromClient(traceId, client, scope)),
200+
);
201+
202+
setHeadersOnRequest(request, sentryTraceHeader, sentryBaggageHeader);
195203
}
196204
};
197205

packages/tracing-internal/src/browser/request.ts

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
getCurrentScope,
66
getDynamicSamplingContextFromClient,
77
getDynamicSamplingContextFromSpan,
8+
getIsolationScope,
89
getRootSpan,
910
hasTracingEnabled,
1011
spanToJSON,
@@ -275,6 +276,7 @@ export function xhrCallback(
275276
}
276277

277278
const scope = getCurrentScope();
279+
const isolationScope = getIsolationScope();
278280
const parentSpan = getActiveSpan();
279281

280282
const span =
@@ -297,21 +299,26 @@ export function xhrCallback(
297299
spans[xhr.__sentry_xhr_span_id__] = span;
298300
}
299301

300-
if (xhr.setRequestHeader && shouldAttachHeaders(sentryXhrData.url)) {
301-
if (span) {
302-
const transaction = span && getRootSpan(span);
303-
const dynamicSamplingContext = transaction && getDynamicSamplingContextFromSpan(transaction);
304-
const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext);
305-
setHeaderOnXhr(xhr, spanToTraceHeader(span), sentryBaggageHeader);
306-
} else {
307-
const client = getClient();
308-
const { traceId, sampled, dsc } = scope.getPropagationContext();
309-
const sentryTraceHeader = generateSentryTraceHeader(traceId, undefined, sampled);
310-
const dynamicSamplingContext =
311-
dsc || (client ? getDynamicSamplingContextFromClient(traceId, client, scope) : undefined);
312-
const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext);
313-
setHeaderOnXhr(xhr, sentryTraceHeader, sentryBaggageHeader);
314-
}
302+
const client = getClient();
303+
304+
if (xhr.setRequestHeader && shouldAttachHeaders(sentryXhrData.url) && client) {
305+
const transaction = span && getRootSpan(span);
306+
307+
const { traceId, spanId, sampled, dsc } = {
308+
...isolationScope.getPropagationContext(),
309+
...scope.getPropagationContext(),
310+
};
311+
312+
const sentryTraceHeader = span ? spanToTraceHeader(span) : generateSentryTraceHeader(traceId, spanId, sampled);
313+
314+
const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader(
315+
dsc ||
316+
(transaction
317+
? getDynamicSamplingContextFromSpan(transaction)
318+
: getDynamicSamplingContextFromClient(traceId, client, scope)),
319+
);
320+
321+
setHeaderOnXhr(xhr, sentryTraceHeader, sentryBaggageHeader);
315322
}
316323

317324
return span;

packages/tracing-internal/src/common/fetch.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
getCurrentScope,
55
getDynamicSamplingContextFromClient,
66
getDynamicSamplingContextFromSpan,
7+
getIsolationScope,
78
getRootSpan,
89
hasTracingEnabled,
910
spanToTraceHeader,
@@ -137,16 +138,21 @@ export function addTracingHeadersToFetchRequest(
137138

138139
const transaction = span && getRootSpan(span);
139140

140-
const { traceId, sampled, dsc } = scope.getPropagationContext();
141+
const isolationScope = getIsolationScope();
141142

142-
const sentryTraceHeader = span ? spanToTraceHeader(span) : generateSentryTraceHeader(traceId, undefined, sampled);
143-
const dynamicSamplingContext = transaction
144-
? getDynamicSamplingContextFromSpan(transaction)
145-
: dsc
146-
? dsc
147-
: getDynamicSamplingContextFromClient(traceId, client, scope);
143+
const { traceId, spanId, sampled, dsc } = {
144+
...isolationScope.getPropagationContext(),
145+
...scope.getPropagationContext(),
146+
};
148147

149-
const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext);
148+
const sentryTraceHeader = span ? spanToTraceHeader(span) : generateSentryTraceHeader(traceId, spanId, sampled);
149+
150+
const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader(
151+
dsc ||
152+
(transaction
153+
? getDynamicSamplingContextFromSpan(transaction)
154+
: getDynamicSamplingContextFromClient(traceId, client, scope)),
155+
);
150156

151157
const headers =
152158
options.headers ||

packages/types/src/tracing.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,43 @@ import type { DynamicSamplingContext } from './envelope';
22

33
export type TracePropagationTargets = (string | RegExp)[];
44

5+
/**
6+
* `PropagationContext` represents the data from an incoming trace. It should be constructed from incoming trace data,
7+
* usually represented by `sentry-trace` and `baggage` HTTP headers.
8+
*
9+
* There is always a propagation context present in the SDK (or rather on Scopes), holding at least a `traceId`. This is
10+
* to ensure that there is always a trace we can attach events onto, even if performance monitoring is disabled. If
11+
* there was no incoming `traceId`, the `traceId` will be generated by the current SDK.
12+
*/
513
export interface PropagationContext {
14+
/**
15+
* Either represents the incoming `traceId` or the `traceId` generated by the current SDK, if there was no incoming trace.
16+
*/
617
traceId: string;
18+
/**
19+
* Represents the execution context of the current SDK. This acts as a fallback value to associate events with a
20+
* particular execution context when performance monitoring is disabled.
21+
*
22+
* The ID of a current span (if one exists) should have precedence over this value when propagating trace data.
23+
*/
724
spanId: string;
25+
/**
26+
* Represents the sampling decision of the incoming trace.
27+
*
28+
* The current SDK should not modify this value!
29+
*/
830
sampled?: boolean;
31+
/**
32+
* The `parentSpanId` denotes the ID of the incoming client span. If there is no `parentSpanId` on the propagation
33+
* context, it means that the the incoming trace didn't come from a span.
34+
*
35+
* The current SDK should not modify this value!
36+
*/
937
parentSpanId?: string;
38+
/**
39+
* An undefined dsc in the propagation context means that the current SDK invocation is the head of trace and still free to modify and set the DSC for outgoing requests.
40+
*
41+
* The current SDK should not modify this value!
42+
*/
1043
dsc?: DynamicSamplingContext;
1144
}

0 commit comments

Comments
 (0)