Skip to content

Commit e2883df

Browse files
authored
ref(node): Avoid using propagator for node-fetch TWP instrumentation (#14509)
Instead, use the new and improved `getTraceData` directly, which avoids URL issues etc. and just gets the trace data. This also deprecates `generateSpanContextForPropagationContext` and inlines it to the one place we still use this for, I'll probably adapt this in a follow up PR.
1 parent 0ac1e10 commit e2883df

File tree

5 files changed

+71
-49
lines changed

5 files changed

+71
-49
lines changed

docs/migration/draft-v9-migration-guide.md

+4
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@
9393

9494
- Deprecated `autoInstrumentRemix: false`. The next major version will default to behaving as if this option were `true` and the option itself will be removed.
9595

96+
## `@sentry/opentelemetry`
97+
98+
- Deprecated `generateSpanContextForPropagationContext` in favor of doing this manually - we do not need this export anymore.
99+
96100
## Server-side SDKs (`@sentry/node` and all dependents)
97101

98102
- Deprecated `processThreadBreadcrumbIntegration` in favor of `childProcessIntegration`. Functionally they are the same.

packages/node/src/integrations/node-fetch.ts

+9-29
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,9 @@
1-
import { context, propagation, trace } from '@opentelemetry/api';
21
import type { UndiciRequest, UndiciResponse } from '@opentelemetry/instrumentation-undici';
32
import { UndiciInstrumentation } from '@opentelemetry/instrumentation-undici';
4-
import {
5-
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
6-
addBreadcrumb,
7-
defineIntegration,
8-
getCurrentScope,
9-
hasTracingEnabled,
10-
} from '@sentry/core';
3+
import { LRUMap, getClient, getTraceData } from '@sentry/core';
4+
import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, addBreadcrumb, defineIntegration, hasTracingEnabled } from '@sentry/core';
115
import { getBreadcrumbLogLevelFromHttpStatusCode, getSanitizedUrlString, parseUrl } from '@sentry/core';
12-
import {
13-
addOpenTelemetryInstrumentation,
14-
generateSpanContextForPropagationContext,
15-
getPropagationContextFromSpan,
16-
} from '@sentry/opentelemetry';
6+
import { addOpenTelemetryInstrumentation, shouldPropagateTraceForUrl } from '@sentry/opentelemetry';
177
import type { IntegrationFn, SanitizedRequestData } from '@sentry/types';
188

