mirror of
https://github.com/hanzoai/next.js.git
synced 2026-10-30 14:46:38 +00:00
When creating spans with OTel random values must be created to follow the OTel spec and is expected in practical implementations. While it is a loose concession Next.js will allow spans to be created while prerendering that have ids generated with random values. and by reading the current time from the system clock. This is pragmatic but potentially risky because if you are not careful and for instance wrap a "use cache" function in a span it is possible you will accidentally pass the span into the cache function causing it to miss on every invocation due to the random nature of one of it's arguments. To defend against this we do check whether `startActiveSpan` is being passed a Cache Function. However it is easy to circumvent by wrapping it in an intermediate function or by using startSpan and context.with yourself. In the long run we should consider tainting the Span objects created in these scopes so they cannot be sent through to a cache. I will consider a follow up PR to land this if we determine taint is likely to be stabilized in React. The pratical implementation of this change is to patch the tracer provider so that it always provides a patched tracer which exits the workUnitStorage on span start/creation and re-enters the scope on inner function exectuion.
183 lines
4.4 KiB
TypeScript
183 lines
4.4 KiB
TypeScript
import { type Span, 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>
|
|
)
|
|
})
|
|
|
|
function Loading() {
|
|
return <span className="fallback">loading...</span>
|
|
}
|
|
|
|
function Result({ children }: { children: React.ReactNode }) {
|
|
return <p className="result">{children}</p>
|
|
}
|