Skip to content

Commit 10c3af3

Browse files
committed
feat(pipeline): add v0.5 output format selection
Add setUseV05() to WasmSpanState so the single trace exporter can emit the v0.5 wire format (/v0.5/traces) instead of the default v0.4. The flag is read once, at the lazy exporter build on first send, then fixed; callers must set it before the first flush. v0.5 uses a fixed 12-field schema with no slots for meta_struct, span_events, or span_links, so libdatadog's v0.5 serializer silently drops them. This mirrors dd-trace-js master's v0.5 encoder and is intentional \u2014 there is no guard and no dual exporter. libdatadog does not downgrade V05 (unlike V1), so the caller (dd-trace-js) is responsible for only enabling this after the agent advertises /v0.5/traces via /info.
1 parent 7ae80b1 commit 10c3af3

2 files changed

Lines changed: 82 additions & 2 deletions

File tree

crates/pipeline/src/lib.rs

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use libdatadog_nodejs_capabilities::WasmCapabilities;
22
use libdd_data_pipeline::trace_exporter::agent_response::AgentResponse;
3-
use libdd_data_pipeline::trace_exporter::{TraceExporter, TraceExporterBuilder};
3+
use libdd_data_pipeline::trace_exporter::{
4+
TraceExporter, TraceExporterBuilder, TraceExporterOutputFormat,
5+
};
46
use libdd_shared_runtime::LocalRuntime;
57
use std::cell::{Cell, RefCell, UnsafeCell};
68
use std::ffi::CStr;
@@ -168,6 +170,16 @@ pub struct WasmSpanState {
168170
/// alias across the await (UB). The guard makes a re-entrant call return
169171
/// an error instead.
170172
sending: Cell<bool>,
173+
/// When true, the lazily-built exporter is configured for v0.5 output
174+
/// (`/v0.5/traces`) instead of the default v0.4. v0.5 is a smaller, fixed
175+
/// 12-field schema with NO slots for `meta_struct`/`span_events`/`span_links`,
176+
/// so libdatadog's v0.5 serializer silently drops them — this mirrors
177+
/// dd-trace-js master's v0.5 encoder and is intentional. Caller (dd-trace-js)
178+
/// must only enable this after confirming the agent advertises `/v0.5/traces`
179+
/// (libdd does NOT downgrade V05 the way it does V1). The output format is
180+
/// fixed once the exporter is built on the first send, so `setUseV05` only
181+
/// takes effect if called before then.
182+
use_v05: Cell<bool>,
171183
}
172184

173185
/// Clears an in-flight flag on drop, so an early return or a dropped future
@@ -256,9 +268,22 @@ impl WasmSpanState {
256268
stats_collector: RefCell::new(stats_collector),
257269
prepared_spans: RefCell::new(None),
258270
sending: Cell::new(false),
271+
use_v05: Cell::new(false),
259272
})
260273
}
261274

275+
/// Select v0.5 output for the trace exporter. Must be called before the
276+
/// first `sendPreparedChunk` (the exporter is built lazily on first send and
277+
/// the output format is fixed at build time; later calls have no effect).
278+
///
279+
/// v0.5 silently drops `meta_struct` (and top-level `span_events`/`span_links`)
280+
/// because the v0.5 wire schema has no slots for them — the caller is
281+
/// responsible for only enabling this when the agent supports `/v0.5/traces`.
282+
#[wasm_bindgen(js_name = "setUseV05")]
283+
pub fn set_use_v05(&self, v: bool) {
284+
self.use_v05.set(v);
285+
}
286+
262287
#[wasm_bindgen]
263288
pub fn change_queue_ptr(&self) -> *const u8 {
264289
self.change_queue.as_ptr()
@@ -373,9 +398,16 @@ impl WasmSpanState {
373398
// First send: build the exporter asynchronously. `build` is not
374399
// available on wasm (it needs a blocking runtime), so we drive
375400
// `build_async` here where we already have an async context.
376-
let builder = unsafe { &mut *self.builder.get() }
401+
let mut builder = unsafe { &mut *self.builder.get() }
377402
.take()
378403
.ok_or_else(|| JsValue::from_str("exporter builder already consumed"))?;
404+
// Output format is decided here, at first build, and then fixed.
405+
// v0.5 drops meta_struct/span_events/span_links by design (the v0.5
406+
// schema has no slots for them); dd-trace-js only enables this after
407+
// confirming agent `/v0.5/traces` support via `/info`.
408+
if self.use_v05.get() {
409+
builder.set_output_format(TraceExporterOutputFormat::V05);
410+
}
379411
let built = builder
380412
.build_async::<WasmCapabilities>()
381413
.await

test/pipeline.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,54 @@ describe('pipeline', () => {
781781
})
782782
})
783783

784+
describe('v0.5 output format', () => {
785+
// Spin up a mock agent that records the request path, so we can assert the
786+
// exporter targets /v0.4/traces by default and /v0.5/traces after
787+
// setUseV05(true). (v0.5 itself drops meta_struct/span_events by design;
788+
// here we only verify endpoint routing, which is the observable behavior.)
789+
async function flushAndCapturePath (useV05) {
790+
const http = require('node:http')
791+
const seen = []
792+
const server = http.createServer((req, res) => {
793+
req.on('data', () => {})
794+
req.on('end', () => {
795+
seen.push({ method: req.method, url: req.url })
796+
res.writeHead(200, { 'content-type': 'application/json' })
797+
res.end('{}')
798+
})
799+
})
800+
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve))
801+
const { port } = server.address()
802+
const ns = new NativeSpansInterface({ agentUrl: `http://127.0.0.1:${port}` })
803+
if (useV05) ns.state.setUseV05(true)
804+
const span = ns.createSpan()
805+
span.name = 'v05-span'
806+
span.service = 'test-service'
807+
span.resource = 'test-resource'
808+
span.type = 'web'
809+
span.duration = 1000000n
810+
try {
811+
await ns.flushSpans(span)
812+
return seen.find(r => r.method === 'POST')
813+
} finally {
814+
server.closeAllConnections?.()
815+
server.close()
816+
}
817+
}
818+
819+
it('targets /v0.4/traces by default', async () => {
820+
const req = await flushAndCapturePath(false)
821+
assert.ok(req, 'agent received a POST')
822+
assert.strictEqual(req.url, '/v0.4/traces')
823+
})
824+
825+
it('targets /v0.5/traces after setUseV05(true)', async () => {
826+
const req = await flushAndCapturePath(true)
827+
assert.ok(req, 'agent received a POST')
828+
assert.strictEqual(req.url, '/v0.5/traces')
829+
})
830+
})
831+
784832
describe('client-side stats', () => {
785833
it('aggregates and flushes stats to /v0.6/stats', async () => {
786834
const http = require('node:http')

0 commit comments

Comments
 (0)