Skip to content

Commit 2efeed3

Browse files
mydeaclaude
andauthored
ref(opentelemetry): Remove enhanceDscWithOpenTelemetryRootSpanName (#22797)
Stacked on top of #22712. With the span name/source inference reworked in #22712, core's `getDynamicSamplingContext` now derives the DSC transaction name from the root span and applies the `url` source guard itself. That makes the OpenTelemetry-specific `createDsc` handler `enhanceDscWithOpenTelemetryRootSpanName` redundant — it was only there to re-derive the name/source via `parseSpanDescription` before core did so natively. This removes the util along with its call sites in `@sentry/node`, `@sentry/vercel-edge`, and the opentelemetry test helper, and drops it from the package's public exports. Since the enhancer is a public export of `@sentry/opentelemetry`, its removal is technically breaking for anyone importing it directly — which is why this targets the breaking-change branch #22712. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 8f24068 commit 2efeed3

13 files changed

Lines changed: 118 additions & 60 deletions

File tree

dev-packages/e2e-tests/test-applications/supabase-nextjs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"build": "next build",
88
"start": "next start",
99
"clean": "npx rimraf node_modules pnpm-lock.yaml .next",
10-
"start-local-supabase": "supabase stop --no-backup 2>/dev/null || true && supabase init --force --workdir . && supabase start -o env && supabase db reset",
10+
"start-local-supabase": "supabase stop --no-backup 2>/dev/null || true && supabase start -o env && supabase db reset",
1111
"test:prod": "TEST_ENV=production playwright test",
1212
"test:build": "pnpm install && pnpm start-local-supabase && pnpm build",
1313
"test:assert": "pnpm test:prod"

dev-packages/e2e-tests/test-applications/supabase-nextjs/supabase/config.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ inspector_port = 8083
288288
# secret_key = "env(SECRET_VALUE)"
289289

290290
[analytics]
291-
enabled = true
291+
enabled = false
292292
port = 54327
293293
# Configure one of the supported backends: `postgres`, `bigquery`.
294294
backend = "postgres"

dev-packages/node-integration-tests/suites/tracing/envelope-header/error-active-span-unsampled/test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ test('envelope header for error event during active unsampled span is correct',
1414
sample_rate: '0',
1515
sampled: 'false',
1616
sample_rand: expect.any(String),
17+
transaction: 'test span',
1718
},
1819
},
1920
})

packages/node/src/sdk/index.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,7 @@ import {
1515
requestDataIntegration,
1616
stackParserFromStackParserOptions,
1717
} from '@sentry/core';
18-
import {
19-
enhanceDscWithOpenTelemetryRootSpanName,
20-
setOpenTelemetryContextAsyncContextStrategy,
21-
setupEventContextTrace,
22-
} from '@sentry/opentelemetry';
18+
import { setOpenTelemetryContextAsyncContextStrategy, setupEventContextTrace } from '@sentry/opentelemetry';
2319
import { isMainThread, parentPort } from 'node:worker_threads';
2420
import { detectOrchestrionSetup } from '@sentry/server-utils/orchestrion';
2521
import { registerDiagnosticsChannelInjection } from '@sentry/server-utils/orchestrion/register';
@@ -206,7 +202,6 @@ function _init(
206202

207203
updateScopeFromEnvVariables();
208204

209-
enhanceDscWithOpenTelemetryRootSpanName(client);
210205
setupEventContextTrace(client);
211206

212207
// Ensure we flush events when vercel functions are ended

packages/opentelemetry/src/index.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
export { getScopesFromContext } from './utils/contextData';
22

3-
export { enhanceDscWithOpenTelemetryRootSpanName } from './utils/enhanceDscWithOpenTelemetryRootSpanName';
4-
53
export { getTraceContextForScope } from './trace';
64

75
export { setupEventContextTrace } from './setupEventContextTrace';

packages/opentelemetry/src/propagator.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,17 @@ import {
2121
shouldPropagateTraceForUrl,
2222
spanToJSON,
2323
} from '@sentry/core';
24-
import { SENTRY_BAGGAGE_HEADER, SENTRY_TRACE_HEADER, SENTRY_TRACE_STATE_URL } from './constants';
24+
import {
25+
SENTRY_BAGGAGE_HEADER,
26+
SENTRY_TRACE_HEADER,
27+
SENTRY_TRACE_STATE_DSC,
28+
SENTRY_TRACE_STATE_URL,
29+
} from './constants';
2530
import { DEBUG_BUILD } from './debug-build';
2631
import { getScopesFromContext, setScopesOnContext } from './utils/contextData';
2732
import { getSampledForPropagation, getSamplingDecision } from './utils/getSamplingDecision';
2833
import { makeTraceState } from './utils/makeTraceState';
34+
import { reconcileDscSampled } from './utils/reconcileDscSampled';
2935

3036
/**
3137
* Injects and extracts `sentry-trace` and `baggage` headers from carriers.
@@ -149,13 +155,22 @@ export function getInjectionData(
149155
// Instead, we use a virtual (generated) spanId for propagation
150156
if (span?.spanContext().isRemote) {
151157
const spanContext = span.spanContext();
152-
const dynamicSamplingContext = getDynamicSamplingContextFromSpan(span);
158+
const sampled = getSamplingDecision(spanContext);
159+
const dsc = getDynamicSamplingContextFromSpan(span);
160+
161+
// When the incoming trace froze its DSC on the trace state, `getDynamicSamplingContextFromSpan`
162+
// returns that DSC verbatim; per the propagation spec it is immutable, so we must not rewrite
163+
// `sampled` or strip `transaction` on it. We only reconcile the DSC that core freshly derives
164+
// from the (binary) span trace flags, which is the sole case that can misrepresent a deferred
165+
// decision as unsampled.
166+
const hasIncomingFrozenDsc = !!spanContext.traceState?.get(SENTRY_TRACE_STATE_DSC);
167+
const dynamicSamplingContext = hasIncomingFrozenDsc ? dsc : reconcileDscSampled(dsc, sampled);
153168

154169
return {
155170
dynamicSamplingContext,
156171
traceId: spanContext.traceId,
157172
spanId: undefined,
158-
sampled: getSamplingDecision(spanContext), // TODO: Do we need to change something here?
173+
sampled,
159174
};
160175
}
161176

packages/opentelemetry/src/trace.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@ import {
2626
spanToJSON,
2727
spanToTraceContext,
2828
} from '@sentry/core';
29+
import { SENTRY_TRACE_STATE_DSC } from './constants';
2930
import { continueTraceAsRemoteSpan } from './propagator';
3031
import type { OpenTelemetrySpanContext } from './types';
3132
import { getContextFromScope } from './utils/contextData';
3233
import { getSamplingDecision } from './utils/getSamplingDecision';
3334
import { makeTraceState } from './utils/makeTraceState';
35+
import { reconcileDscSampled } from './utils/reconcileDscSampled';
3436

3537
/**
3638
* Internal helper for starting spans and manual spans. See {@link startSpan} and {@link startSpanManual} for the public APIs.
@@ -257,7 +259,15 @@ function getContext(scope: Scope | undefined, forceTransaction: boolean | undefi
257259
// In this case, when we are forcing a transaction, we want to treat this like continuing an incoming trace
258260
// so we set the traceState according to the root span
259261
const rootSpan = getRootSpan(parentSpan);
260-
const dsc = getDynamicSamplingContextFromSpan(rootSpan);
262+
const rawDsc = getDynamicSamplingContextFromSpan(rootSpan);
263+
264+
// When the root carried a frozen incoming DSC on its trace state, `getDynamicSamplingContextFromSpan`
265+
// returns it verbatim and it is immutable per the propagation spec. Otherwise core freshly derived the
266+
// DSC from the root's (binary) trace flags, which cannot tell a deferred decision apart from a
267+
// definitive unsampled one — reconcile `sampled` against the authoritative OTel decision so a deferred
268+
// parent (e.g. a `startNewTrace` remote parent with `traceFlags: NONE`) does not bake in `sampled=false`.
269+
const hasIncomingFrozenDsc = !!rootSpan.spanContext().traceState?.get(SENTRY_TRACE_STATE_DSC);
270+
const dsc = hasIncomingFrozenDsc ? rawDsc : reconcileDscSampled(rawDsc, sampled);
261271

262272
const traceState = makeTraceState({
263273
dsc,

packages/opentelemetry/src/utils/enhanceDscWithOpenTelemetryRootSpanName.ts

Lines changed: 0 additions & 42 deletions
This file was deleted.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import type { DynamicSamplingContext } from '@sentry/core';
2+
3+
/**
4+
* Reconcile a freshly-derived DSC's `sampled` flag with the OTel sampling decision.
5+
*
6+
* Only applies to a DSC that core generated from the span's (binary) trace flags — never to a frozen
7+
* incoming DSC from the trace state, which the caller leaves untouched per the propagation spec.
8+
* Trace flags cannot tell a *deferred* decision (an incoming remote span whose decision lives in the
9+
* trace state) apart from a definitive *unsampled* one — both read as `traceFlags: NONE`.
10+
* `getSamplingDecision` resolves this via the OTel trace state, so we let it win here: drop `sampled`
11+
* when the decision is deferred (`undefined`), and — matching the OTel SDK, whose unsampled spans are
12+
* nameless non-recording spans — drop the transaction name when the trace is definitively unsampled.
13+
*/
14+
export function reconcileDscSampled(
15+
dsc: Partial<DynamicSamplingContext>,
16+
sampled: boolean | undefined,
17+
): Partial<DynamicSamplingContext> {
18+
const reconciled = { ...dsc };
19+
20+
if (sampled === undefined) {
21+
delete reconciled.sampled;
22+
} else {
23+
reconciled.sampled = String(sampled);
24+
if (sampled === false) {
25+
delete reconciled.transaction;
26+
}
27+
}
28+
29+
return reconciled;
30+
}

packages/opentelemetry/test/helpers/initOtel.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { DEBUG_BUILD } from '../../src/debug-build';
44
import { SentryPropagator } from '../../src/propagator';
55
import { getSentryResource } from '../../src/resource';
66
import { setupEventContextTrace } from '../../src/setupEventContextTrace';
7-
import { enhanceDscWithOpenTelemetryRootSpanName } from '../../src/utils/enhanceDscWithOpenTelemetryRootSpanName';
87
import type { TestClient } from './TestClient';
98
import { SentryTracerProvider } from '../../src/tracerProvider';
109

@@ -38,7 +37,6 @@ export function initOtel(): void {
3837
}
3938

4039
setupEventContextTrace(client);
41-
enhanceDscWithOpenTelemetryRootSpanName(client);
4240

4341
const provider = new SentryTracerProvider({ resource: getSentryResource('node') });
4442

0 commit comments

Comments
 (0)