Skip to content

Commit cf27bae

Browse files
chargomeclaude
andcommitted
fix(nextjs): Also recognize a prerender span that is activated by its caller
Next.js starts its spans through `startActiveSpan`, which the tracer wrapper marks on the context. A span started through `startSpan` and set on a context by the caller takes a different path, so the wrapper remembers those spans and the propagator checks the span on the context too. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 parent feab86e commit cf27bae

2 files changed

Lines changed: 32 additions & 4 deletions

File tree

‎packages/nextjs/src/server/nextSentryPropagator.ts‎

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import type { Context, SpanOptions, TextMapSetter, Tracer, TracerProvider } from '@opentelemetry/api';
2-
import { context, createContextKey, propagation } from '@opentelemetry/api';
1+
import type { Context, Span, SpanOptions, TextMapSetter, Tracer, TracerProvider } from '@opentelemetry/api';
2+
import { context, createContextKey, propagation, trace } from '@opentelemetry/api';
33
import { SentryPropagator } from '@sentry/opentelemetry';
44
import { ATTR_NEXT_SPAN_NAME, ATTR_NEXT_SPAN_TYPE } from '../common/nextSpanAttributes';
55

@@ -8,6 +8,9 @@ const PRERENDER_ROUTE_SPAN_NAME_PREFIX = 'prerender route';
88

99
const PRERENDER_CONTEXT_KEY = createContextKey('sentry.nextjs.prerender');
1010

11+
// Prerender spans started with `startSpan`, which the caller activates on a context of its own.
12+
const prerenderSpans = new WeakSet<Span>();
13+
1114
/**
1215
* Whether these are the start arguments of the span Next.js wraps a prerender in. Next.js computes the
1316
* `sentry-trace` and `baggage` meta tags of a document inside that span, and a prerender produces a
@@ -26,7 +29,11 @@ export function isPrerenderSpanStart(name: string, options: SpanOptions | undefi
2629

2730
/** Whether the context belongs to a prerender started through a tracer of a wrapped provider. */
2831
export function isPrerenderContext(ctx: Context): boolean {
29-
return ctx.getValue(PRERENDER_CONTEXT_KEY) === true;
32+
if (ctx.getValue(PRERENDER_CONTEXT_KEY) === true) {
33+
return true;
34+
}
35+
const span = trace.getSpan(ctx);
36+
return !!span && prerenderSpans.has(span);
3037
}
3138

3239
/**
@@ -37,7 +44,13 @@ export function isPrerenderContext(ctx: Context): boolean {
3744
*/
3845
function wrapTracer(tracer: Tracer): Tracer {
3946
return {
40-
startSpan: (name, options, ctx) => tracer.startSpan(name, options, ctx),
47+
startSpan: (name, options, ctx) => {
48+
const span = tracer.startSpan(name, options, ctx);
49+
if (isPrerenderSpanStart(name, options)) {
50+
prerenderSpans.add(span);
51+
}
52+
return span;
53+
},
4154
startActiveSpan: (name: string, ...args: unknown[]) => {
4255
const startActiveSpan = tracer.startActiveSpan.bind(tracer) as (name: string, ...args: unknown[]) => unknown;
4356
const options = typeof args[0] === 'function' ? undefined : (args[0] as SpanOptions | undefined);

‎packages/nextjs/test/server/nextSentryPropagator.test.ts‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,21 @@ describe('NextSentryPropagator', () => {
114114
});
115115
});
116116

117+
it('recognizes a prerender span that was started inactive and activated by the caller', () => {
118+
const span = trace.getTracer('next.js').startSpan('prerender route (app) /[id]', PRERENDER_SPAN_OPTIONS);
119+
context.with(trace.setSpan(context.active(), span), () => {
120+
expect(isPrerenderContext(context.active())).toBe(true);
121+
expect(inject()).toEqual({});
122+
});
123+
span.end();
124+
125+
const renderSpan = trace.getTracer('next.js').startSpan('render route (app) /blocking', RENDER_SPAN_OPTIONS);
126+
context.with(trace.setSpan(context.active(), renderSpan), () => {
127+
expect(inject()['sentry-trace']).toBeDefined();
128+
});
129+
renderSpan.end();
130+
});
131+
117132
it('marks an explicitly passed context', () => {
118133
trace
119134
.getTracer('next.js')

0 commit comments

Comments
 (0)