199
interface NodeFetchOptions {
@@ -37,6 +27,8 @@ const _nativeNodeFetchIntegration = ((options: NodeFetchOptions = {}) => {
3727
return {
3828
name: 'NodeFetch',
3929
setupOnce() {
30+
const propagationDecisionMap = new LRUMap<string, boolean>(100);
31+
4032
const instrumentation = new UndiciInstrumentation({
4133
requireParentforSpans: false,
4234
ignoreRequestHook: request => {
@@ -50,22 +42,10 @@ const _nativeNodeFetchIntegration = ((options: NodeFetchOptions = {}) => {
5042
// If tracing is disabled, we still want to propagate traces
5143
// So we do that manually here, matching what the instrumentation does otherwise
5244
if (!hasTracingEnabled()) {
53-
const ctx = context.active();
54-
const addedHeaders: Record<string, string> = {};
55-
56-
// We generate a virtual span context from the active one,
57-
// Where we attach the URL to the trace state, so the propagator can pick it up
58-
const activeSpan = trace.getSpan(ctx);
59-
const propagationContext = activeSpan
60-
? getPropagationContextFromSpan(activeSpan)
61-
: getCurrentScope().getPropagationContext();
62-
63-
const spanContext = generateSpanContextForPropagationContext(propagationContext);
64-
// We know that in practice we'll _always_ haven a traceState here
65-
spanContext.traceState = spanContext.traceState?.set('sentry.url', url);
66-
const ctxWithUrlTraceState = trace.setSpanContext(ctx, spanContext);
67-
68-
propagation.inject(ctxWithUrlTraceState, addedHeaders);
45+
const tracePropagationTargets = getClient()?.getOptions().tracePropagationTargets;
46+
const addedHeaders = shouldPropagateTraceForUrl(url, tracePropagationTargets, propagationDecisionMap)
47+
? getTraceData()
48+
: {};
6949

7050
const requestHeaders = request.headers;
7151
if (Array.isArray(requestHeaders)) {

packages/opentelemetry/src/index.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export { getDynamicSamplingContextFromSpan } from '@sentry/core';
2424
export { isSentryRequestSpan } from './utils/isSentryRequest';
2525

2626
export { enhanceDscWithOpenTelemetryRootSpanName } from './utils/enhanceDscWithOpenTelemetryRootSpanName';
27+
// eslint-disable-next-line deprecation/deprecation
2728
export { generateSpanContextForPropagationContext } from './utils/generateSpanContextForPropagationContext';
2829

2930
export { getActiveSpan } from './utils/getActiveSpan';
@@ -44,7 +45,11 @@ export { setupEventContextTrace } from './setupEventContextTrace';
4445

4546
export { setOpenTelemetryContextAsyncContextStrategy } from './asyncContextStrategy';
4647
export { wrapContextManagerClass } from './contextManager';
47-
export { SentryPropagator, getPropagationContextFromSpan } from './propagator';
48+
export {
49+
SentryPropagator,
50+
getPropagationContextFromSpan,
51+
shouldPropagateTraceForUrl,
52+
} from './propagator';
4853
export { SentrySpanProcessor } from './spanProcessor';
4954
export {
5055
SentrySampler,

packages/opentelemetry/src/propagator.ts

+50-19
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import type { Baggage, Context, Span, TextMapGetter, TextMapSetter } from '@opentelemetry/api';
1+
import type { Baggage, Context, Span, SpanContext, TextMapGetter, TextMapSetter } from '@opentelemetry/api';
2+
import { TraceFlags } from '@opentelemetry/api';
23
import { INVALID_TRACEID } from '@opentelemetry/api';
34
import { context } from '@opentelemetry/api';
45
import { propagation, trace } from '@opentelemetry/api';
@@ -30,8 +31,8 @@ import {
3031
} from './constants';
3132
import { DEBUG_BUILD } from './debug-build';
3233
import { getScopesFromContext, setScopesOnContext } from './utils/contextData';
33-
import { generateSpanContextForPropagationContext } from './utils/generateSpanContextForPropagationContext';
3434
import { getSamplingDecision } from './utils/getSamplingDecision';
35+
import { makeTraceState } from './utils/makeTraceState';
3536
import { setIsSetup } from './utils/setupCheck';
3637

3738
/** Get the Sentry propagation context from a span context. */
@@ -88,11 +89,7 @@ export class SentryPropagator extends W3CBaggagePropagator {
8889
const url = activeSpan && getCurrentURL(activeSpan);
8990

9091
const tracePropagationTargets = getClient()?.getOptions()?.tracePropagationTargets;
91-
if (
92-
typeof url === 'string' &&
93-
tracePropagationTargets &&
94-
!this._shouldInjectTraceData(tracePropagationTargets, url)
95-
) {
92+
if (!shouldPropagateTraceForUrl(url, tracePropagationTargets, this._urlMatchesTargetsMap)) {
9693
DEBUG_BUILD &&
9794
logger.log(
9895
'[Tracing] Not injecting trace data for url because it does not match tracePropagationTargets:',
@@ -168,22 +165,36 @@ export class SentryPropagator extends W3CBaggagePropagator {
168165
public fields(): string[] {
169166
return [SENTRY_TRACE_HEADER, SENTRY_BAGGAGE_HEADER];
170167
}
168+
}
171169

172-
/** If we want to inject trace data for a given URL. */
173-
private _shouldInjectTraceData(tracePropagationTargets: Options['tracePropagationTargets'], url: string): boolean {
174-
if (tracePropagationTargets === undefined) {
175-
return true;
176-
}
170+
const NOT_PROPAGATED_MESSAGE =
171+
'[Tracing] Not injecting trace data for url because it does not match tracePropagationTargets:';
177172

178-
const cachedDecision = this._urlMatchesTargetsMap.get(url);
179-
if (cachedDecision !== undefined) {
180-
return cachedDecision;
181-
}
173+
/**
174+
* Check if a given URL should be propagated to or not.
175+
* If no url is defined, or no trace propagation targets are defined, this will always return `true`.
176+
* You can also optionally provide a decision map, to cache decisions and avoid repeated regex lookups.
177+
*/
178+
export function shouldPropagateTraceForUrl(
179+
url: string | undefined,
180+
tracePropagationTargets: Options['tracePropagationTargets'],
181+
decisionMap?: LRUMap<string, boolean>,
182+
): boolean {
183+
if (typeof url !== 'string' || !tracePropagationTargets) {
184+
return true;
185+
}
182186

183-
const decision = stringMatchesSomePattern(url, tracePropagationTargets);
184-
this._urlMatchesTargetsMap.set(url, decision);
185-
return decision;
187+
const cachedDecision = decisionMap?.get(url);
188+
if (cachedDecision !== undefined) {
189+
DEBUG_BUILD && !cachedDecision && logger.log(NOT_PROPAGATED_MESSAGE, url);
190+
return cachedDecision;
186191
}
192+
193+
const decision = stringMatchesSomePattern(url, tracePropagationTargets);
194+
decisionMap?.set(url, decision);
195+
196+
DEBUG_BUILD && !decision && logger.log(NOT_PROPAGATED_MESSAGE, url);
197+
return decision;
187198
}
188199

189200
/**
@@ -287,3 +298,23 @@ function getCurrentURL(span: Span): string | undefined {
287298

288299
return undefined;
289300
}
301+
302+
// TODO: Adjust this behavior to avoid invalid spans
303+
function generateSpanContextForPropagationContext(propagationContext: PropagationContext): SpanContext {
304+
// We store the DSC as OTEL trace state on the span context
305+
const traceState = makeTraceState({
306+
parentSpanId: propagationContext.parentSpanId,
307+
dsc: propagationContext.dsc,
308+
sampled: propagationContext.sampled,
309+
});
310+
311+
const spanContext: SpanContext = {
312+
traceId: propagationContext.traceId,
313+
spanId: propagationContext.parentSpanId || '',
314+
isRemote: true,
315+
traceFlags: propagationContext.sampled ? TraceFlags.SAMPLED : TraceFlags.NONE,
316+
traceState,
317+
};
318+
319+
return spanContext;
320+
}

packages/opentelemetry/src/utils/generateSpanContextForPropagationContext.ts

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { makeTraceState } from './makeTraceState';
66
/**
77
* Generates a SpanContext that represents a PropagationContext.
88
* This can be set on a `context` to make this a (virtual) active span.
9+
*
10+
* @deprecated This function is deprecated and will be removed in the next major version.
911
*/
1012
export function generateSpanContextForPropagationContext(propagationContext: PropagationContext): SpanContext {
1113
// We store the DSC as OTEL trace state on the span context

0 commit comments

Comments
 (0)