Skip to content

[BUG]: OTel getActiveSpan().setAttributes() fails silently (Context Drift) #7194

@naseemkullah

Description

@naseemkullah

Tracer Version(s)

latest

Node.js Version(s)

lts

Bug Report

When using the Datadog SDK with the OTel Bridge, attributes set on a span retrieved via otel.trace.getActiveSpan() are ignored and do not appear in Datadog.

In our testing, we found that the OpenTelemetry "Active Span" and the Datadog internal "Active Scope" had drifted apart. Calls to the OTel object succeeded (no error thrown) but resulted in no data change, whereas calls to the Datadog scope in the exact same function worked immediately.

Reproduce

We observed this in a standard utility function used to enrich spans.

import ddTrace from 'dd-trace';
import { trace } from '@opentelemetry/api';

function enrichSpan(attributes) {
  const otelSpan = trace.getActiveSpan();
  const ddSpan = ddTrace.scope().active();

  // 1. OTel Attempt: SILENT FAILURE
  // This executes without error, but 'my.otel.attr' NEVER appears in the Datadog UI.
  otelSpan?.setAttribute('my.otel.attr', 'failed');

  // 2. Datadog Attempt: SUCCESS
  // This works perfectly, and 'my.dd.attr' appears on the trace.
  ddSpan?.addTags({ 'my.dd.attr': 'success' });
}

Expected behavior

trace.getActiveSpan() should return a proxy to the currently active Datadog span (ddTrace.scope().active()). Setting attributes on one should be identical to adding tags to the other.

Actual behavior

  • trace.getActiveSpan() returns a span object (it is not undefined).

  • No errors are thrown when calling .setAttribute().

  • The attributes set via OTel are discarded and do not appear in Datadog.

  • The attributes set via ddTrace in the exact same function are saved and appear correctly.

Root Cause Analysis

This indicates a desynchronization between the Datadog ScopeManager and the OpenTelemetry ContextManager.

The Datadog SDK appears to be maintaining the "real" active span in its own internal storage (AsyncLocalStorage for DD). The OTel Bridge is supposed to mirror this state into the OTel context, but it fails to keep them in sync. As a result, getActiveSpan() returns a "Ghost Object"—a wrapper around a span that is no longer connected to the active trace context in the engine.

Reproduction Code

No response

Error Logs

No response

Tracer Config

No response

Operating System

No response

Bundling

Unsure

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions