Skip to content

Commit 65531f3

Browse files
authored
fix(core): Use consistent continueTrace implementation in core (#14813)
We have a different implementation of `continueTrace` for OTEL/Node. Until now we relied on actually using the import from `@sentry/node` vs `@sentry/core` to ensure this. However, this is a footgun, and actually lead to a problem in NextJS because we used the core variant there. Also, it is simply not isomorphic. So to fix this, this PR puts `continueTrace` on the ACS so we can consistently use the core variant in all environments. Fixes #14787
1 parent b008139 commit 65531f3

File tree

16 files changed

+50
-52
lines changed

16 files changed

+50
-52
lines changed

packages/astro/src/index.types.ts

-2
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ export declare function flush(timeout?: number | undefined): PromiseLike<boolean
2626

2727
// eslint-disable-next-line deprecation/deprecation
2828
export declare const getCurrentHub: typeof clientSdk.getCurrentHub;
29-
export declare const getClient: typeof clientSdk.getClient;
30-
export declare const continueTrace: typeof clientSdk.continueTrace;
3129

3230
export declare const Span: clientSdk.Span;
3331

packages/core/src/asyncContext/types.ts

+8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Scope } from '../scope';
22
import type { getTraceData } from '../utils/traceData';
33
import type {
4+
continueTrace,
45
startInactiveSpan,
56
startSpan,
67
startSpanManual,
@@ -68,4 +69,11 @@ export interface AsyncContextStrategy {
6869

6970
/** Get trace data as serialized string values for propagation via `sentry-trace` and `baggage`. */
7071
getTraceData?: typeof getTraceData;
72+
73+
/**
74+
* Continue a trace from `sentry-trace` and `baggage` values.
75+
* These values can be obtained from incoming request headers, or in the browser from `<meta name="sentry-trace">`
76+
* and `<meta name="baggage">` HTML tags.
77+
*/
78+
continueTrace?: typeof continueTrace;
7179
}

packages/core/src/tracing/trace.ts

+9-4
Original file line numberDiff line numberDiff line change
@@ -192,15 +192,20 @@ export function startInactiveSpan(options: StartSpanOptions): Span {
192192
* be attached to the incoming trace.
193193
*/
194194
export const continueTrace = <V>(
195-
{
196-
sentryTrace,
197-
baggage,
198-
}: {
195+
options: {
199196
sentryTrace: Parameters<typeof propagationContextFromHeaders>[0];
200197
baggage: Parameters<typeof propagationContextFromHeaders>[1];
201198
},
202199
callback: () => V,
203200
): V => {
201+
const carrier = getMainCarrier();
202+
const acs = getAsyncContextStrategy(carrier);
203+
if (acs.continueTrace) {
204+
return acs.continueTrace(options, callback);
205+
}
206+
207+
const { sentryTrace, baggage } = options;
208+
204209
return withScope(scope => {
205210
const propagationContext = propagationContextFromHeaders(sentryTrace, baggage);
206211
scope.setPropagationContext(propagationContext);

packages/nextjs/src/common/pages-router-instrumentation/wrapApiHandlerWithSentry.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
44
captureException,
55
continueTrace,
6+
getActiveSpan,
67
httpRequestToRequestData,
78
isString,
89
logger,
@@ -59,7 +60,13 @@ export function wrapApiHandlerWithSentry(apiHandler: NextApiHandler, parameteriz
5960
req.__withSentry_applied__ = true;
6061

6162
return withIsolationScope(isolationScope => {
62-
return continueTrace(
63+
// Normally, there is an active span here (from Next.js OTEL) and we just use that as parent
64+
// Else, we manually continueTrace from the incoming headers
65+
const continueTraceIfNoActiveSpan = getActiveSpan()
66+
? <T>(_opts: unknown, callback: () => T) => callback()
67+
: continueTrace;
68+
69+
return continueTraceIfNoActiveSpan(
6370
{
6471
sentryTrace:
6572
req.headers && isString(req.headers['sentry-trace']) ? req.headers['sentry-trace'] : undefined,

packages/nextjs/src/common/withServerActionInstrumentation.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { RequestEventData } from '@sentry/core';
2+
import { getActiveSpan } from '@sentry/core';
23
import {
34
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
45
SPAN_STATUS_ERROR,
@@ -95,7 +96,13 @@ async function withServerActionInstrumentationImplementation<A extends (...args:
9596
} satisfies RequestEventData,
9697
});
9798

98-
return continueTrace(
99+
// Normally, there is an active span here (from Next.js OTEL) and we just use that as parent
100+
// Else, we manually continueTrace from the incoming headers
101+
const continueTraceIfNoActiveSpan = getActiveSpan()
102+
? <T>(_opts: unknown, callback: () => T) => callback()
103+
: continueTrace;
104+
105+
return continueTraceIfNoActiveSpan(
99106
{
100107
sentryTrace: sentryTraceHeader,
101108
baggage: baggageHeader,

packages/nextjs/src/index.types.ts

-4
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,6 @@ export declare function init(
1919
options: Options | clientSdk.BrowserOptions | serverSdk.NodeOptions | edgeSdk.EdgeOptions,
2020
): Client | undefined;
2121

22-
export declare const getClient: typeof clientSdk.getClient;
23-
export declare const getRootSpan: typeof serverSdk.getRootSpan;
24-
export declare const continueTrace: typeof clientSdk.continueTrace;
25-
2622
export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsIntegration;
2723
export declare const contextLinesIntegration: typeof clientSdk.contextLinesIntegration;
2824

packages/node/src/index.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,6 @@ export type { NodeOptions } from './types';
5858
export { addRequestDataToEvent, DEFAULT_USER_INCLUDES, extractRequestData } from '@sentry/core';
5959

6060
export {
61-
// These are custom variants that need to be used instead of the core one
62-
// As they have slightly different implementations
63-
continueTrace,
6461
// This needs exporting so the NodeClient can be used without calling init
6562
setOpenTelemetryContextAsyncContextStrategy as setNodeAsyncContextStrategy,
6663
} from '@sentry/opentelemetry';
@@ -105,6 +102,7 @@ export {
105102
getIsolationScope,
106103
getTraceData,
107104
getTraceMetaTags,
105+
continueTrace,
108106
withScope,
109107
withIsolationScope,
110108
captureException,

packages/nuxt/src/index.types.ts

-1
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,3 @@ export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsInteg
1414
export declare const contextLinesIntegration: typeof clientSdk.contextLinesIntegration;
1515
export declare const getDefaultIntegrations: (options: Options) => Integration[];
1616
export declare const defaultStackParser: StackParser;
17-
export declare const continueTrace: typeof clientSdk.continueTrace;

packages/opentelemetry/src/asyncContextStrategy.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY,
77
SENTRY_FORK_SET_SCOPE_CONTEXT_KEY,
88
} from './constants';
9-
import { startInactiveSpan, startSpan, startSpanManual, withActiveSpan } from './trace';
9+
import { continueTrace, startInactiveSpan, startSpan, startSpanManual, withActiveSpan } from './trace';
1010
import type { CurrentScopes } from './types';
1111
import { getScopesFromContext } from './utils/contextData';
1212
import { getActiveSpan } from './utils/getActiveSpan';
@@ -103,6 +103,7 @@ export function setOpenTelemetryContextAsyncContextStrategy(): void {
103103
getActiveSpan,
104104
suppressTracing,
105105
getTraceData,
106+
continueTrace,
106107
// The types here don't fully align, because our own `Span` type is narrower
107108
// than the OTEL one - but this is OK for here, as we now we'll only have OTEL spans passed around
108109
withActiveSpan: withActiveSpan as typeof defaultWithActiveSpan,

packages/opentelemetry/src/trace.ts

+9-5
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
import type { Context, Span, SpanContext, SpanOptions, Tracer } from '@opentelemetry/api';
22
import { SpanStatusCode, TraceFlags, context, trace } from '@opentelemetry/api';
33
import { suppressTracing } from '@opentelemetry/core';
4-
import type { Client, DynamicSamplingContext, Scope, Span as SentrySpan, TraceContext } from '@sentry/core';
4+
import type {
5+
Client,
6+
DynamicSamplingContext,
7+
Scope,
8+
Span as SentrySpan,
9+
TraceContext,
10+
continueTrace as baseContinueTrace,
11+
} from '@sentry/core';
512
import {
613
SDK_VERSION,
714
SEMANTIC_ATTRIBUTE_SENTRY_OP,
8-
continueTrace as baseContinueTrace,
915
getClient,
1016
getCurrentScope,
1117
getDynamicSamplingContextFromScope,
@@ -247,9 +253,7 @@ function getContextForScope(scope?: Scope): Context {
247253
* It propagates the trace as a remote span, in addition to setting it on the propagation context.
248254
*/
249255
export function continueTrace<T>(options: Parameters<typeof baseContinueTrace>[0], callback: () => T): T {
250-
return baseContinueTrace(options, () => {
251-
return continueTraceAsRemoteSpan(context.active(), options, callback);
252-
});
256+
return continueTraceAsRemoteSpan(context.active(), options, callback);
253257
}
254258

255259
/**

packages/opentelemetry/test/trace.test.ts

+3-19
Original file line numberDiff line numberDiff line change
@@ -1576,11 +1576,8 @@ describe('continueTrace', () => {
15761576
);
15771577

15781578
expect(scope.getPropagationContext()).toEqual({
1579-
dsc: {}, // DSC should be an empty object (frozen), because there was an incoming trace
1580-
sampled: false,
1581-
parentSpanId: '1121201211212012',
15821579
spanId: expect.any(String),
1583-
traceId: '12312012123120121231201212312012',
1580+
traceId: expect.any(String),
15841581
});
15851582

15861583
expect(scope.getScopeData().sdkProcessingMetadata).toEqual({});
@@ -1609,14 +1606,8 @@ describe('continueTrace', () => {
16091606
);
16101607

16111608
expect(scope.getPropagationContext()).toEqual({
1612-
dsc: {
1613-
environment: 'production',
1614-
version: '1.0',
1615-
},
1616-
sampled: true,
1617-
parentSpanId: '1121201211212012',
16181609
spanId: expect.any(String),
1619-
traceId: '12312012123120121231201212312012',
1610+
traceId: expect.any(String),
16201611
});
16211612

16221613
expect(scope.getScopeData().sdkProcessingMetadata).toEqual({});
@@ -1645,16 +1636,9 @@ describe('continueTrace', () => {
16451636
);
16461637

16471638
expect(scope.getPropagationContext()).toEqual({
1648-
dsc: {
1649-
environment: 'production',
1650-
version: '1.0',
1651-
},
1652-
sampled: true,
1653-
parentSpanId: '1121201211212012',
16541639
spanId: expect.any(String),
1655-
traceId: '12312012123120121231201212312012',
1640+
traceId: expect.any(String),
16561641
});
1657-
16581642
expect(scope.getScopeData().sdkProcessingMetadata).toEqual({});
16591643
});
16601644

packages/remix/src/index.types.ts

-2
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ declare const runtime: 'client' | 'server';
3232

3333
// eslint-disable-next-line deprecation/deprecation
3434
export declare const getCurrentHub: typeof clientSdk.getCurrentHub;
35-
export declare const getClient: typeof clientSdk.getClient;
36-
export declare const continueTrace: typeof clientSdk.continueTrace;
3735

3836
export const close = runtime === 'client' ? clientSdk.close : serverSdk.close;
3937
export const flush = runtime === 'client' ? clientSdk.flush : serverSdk.flush;

packages/remix/src/utils/instrumentServer.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
SEMANTIC_ATTRIBUTE_SENTRY_OP,
55
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
66
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
7+
continueTrace,
78
fill,
89
getActiveSpan,
910
getClient,
@@ -19,7 +20,6 @@ import {
1920
winterCGRequestToRequestData,
2021
withIsolationScope,
2122
} from '@sentry/core';
22-
import { continueTrace } from '@sentry/opentelemetry';
2323
import { DEBUG_BUILD } from './debug-build';
2424
import { captureRemixServerException, errorHandleDataFunction, errorHandleDocumentRequestFunction } from './errors';
2525
import { getFutureFlagsServer, getRemixVersionFromBuild } from './futureFlags';

packages/solidstart/src/index.types.ts

-4
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,6 @@ export declare const contextLinesIntegration: typeof clientSdk.contextLinesInteg
1919
export declare const getDefaultIntegrations: (options: Options) => Integration[];
2020
export declare const defaultStackParser: StackParser;
2121

22-
export declare const getClient: typeof clientSdk.getClient;
23-
2422
export declare function close(timeout?: number | undefined): PromiseLike<boolean>;
2523
export declare function flush(timeout?: number | undefined): PromiseLike<boolean>;
2624
export declare function lastEventId(): string | undefined;
27-
28-
export declare const continueTrace: typeof clientSdk.continueTrace;

packages/sveltekit/src/index.types.ts

-3
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,11 @@ export declare const contextLinesIntegration: typeof clientSdk.contextLinesInteg
4242
export declare const getDefaultIntegrations: (options: Options) => Integration[];
4343
export declare const defaultStackParser: StackParser;
4444

45-
export declare const getClient: typeof clientSdk.getClient;
4645
// eslint-disable-next-line deprecation/deprecation
4746
export declare const getCurrentHub: typeof clientSdk.getCurrentHub;
4847

4948
export declare function close(timeout?: number | undefined): PromiseLike<boolean>;
5049
export declare function flush(timeout?: number | undefined): PromiseLike<boolean>;
5150
export declare function lastEventId(): string | undefined;
5251

53-
export declare const continueTrace: typeof clientSdk.continueTrace;
54-
5552
export declare function trackComponent(options: clientSdk.TrackingOptions): ReturnType<typeof clientSdk.trackComponent>;

packages/sveltekit/src/server/handle.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { Span } from '@sentry/core';
22
import {
33
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
44
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
5+
continueTrace,
56
getActiveSpan,
67
getCurrentScope,
78
getDefaultIsolationScope,
@@ -13,7 +14,6 @@ import {
1314
winterCGRequestToRequestData,
1415
withIsolationScope,
1516
} from '@sentry/core';
16-
import { continueTrace } from '@sentry/node';
1717
import type { Handle, ResolveOptions } from '@sveltejs/kit';
1818

1919
import { DEBUG_BUILD } from '../common/debug-build';

0 commit comments

Comments
 (0)