Skip to content

Nested continueTrace calls in @sentry/node cause new root span creation within callback #11199

Closed as not planned
@Lms24

Description

@Lms24

Problem Statement

Our current Otel-node-specific implementation of continueTrace creates a remote span which holds the trace information from the sentry-trace and baggage data passed to continueTrace (as described in #11092).

The presence of the remote span as the currently active span forces startSpan to create a new root span for the first startSpan call within the continueTrace callback. This causes potentially unexpected behavior, whenever nested continueTrace calls occur.

I came across this problem in our SvelteKit SDK where we run into a situation where we indeed call continueTrace in a nested form. In certain situations, an initial server request (creating a http.server root span in a continueTrace callback) can make a "sub-request" which would trigger the same instrumentation to make another continueTrace call and start another http.server span. While this span should be a child span within the outer http.server root span, it becomes its own root span at the moment.

For SvelteKit I opted to only call continueTrace once and avoid calling it if we already have an active span. TODO: Link to PR once it's up.

In a more general example, this behaviour would lead to two root spans/transactions:

function handle(callback) {
  return continueTrace({sentry-trace, baggage}, () => {
    return Sentry.startSpan({name: 'http.server'}, () => {
      return callback();
    })
  })
}

function getUser() {
  return handle(() => {
    return {id: 1};
  })
}

function getUserDetails() {
  return handle(() => {
    const user = getUser();
    return {...user, name: 'foo'};
  })
}

Now imagine this handle function being a middleware or something that's automatically invoked whenever a get function is called. In situations with a sub request like in Kit or user-created nested continueTrace calls, we'd end up with multiple http.server root spans instead of a nested structure.

Solution Brainstorm

Before thinking about a concrete solution, we need to answer a more fundamental question: Is this actually a problem or intended behaviour?

This can be argued both ways:

  1. [intended] If I'm in an active root span but I call continueTrace - why should my new spans within that call even be part of the active root span? Especially if the trace data differs, there's no reason to connect these anymore. In this case we want separate root spans.
  2. [problematic] In our previous none-otel based Node SDK, nested continueTrace calls did nothing(?) or even changed the entire transaction's trace parent to the trace data from the nested call (assuming the trace data wasn't identical). Changing this behaviour could confuse users who currently rely on this behavior (just like we did in SvelteKit).

After thinking about it for a bit I'm currently tentatively leaning towards intended behaviour (1). If the team agrees, we can leave things as is but we need to point this out clearly in our documentation (i.e. "don't make nested continueTrace calls")

In case we want to change the behaviour (i.e. go with (2)), we need a solution how to handle such situations. Some ideas:

  1. Simply ignore the nested continueTrace call (maybe log a warning) and therefore avoid creating a remote span -> no new root span
  2. Wait until we can stream spans because by then the trace context has to be passed on per span (or segment?). This implicitly also means multiple root spans but maybe at that point it's fine 🤔

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions