Skip to content

Commit 3f3148e

Browse files
committed
fix(native-spans): keep OTLP span events on the native event slot
The `meta.events` JSON fallback exists for agents that cannot read the native `span_events` field, gated on `DD_TRACE_NATIVE_SPAN_EVENTS`. That gate is about the agent protocol, so with `OTEL_TRACES_EXPORTER=otlp` and the default flag value every event reached the collector as a JSON string attribute instead of a structured OTLP event, breaking consumers of exception data. The deleted OTLP transformer converted events regardless of the flag. Take the native path whenever the destination is OTLP. Reported by Codex review as P2.
1 parent 78201a7 commit 3f3148e

2 files changed

Lines changed: 23 additions & 5 deletions

File tree

packages/dd-trace/src/native/span.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -559,15 +559,18 @@ class NativeDatadogSpan extends DatadogSpan {
559559
* shape the legacy JS encoder writes (`meta.events` via stringifySpanEvents),
560560
* which is what the agent expects when it doesn't support native span events
561561
* (system-tests Test_SpanEvents_WithoutAgentSupport).
562+
*
563+
* The meta fallback exists purely for agents that cannot read the native slot,
564+
* so it must not apply to OTLP: libdatadog maps the native `span_events` into
565+
* real OTLP events, whereas the meta tag would reach the collector as a JSON
566+
* string attribute. The deleted OTLP transformer converted events regardless of
567+
* this agent-protocol flag, so OTLP always takes the native path.
562568
*/
563569
#serializeSpanEvents () {
564570
if (!this._events?.length) return
565571

566-
// When native span events are enabled (matching the legacy encoder's
567-
// `DD_TRACE_NATIVE_SPAN_EVENTS` gate), append each event to the top-level
568-
// v0.4 `span_events` field via the native setter — no truncation, typed
569-
// attributes. Otherwise fall back to the `events` meta tag (plain JSON).
570-
if (this.tracer()._config.DD_TRACE_NATIVE_SPAN_EVENTS) {
572+
const config = this.tracer()._config
573+
if (config.DD_TRACE_NATIVE_SPAN_EVENTS || config.OTEL_TRACES_EXPORTER === 'otlp') {
571574
for (const event of this._events) {
572575
// `addEvent` and the OTel bridge do not type-check `name`. A non-string
573576
// reaches the WASM string parameter and throws out of `finish()` into

packages/dd-trace/test/native/span.spec.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,21 @@ describe('NativeDatadogSpan', () => {
678678
assert.strictEqual(nativeSpans.addSpanEvent.getCall(0).args[1], 'good')
679679
})
680680

681+
it('uses the native event slot for OTLP even when the agent flag is disabled', () => {
682+
// The meta fallback exists for agents that cannot read the native slot. An
683+
// OTLP collector would receive it as a JSON string attribute instead of
684+
// structured events, so OTLP must always take the native path.
685+
tracer._config.DD_TRACE_NATIVE_SPAN_EVENTS = false
686+
tracer._config.OTEL_TRACES_EXPORTER = 'otlp'
687+
span._events.push({ name: 'exception', startTime: 4 })
688+
689+
span.finish()
690+
691+
sinon.assert.calledOnce(nativeSpans.addSpanEvent)
692+
assert.strictEqual(nativeSpans.addSpanEvent.getCall(0).args[1], 'exception')
693+
assert.strictEqual(span._spanContext.getTag('events'), undefined)
694+
})
695+
681696
it('falls back to the `events` meta tag when the flag is disabled', () => {
682697
tracer._config.DD_TRACE_NATIVE_SPAN_EVENTS = false
683698
span._events.push({ name: 'evt', startTime: 1, attributes: { k: 'v' } })

0 commit comments

Comments
 (0)