Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/next/src/server/base-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,15 @@ export default abstract class Server<ServerOptions extends Options = Options> {
): Promise<void> {
await this.prepare()
const method = req.method.toUpperCase()

// Extract the w3c trace context headers and pass them to tracer
const traceParentHeader = req.headers.traceparent
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO, it'd be better to either move the propagation extraction code from below to here or to pass headers to the trace call. For instance, this code could execute:

spanContext = propagation.extract(context.active(), req.headers)

And pass an optional spanContext to the trace() method. This way the SDK can configure whatever propagation strategy it likes outside of Next and it'd work.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't want to pass spanContext to trace(), there should also be a way to propagate it via OpenTelemetry's context.with().

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's the alternative, which, IMHO, should cover the target cases better: #57084

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK thanks, I was planning to work on your feedback today but I'm happy to let you take it from here with your PR

Can you help me understand how your PR is an improvement / expand on what you mean by This way the SDK can configure whatever propagation strategy it likes outside of Next and it'd work?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a otel setup (in an application, not in Next), you could do something like this:

traceProvider.register({
  propagator: new XPropagator(),
})

E.g. you could use AWSXRayPropagator or make your own.

This propagator could work off headers or some special environment context. Next.js wouldn't need to know any of that - it'd just call a generic propagator.extract() and (eventually)propagator.inject() and delegate this part fully to the configuration of the application or a deployment environment.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And FWIW, your PR is super close, it just limits which propagators can be used.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, thanks for explaining and for opening your PR. I'll close this one out in favor of yours

const traceStateHeader = req.headers.tracestate
const traceParent =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we extract the propagation, should we also inject it in the downstream fetch calls? Or would that be too subjective?

typeof traceParentHeader === 'string' ? traceParentHeader : undefined
const traceState =
typeof traceStateHeader === 'string' ? traceStateHeader : undefined

return getTracer().trace(
BaseServerSpan.handleRequest,
{
Expand All @@ -711,6 +720,7 @@ export default abstract class Server<ServerOptions extends Options = Options> {
'http.method': method,
'http.target': req.url,
},
reqHeaderTraceContext: { traceParent, traceState },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need the intermediary context object. Also let's use the traceparent as is, without the camelcase, to follow the OTEL convention

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need the intermediary context object

To clarify, do you mean to include the traceparent and tracestate properties in the top level of this TracerSpanOptions object?

},
async (span) =>
this.handleRequestImpl(req, res, parsedUrl).finally(() => {
Expand Down
20 changes: 18 additions & 2 deletions packages/next/src/server/lib/trace/tracer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ if (process.env.NEXT_RUNTIME === 'edge') {
}
}

const { context, trace, SpanStatusCode, SpanKind } = api
const { context, trace, SpanStatusCode, SpanKind, propagation } = api

const isPromise = <T>(p: any): p is Promise<T> => {
return p !== null && typeof p === 'object' && typeof p.then === 'function'
Expand All @@ -47,6 +47,10 @@ type TracerSpanOptions = Omit<SpanOptions, 'attributes'> & {
spanName?: string
attributes?: Partial<Record<AttributeNames, AttributeValue | undefined>>
hideSpan?: boolean
reqHeaderTraceContext?: {
traceParent?: string
traceState?: string
}
}

interface NextTracer {
Expand Down Expand Up @@ -220,8 +224,20 @@ class NextTracerImpl implements NextTracer {
let spanContext = this.getSpanContext(
options?.parentSpan ?? this.getActiveScopeSpan()
)
let isRootSpan = false

// Use w3c trace context headers to create span context if they exist
if (
!spanContext &&
(options.reqHeaderTraceContext?.traceParent ||
options.reqHeaderTraceContext?.traceState)
) {
spanContext = propagation.extract(context.active(), {
traceparent: options.reqHeaderTraceContext?.traceParent,
tracestate: options.reqHeaderTraceContext?.traceState,
})
}

let isRootSpan = false
if (!spanContext) {
spanContext = api.ROOT_CONTEXT
isRootSpan = true
Expand Down