mirror of
https://github.com/hanzoai/next.js.git
synced 2026-10-30 14:46:38 +00:00
> [!TIP] > Best reviewed with hide whitespace. ### Why? Cache Components prerenders can fail when an OpenTelemetry tracer is acquired before provider registration and later resolves through proxy tracer delegation: ``` Next.js encountered the unstable value `Math.random()` while prerendering ``` Current behavior: https://github.com/vercel/next.js/actions/runs/28518518762/job/84536475151?pr=95317#step:32:343 ### How? Patch proxy tracer delegation to apply the same Cache Components span instrumentation used for normal tracer lookup. Closes https://github.com/vercel/next.js/issues/94753 <!-- NEXT_JS_LLM_PR -->
222 lines
5.4 KiB
TypeScript
222 lines
5.4 KiB
TypeScript
import { type Span, type Tracer, trace, context } from '@opentelemetry/api'
|
|
import { Suspense } from 'react'
|
|
|
|
async function asyncWork() {
|
|
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
return 42
|
|
}
|
|
|
|
async function cachedAsyncWork() {
|
|
'use cache'
|
|
return asyncWork()
|
|
}
|
|
|
|
function withSpan(fn) {
|
|
return function () {
|
|
const tracer = trace.getTracer('tracer-manual-span')
|
|
const span = tracer.startSpan('span-manual-span')
|
|
const ctx = trace.setSpan(context.active(), span)
|
|
return context.with(ctx, fn)
|
|
}
|
|
}
|
|
|
|
function withActiveSpan(fn) {
|
|
return function () {
|
|
const tracer = trace.getTracer('tracer-active-span')
|
|
return tracer.startActiveSpan('span-active-span', fn)
|
|
}
|
|
}
|
|
|
|
const asyncWorkWithManualSpan = withSpan(asyncWork)
|
|
const asyncWorkWithActiveSpan = withActiveSpan(asyncWork)
|
|
const cachedAsyncWorkWithManualSpan = withSpan(cachedAsyncWork)
|
|
const cachedAsyncWorkWithActiveSpan = withActiveSpan(cachedAsyncWork)
|
|
|
|
export function CachedInnerTraceManualSpan() {
|
|
async function Inner() {
|
|
const result = await cachedAsyncWorkWithManualSpan()
|
|
return <p className="result">{result}</p>
|
|
}
|
|
return (
|
|
<section id="t1">
|
|
<h2>(Manual Span) Cached Async Work</h2>
|
|
<Suspense fallback={<Loading />}>
|
|
<Inner />
|
|
</Suspense>
|
|
</section>
|
|
)
|
|
}
|
|
|
|
export function InnerTraceManualSpan() {
|
|
async function Inner() {
|
|
const result = await asyncWorkWithManualSpan()
|
|
return <p className="result">{result}</p>
|
|
}
|
|
return (
|
|
<section id="t2">
|
|
<h2>(Manual Span) Async Work</h2>
|
|
<Suspense fallback={<Loading />}>
|
|
<Inner />
|
|
</Suspense>
|
|
</section>
|
|
)
|
|
}
|
|
|
|
export const CachedTracedComponentManualSpan = withSpan(async function () {
|
|
async function Inner() {
|
|
const result = await cachedAsyncWork()
|
|
return <Result>{result}</Result>
|
|
}
|
|
return (
|
|
<section id="t3">
|
|
<h2>(Manual Span) Inner Traced "use cache" Function</h2>
|
|
<Suspense fallback={<Loading />}>
|
|
<Inner />
|
|
</Suspense>
|
|
</section>
|
|
)
|
|
})
|
|
|
|
export const TracedComponentManualSpan = withSpan(async function () {
|
|
async function Inner() {
|
|
const result = await asyncWork()
|
|
return <Result>{result}</Result>
|
|
}
|
|
return (
|
|
<section id="t4">
|
|
<h2>(Manual Span) Inner Traced Function</h2>
|
|
<Suspense fallback={<Loading />}>
|
|
<Inner />
|
|
</Suspense>
|
|
</section>
|
|
)
|
|
})
|
|
|
|
export function CachedInnerTraceActiveSpan() {
|
|
async function Inner() {
|
|
const result = await cachedAsyncWorkWithActiveSpan()
|
|
return <Result>{result}</Result>
|
|
}
|
|
return (
|
|
<section id="t5">
|
|
<h2>(Active Span) Cached Async Work</h2>
|
|
<Suspense fallback={<Loading />}>
|
|
<Inner />
|
|
</Suspense>
|
|
</section>
|
|
)
|
|
}
|
|
|
|
export function InnerTraceActiveSpan() {
|
|
async function Inner() {
|
|
const result = await asyncWorkWithActiveSpan()
|
|
return <Result>{result}</Result>
|
|
}
|
|
return (
|
|
<section id="t6">
|
|
<h2>(Active Span) Async Work</h2>
|
|
<Suspense fallback={<Loading />}>
|
|
<Inner />
|
|
</Suspense>
|
|
</section>
|
|
)
|
|
}
|
|
|
|
export const CachedTracedComponentActiveSpan = withActiveSpan(async function (
|
|
span: Span
|
|
) {
|
|
async function Inner() {
|
|
const result = await cachedAsyncWork()
|
|
return <Result>{result}</Result>
|
|
}
|
|
|
|
return (
|
|
<section id="t7">
|
|
<h2>(Active Span) Inner Traced "use cache" Function</h2>
|
|
<div>
|
|
<p>
|
|
Span Representative{' '}
|
|
<span className="span" suppressHydrationWarning>
|
|
{parseInt(span.spanContext().spanId.slice(10), 16)}
|
|
</span>
|
|
</p>
|
|
<Suspense fallback={<Loading />}>
|
|
<Inner />
|
|
</Suspense>
|
|
</div>
|
|
</section>
|
|
)
|
|
})
|
|
|
|
export const TracedComponentActiveSpan = withActiveSpan(async function (
|
|
span: Span
|
|
) {
|
|
async function Inner() {
|
|
const result = await asyncWork()
|
|
return <Result>{result}</Result>
|
|
}
|
|
return (
|
|
<section id="t8">
|
|
<h2>(Active Span) Inner Traced Function</h2>
|
|
<div>
|
|
<p>
|
|
Span Representative{' '}
|
|
<span className="span" suppressHydrationWarning>
|
|
{parseInt(span.spanContext().spanId.slice(10), 16)}
|
|
</span>
|
|
</p>
|
|
<Suspense fallback={<Loading />}>
|
|
<Inner />
|
|
</Suspense>
|
|
</div>
|
|
</section>
|
|
)
|
|
})
|
|
|
|
type TestGlobal = typeof globalThis & {
|
|
__nextTestEarlyTracer?: Tracer
|
|
}
|
|
|
|
export async function TracedComponentEarlyTracerSpan() {
|
|
const tracer = (globalThis as TestGlobal).__nextTestEarlyTracer
|
|
if (!tracer) {
|
|
throw new Error(
|
|
'Expected instrumentation to register the early tracer before rendering'
|
|
)
|
|
}
|
|
|
|
const span = tracer.startSpan('span-early-manual-span')
|
|
const ctx = trace.setSpan(context.active(), span)
|
|
|
|
return context.with(ctx, async () => {
|
|
async function Inner() {
|
|
const result = await asyncWork()
|
|
return <Result>{result}</Result>
|
|
}
|
|
return (
|
|
<section id="t9">
|
|
<h2>(Manual Span) Tracer acquired before provider registration</h2>
|
|
<div>
|
|
<p>
|
|
Span Representative{' '}
|
|
<span className="span" suppressHydrationWarning>
|
|
{parseInt(span.spanContext().spanId.slice(10), 16)}
|
|
</span>
|
|
</p>
|
|
<Suspense fallback={<Loading />}>
|
|
<Inner />
|
|
</Suspense>
|
|
</div>
|
|
</section>
|
|
)
|
|
})
|
|
}
|
|
|
|
function Loading() {
|
|
return <span className="fallback">loading...</span>
|
|
}
|
|
|
|
function Result({ children }: { children: React.ReactNode }) {
|
|
return <p className="result">{children}</p>
|
|
}
|