From 8246a0b902edf70bf1106a399f32acbadcf776b9 Mon Sep 17 00:00:00 2001 From: Jules Wiriath Date: Wed, 15 Jul 2026 13:58:10 +0200 Subject: [PATCH 01/14] feat: added env capability --- crates/capabilities/src/env.rs | 49 ++++++++++++++++++++++++ crates/capabilities/src/env_transport.js | 13 +++++++ crates/capabilities/src/lib.rs | 22 +++++++++++ crates/library_config/Cargo.toml | 2 +- crates/process_discovery/Cargo.toml | 7 +--- test/env-transport.js | 37 ++++++++++++++++++ 6 files changed, 124 insertions(+), 6 deletions(-) create mode 100644 crates/capabilities/src/env.rs create mode 100644 crates/capabilities/src/env_transport.js create mode 100644 test/env-transport.js diff --git a/crates/capabilities/src/env.rs b/crates/capabilities/src/env.rs new file mode 100644 index 00000000..a60fa852 --- /dev/null +++ b/crates/capabilities/src/env.rs @@ -0,0 +1,49 @@ +// Copyright 2026-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 + +//! Wasm implementation of [`EnvCapability`] backed by Node.js `process.env`. + +use wasm_bindgen::prelude::*; + +use libdd_capabilities::env::{EnvCapability, EnvError}; + +#[wasm_bindgen(module = "/src/env_transport.js")] +extern "C" { + #[wasm_bindgen(js_name = "get")] + fn js_env_get(name: &str) -> JsValue; + + #[wasm_bindgen(js_name = "set")] + fn js_env_set(name: &str, value: &str); + + #[wasm_bindgen(js_name = "unset")] + fn js_env_unset(name: &str); +} + +#[derive(Debug, Clone)] +pub struct WasmEnvCapability; + +impl EnvCapability for WasmEnvCapability { + fn new() -> Self { + Self + } + + fn get(&self, name: &str) -> Result, EnvError> { + // Node coerces every process.env value to a string, so NotUnicode is unreachable here. + let value = js_env_get(name); + if value.is_undefined() || value.is_null() { + Ok(None) + } else { + Ok(value.as_string()) + } + } + + fn set(&self, name: &str, value: &str) -> Result<(), EnvError> { + js_env_set(name, value); + Ok(()) + } + + fn unset(&self, name: &str) -> Result<(), EnvError> { + js_env_unset(name); + Ok(()) + } +} diff --git a/crates/capabilities/src/env_transport.js b/crates/capabilities/src/env_transport.js new file mode 100644 index 00000000..da79bc07 --- /dev/null +++ b/crates/capabilities/src/env_transport.js @@ -0,0 +1,13 @@ +'use strict' + +module.exports.get = function (name) { + return process.env[name] +} + +module.exports.set = function (name, value) { + process.env[name] = value +} + +module.exports.unset = function (name) { + delete process.env[name] +} diff --git a/crates/capabilities/src/lib.rs b/crates/capabilities/src/lib.rs index ebd385f3..111571c5 100644 --- a/crates/capabilities/src/lib.rs +++ b/crates/capabilities/src/lib.rs @@ -12,14 +12,17 @@ use std::future::Future; use std::time::Duration; +use libdd_capabilities::env::{EnvCapability, EnvError}; use libdd_capabilities::file::{FileCapability, FileError, FileMetadata}; use libdd_capabilities::http::HttpError; use libdd_capabilities::{HttpClientCapability, LogWriterCapability, MaybeSend, SleepCapability}; +pub mod env; pub mod file; pub mod http; pub mod sleep; +pub use env::WasmEnvCapability; pub use file::WasmFileCapability; pub use http::WasmHttpClient; pub use sleep::WasmSleepCapability; @@ -38,6 +41,7 @@ pub struct WasmCapabilities { sleep: WasmSleepCapability, /// Filesystem access delegated to the Node.js `fs` transport. file: WasmFileCapability, + env: WasmEnvCapability, } impl Default for WasmCapabilities { @@ -52,6 +56,7 @@ impl WasmCapabilities { http: WasmHttpClient::new_client(), sleep: WasmSleepCapability, file: WasmFileCapability, + env: WasmEnvCapability, } } } @@ -122,3 +127,20 @@ impl FileCapability for WasmCapabilities { } } +impl EnvCapability for WasmCapabilities { + fn new() -> Self { + Self::new() + } + + fn get(&self, name: &str) -> Result, EnvError> { + self.env.get(name) + } + + fn set(&self, name: &str, value: &str) -> Result<(), EnvError> { + self.env.set(name, value) + } + + fn unset(&self, name: &str) -> Result<(), EnvError> { + self.env.unset(name) + } +} diff --git a/crates/library_config/Cargo.toml b/crates/library_config/Cargo.toml index be76bd45..a4f47ccc 100644 --- a/crates/library_config/Cargo.toml +++ b/crates/library_config/Cargo.toml @@ -8,7 +8,7 @@ crate-type = ["cdylib", "rlib"] [dependencies] anyhow = "1" -libdd-library-config = { git = "https://github.com/DataDog/libdatadog.git", rev = "353134770b312b7ccd2df6afabc253090b948e5f" } +libdd-library-config = { git = "https://github.com/DataDog/libdatadog.git", branch = "jwiriath/env-caps" } wasm-bindgen = "0.2.100" serde = { version = "1.0", features = ["derive"] } diff --git a/crates/process_discovery/Cargo.toml b/crates/process_discovery/Cargo.toml index 44042286..30c0fa25 100644 --- a/crates/process_discovery/Cargo.toml +++ b/crates/process_discovery/Cargo.toml @@ -8,11 +8,8 @@ crate-type = ["cdylib", "rlib"] [dependencies] anyhow = "1" -# Pointed at the merge commit that introduced ThreadLocalMetadata (caller-supplied -# schema version + extra process-context attributes). Swap back to a tagged release -# once one that includes 7cdeb7896e92d1ba38bde495934e112dac2eda25 is published. -libdd-library-config = { git = "https://github.com/DataDog/libdatadog.git", rev = "7cdeb7896e92d1ba38bde495934e112dac2eda25", features = ["otel-thread-ctx"] } -libdd-trace-protobuf = { git = "https://github.com/DataDog/libdatadog.git", rev = "7cdeb7896e92d1ba38bde495934e112dac2eda25" } +libdd-library-config = { git = "https://github.com/DataDog/libdatadog.git", branch = "jwiriath/env-caps", features = ["otel-thread-ctx"] } +libdd-trace-protobuf = { git = "https://github.com/DataDog/libdatadog.git", branch = "jwiriath/env-caps" } napi = { version = "2" } napi-derive = { version = "2", default-features = false } diff --git a/test/env-transport.js b/test/env-transport.js new file mode 100644 index 00000000..f7a82f6d --- /dev/null +++ b/test/env-transport.js @@ -0,0 +1,37 @@ +'use strict' + +// The transport shim is plain CommonJS, so drive it directly. + +const { describe, it, before, after } = require('node:test') +const assert = require('node:assert') + +const envTransport = require('../crates/capabilities/src/env_transport') + +describe('env_transport', () => { + const NAME = 'LIBDD_CAP_TEST_ENV_TRANSPORT' + let savedValue + + before(() => { + savedValue = process.env[NAME] + }) + after(() => { + if (savedValue === undefined) delete process.env[NAME] + else process.env[NAME] = savedValue + }) + + it('returns undefined for an unset var', () => { + delete process.env[NAME] + assert.strictEqual(envTransport.get(NAME), undefined) + }) + + it('set then get round-trips the value', () => { + envTransport.set(NAME, 'value1') + assert.strictEqual(envTransport.get(NAME), 'value1') + }) + + it('unset then get returns undefined', () => { + envTransport.set(NAME, 'value2') + envTransport.unset(NAME) + assert.strictEqual(envTransport.get(NAME), undefined) + }) +}) From d6240f48ff96a37f1e2d9e9101ecf87fa764de76 Mon Sep 17 00:00:00 2001 From: Jules Wiriath Date: Thu, 16 Jul 2026 11:52:11 +0200 Subject: [PATCH 02/14] feat: respect envcapabilities method unsafedness --- crates/capabilities/src/env.rs | 6 ++++-- crates/capabilities/src/lib.rs | 10 ++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/crates/capabilities/src/env.rs b/crates/capabilities/src/env.rs index a60fa852..c5c99ae6 100644 --- a/crates/capabilities/src/env.rs +++ b/crates/capabilities/src/env.rs @@ -37,12 +37,14 @@ impl EnvCapability for WasmEnvCapability { } } - fn set(&self, name: &str, value: &str) -> Result<(), EnvError> { + unsafe fn set(&self, name: &str, value: &str) -> Result<(), EnvError> { + // SAFETY: Wasm is single-threaded; no concurrent env access is possible. js_env_set(name, value); Ok(()) } - fn unset(&self, name: &str) -> Result<(), EnvError> { + unsafe fn unset(&self, name: &str) -> Result<(), EnvError> { + // SAFETY: Wasm is single-threaded; no concurrent env access is possible. js_env_unset(name); Ok(()) } diff --git a/crates/capabilities/src/lib.rs b/crates/capabilities/src/lib.rs index 111571c5..f259559a 100644 --- a/crates/capabilities/src/lib.rs +++ b/crates/capabilities/src/lib.rs @@ -136,11 +136,13 @@ impl EnvCapability for WasmCapabilities { self.env.get(name) } - fn set(&self, name: &str, value: &str) -> Result<(), EnvError> { - self.env.set(name, value) + unsafe fn set(&self, name: &str, value: &str) -> Result<(), EnvError> { + // SAFETY: forwarded verbatim; Wasm is single-threaded so the precondition is trivially upheld. + unsafe { self.env.set(name, value) } } - fn unset(&self, name: &str) -> Result<(), EnvError> { - self.env.unset(name) + unsafe fn unset(&self, name: &str) -> Result<(), EnvError> { + // SAFETY: forwarded verbatim; Wasm is single-threaded so the precondition is trivially upheld. + unsafe { self.env.unset(name) } } } From 01e760fe905b0033a0e15c9ebb0c8f7d279b7809 Mon Sep 17 00:00:00 2001 From: Jules Wiriath Date: Thu, 16 Jul 2026 13:25:20 +0200 Subject: [PATCH 03/14] feat: validate before saying it's ok --- crates/capabilities/src/env.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/capabilities/src/env.rs b/crates/capabilities/src/env.rs index c5c99ae6..51bff883 100644 --- a/crates/capabilities/src/env.rs +++ b/crates/capabilities/src/env.rs @@ -5,7 +5,7 @@ use wasm_bindgen::prelude::*; -use libdd_capabilities::env::{EnvCapability, EnvError}; +use libdd_capabilities::env::{validate_name, validate_value, EnvCapability, EnvError}; #[wasm_bindgen(module = "/src/env_transport.js")] extern "C" { @@ -38,12 +38,15 @@ impl EnvCapability for WasmEnvCapability { } unsafe fn set(&self, name: &str, value: &str) -> Result<(), EnvError> { + validate_name(name)?; + validate_value(value)?; // SAFETY: Wasm is single-threaded; no concurrent env access is possible. js_env_set(name, value); Ok(()) } unsafe fn unset(&self, name: &str) -> Result<(), EnvError> { + validate_name(name)?; // SAFETY: Wasm is single-threaded; no concurrent env access is possible. js_env_unset(name); Ok(()) From 381e7eb005aafd3f900351ad971bf1f0648cf4fd Mon Sep 17 00:00:00 2001 From: Jules Wiriath Date: Thu, 16 Jul 2026 16:24:42 +0200 Subject: [PATCH 04/14] revert: remove set/unset since they would be dangerous APIs in libdatadog (and are most likely not useful in the near/middel future in dd-trace-js) --- crates/capabilities/src/env.rs | 23 +---------------------- crates/capabilities/src/env_transport.js | 8 -------- crates/capabilities/src/lib.rs | 10 ---------- test/env-transport.js | 10 ++-------- 4 files changed, 3 insertions(+), 48 deletions(-) diff --git a/crates/capabilities/src/env.rs b/crates/capabilities/src/env.rs index 51bff883..1cba7dbc 100644 --- a/crates/capabilities/src/env.rs +++ b/crates/capabilities/src/env.rs @@ -5,18 +5,12 @@ use wasm_bindgen::prelude::*; -use libdd_capabilities::env::{validate_name, validate_value, EnvCapability, EnvError}; +use libdd_capabilities::env::{EnvCapability, EnvError}; #[wasm_bindgen(module = "/src/env_transport.js")] extern "C" { #[wasm_bindgen(js_name = "get")] fn js_env_get(name: &str) -> JsValue; - - #[wasm_bindgen(js_name = "set")] - fn js_env_set(name: &str, value: &str); - - #[wasm_bindgen(js_name = "unset")] - fn js_env_unset(name: &str); } #[derive(Debug, Clone)] @@ -36,19 +30,4 @@ impl EnvCapability for WasmEnvCapability { Ok(value.as_string()) } } - - unsafe fn set(&self, name: &str, value: &str) -> Result<(), EnvError> { - validate_name(name)?; - validate_value(value)?; - // SAFETY: Wasm is single-threaded; no concurrent env access is possible. - js_env_set(name, value); - Ok(()) - } - - unsafe fn unset(&self, name: &str) -> Result<(), EnvError> { - validate_name(name)?; - // SAFETY: Wasm is single-threaded; no concurrent env access is possible. - js_env_unset(name); - Ok(()) - } } diff --git a/crates/capabilities/src/env_transport.js b/crates/capabilities/src/env_transport.js index da79bc07..414fd2a9 100644 --- a/crates/capabilities/src/env_transport.js +++ b/crates/capabilities/src/env_transport.js @@ -3,11 +3,3 @@ module.exports.get = function (name) { return process.env[name] } - -module.exports.set = function (name, value) { - process.env[name] = value -} - -module.exports.unset = function (name) { - delete process.env[name] -} diff --git a/crates/capabilities/src/lib.rs b/crates/capabilities/src/lib.rs index f259559a..b2288d2e 100644 --- a/crates/capabilities/src/lib.rs +++ b/crates/capabilities/src/lib.rs @@ -135,14 +135,4 @@ impl EnvCapability for WasmCapabilities { fn get(&self, name: &str) -> Result, EnvError> { self.env.get(name) } - - unsafe fn set(&self, name: &str, value: &str) -> Result<(), EnvError> { - // SAFETY: forwarded verbatim; Wasm is single-threaded so the precondition is trivially upheld. - unsafe { self.env.set(name, value) } - } - - unsafe fn unset(&self, name: &str) -> Result<(), EnvError> { - // SAFETY: forwarded verbatim; Wasm is single-threaded so the precondition is trivially upheld. - unsafe { self.env.unset(name) } - } } diff --git a/test/env-transport.js b/test/env-transport.js index f7a82f6d..f45a3779 100644 --- a/test/env-transport.js +++ b/test/env-transport.js @@ -24,14 +24,8 @@ describe('env_transport', () => { assert.strictEqual(envTransport.get(NAME), undefined) }) - it('set then get round-trips the value', () => { - envTransport.set(NAME, 'value1') + it('returns the value when the var is set', () => { + process.env[NAME] = 'value1' assert.strictEqual(envTransport.get(NAME), 'value1') }) - - it('unset then get returns undefined', () => { - envTransport.set(NAME, 'value2') - envTransport.unset(NAME) - assert.strictEqual(envTransport.get(NAME), undefined) - }) }) From 3f3544aee7d18617eb7aab84c3a1f072f427765f Mon Sep 17 00:00:00 2001 From: Jules Wiriath Date: Fri, 17 Jul 2026 11:48:32 +0200 Subject: [PATCH 05/14] chore: change input to main's commit that has the change --- crates/library_config/Cargo.toml | 2 +- crates/process_discovery/Cargo.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/library_config/Cargo.toml b/crates/library_config/Cargo.toml index a4f47ccc..b6deab09 100644 --- a/crates/library_config/Cargo.toml +++ b/crates/library_config/Cargo.toml @@ -8,7 +8,7 @@ crate-type = ["cdylib", "rlib"] [dependencies] anyhow = "1" -libdd-library-config = { git = "https://github.com/DataDog/libdatadog.git", branch = "jwiriath/env-caps" } +libdd-library-config = { git = "https://github.com/DataDog/libdatadog.git", rev = "0c6e2a5df2a163d34c4f385353ffc5d7257c72f4" } wasm-bindgen = "0.2.100" serde = { version = "1.0", features = ["derive"] } diff --git a/crates/process_discovery/Cargo.toml b/crates/process_discovery/Cargo.toml index 30c0fa25..32e9449e 100644 --- a/crates/process_discovery/Cargo.toml +++ b/crates/process_discovery/Cargo.toml @@ -8,8 +8,8 @@ crate-type = ["cdylib", "rlib"] [dependencies] anyhow = "1" -libdd-library-config = { git = "https://github.com/DataDog/libdatadog.git", branch = "jwiriath/env-caps", features = ["otel-thread-ctx"] } -libdd-trace-protobuf = { git = "https://github.com/DataDog/libdatadog.git", branch = "jwiriath/env-caps" } +libdd-library-config = { git = "https://github.com/DataDog/libdatadog.git", rev = "0c6e2a5df2a163d34c4f385353ffc5d7257c72f4", features = ["otel-thread-ctx"] } +libdd-trace-protobuf = { git = "https://github.com/DataDog/libdatadog.git", rev = "0c6e2a5df2a163d34c4f385353ffc5d7257c72f4" } napi = { version = "2" } napi-derive = { version = "2", default-features = false } From 584465816cf5a10f48d660c1892b25ec6ef4e4f7 Mon Sep 17 00:00:00 2001 From: Jules Wiriath Date: Thu, 23 Jul 2026 12:03:21 +0200 Subject: [PATCH 06/14] docs: update doc to remove specific references to the capabilities --- crates/capabilities/src/lib.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/capabilities/src/lib.rs b/crates/capabilities/src/lib.rs index b2288d2e..e76a3812 100644 --- a/crates/capabilities/src/lib.rs +++ b/crates/capabilities/src/lib.rs @@ -4,10 +4,9 @@ //! Wasm capability implementations for libdatadog-nodejs. //! //! [`WasmCapabilities`] is the bundle struct that implements every capability -//! trait `TraceExporter` requires (HTTP, sleep, log output) using wasm_bindgen -//! and JS transports. The wasm binding crate pins this type as the capability -//! generic for libdatadog's `TraceExporter`, mirroring libdatadog's native -//! `NativeCapabilities`. +//! trait `TraceExporter` requires using wasm_bindgen and JS transports. The +//! wasm binding crate pins this type as the capability generic for libdatadog's +//! `TraceExporter`, mirroring libdatadog's native `NativeCapabilities`. use std::future::Future; use std::time::Duration; From a872df95b0414706484ff0e87aa01907310e841f Mon Sep 17 00:00:00 2001 From: Jules Wiriath Date: Thu, 23 Jul 2026 12:09:54 +0200 Subject: [PATCH 07/14] fix: remove extra checks --- crates/capabilities/src/env.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/crates/capabilities/src/env.rs b/crates/capabilities/src/env.rs index 1cba7dbc..716d4b4a 100644 --- a/crates/capabilities/src/env.rs +++ b/crates/capabilities/src/env.rs @@ -23,11 +23,6 @@ impl EnvCapability for WasmEnvCapability { fn get(&self, name: &str) -> Result, EnvError> { // Node coerces every process.env value to a string, so NotUnicode is unreachable here. - let value = js_env_get(name); - if value.is_undefined() || value.is_null() { - Ok(None) - } else { - Ok(value.as_string()) - } + Ok(js_env_get(name).as_string()) } } From 4c45d0706dc96ab12942a6f6f282151b49c12826 Mon Sep 17 00:00:00 2001 From: Jules Wiriath Date: Mon, 27 Jul 2026 15:57:58 +0200 Subject: [PATCH 08/14] fix: cache env beforehand --- Cargo.lock | 42 ++++++++---------------- crates/capabilities/src/env_transport.js | 6 ++-- 2 files changed, 18 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d45e0970..377fa066 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1072,7 +1072,7 @@ dependencies = [ "libdd-shared-runtime 2.0.0", "libdd-tinybytes", "libdd-trace-normalization", - "libdd-trace-protobuf 4.0.0", + "libdd-trace-protobuf 4.0.0 (git+https://github.com/DataDog/libdatadog.git?rev=3081603d3c74f209be4e3be951f78a1a7469397f)", "libdd-trace-stats", "libdd-trace-utils", "rmp-serde", @@ -1117,26 +1117,12 @@ dependencies = [ [[package]] name = "libdd-library-config" -version = "1.0.0" -source = "git+https://github.com/DataDog/libdatadog.git?rev=353134770b312b7ccd2df6afabc253090b948e5f#353134770b312b7ccd2df6afabc253090b948e5f" -dependencies = [ - "anyhow", - "memfd", - "rand", - "rmp", - "rmp-serde", - "serde", - "serde_yaml", -] - -[[package]] -name = "libdd-library-config" -version = "2.0.0" -source = "git+https://github.com/DataDog/libdatadog.git?rev=7cdeb7896e92d1ba38bde495934e112dac2eda25#7cdeb7896e92d1ba38bde495934e112dac2eda25" +version = "3.0.0" +source = "git+https://github.com/DataDog/libdatadog.git?rev=0c6e2a5df2a163d34c4f385353ffc5d7257c72f4#0c6e2a5df2a163d34c4f385353ffc5d7257c72f4" dependencies = [ "anyhow", "libc", - "libdd-trace-protobuf 3.0.2", + "libdd-trace-protobuf 4.0.0 (git+https://github.com/DataDog/libdatadog.git?rev=0c6e2a5df2a163d34c4f385353ffc5d7257c72f4)", "memfd", "prost", "rand", @@ -1232,7 +1218,7 @@ version = "3.0.0" source = "git+https://github.com/DataDog/libdatadog.git?rev=3081603d3c74f209be4e3be951f78a1a7469397f#3081603d3c74f209be4e3be951f78a1a7469397f" dependencies = [ "anyhow", - "libdd-trace-protobuf 4.0.0", + "libdd-trace-protobuf 4.0.0 (git+https://github.com/DataDog/libdatadog.git?rev=3081603d3c74f209be4e3be951f78a1a7469397f)", ] [[package]] @@ -1243,7 +1229,7 @@ dependencies = [ "anyhow", "fluent-uri", "libdd-common 5.1.0", - "libdd-trace-protobuf 4.0.0", + "libdd-trace-protobuf 4.0.0 (git+https://github.com/DataDog/libdatadog.git?rev=3081603d3c74f209be4e3be951f78a1a7469397f)", "libdd-trace-utils", "log", "percent-encoding", @@ -1253,8 +1239,8 @@ dependencies = [ [[package]] name = "libdd-trace-protobuf" -version = "3.0.2" -source = "git+https://github.com/DataDog/libdatadog.git?rev=7cdeb7896e92d1ba38bde495934e112dac2eda25#7cdeb7896e92d1ba38bde495934e112dac2eda25" +version = "4.0.0" +source = "git+https://github.com/DataDog/libdatadog.git?rev=0c6e2a5df2a163d34c4f385353ffc5d7257c72f4#0c6e2a5df2a163d34c4f385353ffc5d7257c72f4" dependencies = [ "prost", "serde", @@ -1289,7 +1275,7 @@ dependencies = [ "libdd-dogstatsd-client", "libdd-shared-runtime 2.0.0", "libdd-trace-obfuscation", - "libdd-trace-protobuf 4.0.0", + "libdd-trace-protobuf 4.0.0 (git+https://github.com/DataDog/libdatadog.git?rev=3081603d3c74f209be4e3be951f78a1a7469397f)", "libdd-trace-utils", "rmp-serde", "serde", @@ -1320,7 +1306,7 @@ dependencies = [ "libdd-common 5.1.0", "libdd-tinybytes", "libdd-trace-normalization", - "libdd-trace-protobuf 4.0.0", + "libdd-trace-protobuf 4.0.0 (git+https://github.com/DataDog/libdatadog.git?rev=3081603d3c74f209be4e3be951f78a1a7469397f)", "prost", "rand", "rmp", @@ -1357,7 +1343,7 @@ version = "0.2.0" dependencies = [ "anyhow", "getrandom 0.2.17", - "libdd-library-config 1.0.0", + "libdd-library-config", "serde", "serde-wasm-bindgen", "wasm-bindgen", @@ -1811,7 +1797,7 @@ dependencies = [ "libdd-common 5.1.0", "libdd-data-pipeline", "libdd-shared-runtime 2.0.0", - "libdd-trace-protobuf 4.0.0", + "libdd-trace-protobuf 4.0.0 (git+https://github.com/DataDog/libdatadog.git?rev=3081603d3c74f209be4e3be951f78a1a7469397f)", "libdd-trace-stats", "libdd-trace-utils", "rmp-serde", @@ -1872,8 +1858,8 @@ name = "process-discovery" version = "0.1.0" dependencies = [ "anyhow", - "libdd-library-config 2.0.0", - "libdd-trace-protobuf 3.0.2", + "libdd-library-config", + "libdd-trace-protobuf 4.0.0 (git+https://github.com/DataDog/libdatadog.git?rev=0c6e2a5df2a163d34c4f385353ffc5d7257c72f4)", "napi", "napi-derive", ] diff --git a/crates/capabilities/src/env_transport.js b/crates/capabilities/src/env_transport.js index 414fd2a9..92d6504b 100644 --- a/crates/capabilities/src/env_transport.js +++ b/crates/capabilities/src/env_transport.js @@ -1,5 +1,7 @@ 'use strict' -module.exports.get = function (name) { - return process.env[name] +const { env } = process + +module.exports.get = (name) => { + return env[name] } From a595c9d36ac78351a5f0a0d954a767bb1adf4001 Mon Sep 17 00:00:00 2001 From: Jules Wiriath Date: Mon, 20 Jul 2026 18:47:27 +0200 Subject: [PATCH 09/14] feat: Adopt TraceExporter's Telemetry stuff --- crates/pipeline/Cargo.toml | 3 ++- crates/pipeline/src/lib.rs | 34 +++++++++++++++++++++++++++++++++- crates/pipeline/src/stats.rs | 8 ++++---- 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/crates/pipeline/Cargo.toml b/crates/pipeline/Cargo.toml index 29fc2722..4eb8fe30 100644 --- a/crates/pipeline/Cargo.toml +++ b/crates/pipeline/Cargo.toml @@ -16,7 +16,7 @@ serde_json = "1" libdatadog-nodejs-capabilities = { path = "../capabilities" } libdd-capabilities = { git = "https://github.com/DataDog/libdatadog.git", rev = "3081603d3c74f209be4e3be951f78a1a7469397f" } libdd-common = { git = "https://github.com/DataDog/libdatadog.git", rev = "3081603d3c74f209be4e3be951f78a1a7469397f", default-features = false } -libdd-data-pipeline = { git = "https://github.com/DataDog/libdatadog.git", rev = "3081603d3c74f209be4e3be951f78a1a7469397f", default-features = false } +libdd-data-pipeline = { git = "https://github.com/DataDog/libdatadog.git", rev = "3081603d3c74f209be4e3be951f78a1a7469397f", default-features = false, features = ["telemetry"] } libdd-trace-utils = { git = "https://github.com/DataDog/libdatadog.git", rev = "3081603d3c74f209be4e3be951f78a1a7469397f", default-features = false, features = ["change-buffer"] } libdd-trace-stats = { git = "https://github.com/DataDog/libdatadog.git", rev = "3081603d3c74f209be4e3be951f78a1a7469397f", default-features = false } libdd-trace-protobuf = { git = "https://github.com/DataDog/libdatadog.git", rev = "3081603d3c74f209be4e3be951f78a1a7469397f", default-features = false } @@ -25,6 +25,7 @@ web-time = "1" rmp-serde = "1" bytes = "1" http = "1" +web-time = "1" console_error_panic_hook = "0.1" [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/crates/pipeline/src/lib.rs b/crates/pipeline/src/lib.rs index 31188af5..f6bc3247 100644 --- a/crates/pipeline/src/lib.rs +++ b/crates/pipeline/src/lib.rs @@ -1,7 +1,7 @@ use libdatadog_nodejs_capabilities::WasmCapabilities; use libdd_data_pipeline::trace_exporter::agent_response::AgentResponse; use libdd_data_pipeline::trace_exporter::{ - TraceExporter, TraceExporterBuilder, TraceExporterOutputFormat, + TelemetryConfig, TraceExporter, TraceExporterBuilder, TraceExporterOutputFormat, }; use libdd_data_pipeline::OtlpProtocol; use libdd_shared_runtime::LocalRuntime; @@ -204,6 +204,11 @@ pub struct WasmSpanState { /// Extra HTTP headers for OTLP export (e.g. collector auth), as key/value /// pairs. Only applied when `otlp_endpoint` is set. otlp_headers: RefCell>, + /// When set, the lazily-built exporter has telemetry enabled with this + /// config (`heartbeat`/`runtime_id`/`debug_enabled` — see libdatadog's + /// `TelemetryConfig`). Only takes effect if set before the first send + /// (when the exporter is built). + telemetry_config: RefCell>, /// Latched message from a failed lazy `build_async`. Building is one-shot and /// a failure is fatal (bad config), so once set every send returns it (as a /// distinguishable error) instead of a misleading "builder already consumed", @@ -338,6 +343,7 @@ impl WasmSpanState { otlp_endpoint: RefCell::new(None), otlp_protocol: Cell::new(None), otlp_headers: RefCell::new(Vec::new()), + telemetry_config: RefCell::new(None), build_error: RefCell::new(None), }) } @@ -374,6 +380,29 @@ impl WasmSpanState { Ok(()) } + /// Enable telemetry on the lazily-built trace exporter. Off by default — + /// dd-trace-js opts in from JS. `heartbeat_ms` sets the metric-flush cadence + /// (0 disables periodic heartbeats and only sends on shutdown), `runtime_id` + /// tags telemetry payloads with the tracer's runtime id when provided, and + /// `debug_enabled` toggles libdd-telemetry's verbose logging. + /// + /// Must be called before the first `sendPreparedChunk` — the exporter is + /// built lazily on first send and telemetry config is fixed at build time, + /// so later calls have no effect. + #[wasm_bindgen(js_name = "enableTelemetry")] + pub fn enable_telemetry( + &self, + heartbeat_ms: u64, + runtime_id: Option, + debug_enabled: bool, + ) { + *self.telemetry_config.borrow_mut() = Some(TelemetryConfig { + heartbeat: heartbeat_ms, + runtime_id, + debug_enabled, + }); + } + /// Set extra HTTP headers for OTLP export as a flat `[key, value, ...]` /// array (the host flattens its key/value map). Only takes effect with an /// OTLP endpoint set, before the first send. A trailing unpaired element on @@ -530,6 +559,9 @@ impl WasmSpanState { builder.set_otlp_headers(headers.clone()); } } + if let Some(cfg) = self.telemetry_config.borrow().clone() { + builder.enable_telemetry(cfg); + } match builder.build_async::().await { Ok(built) => *exporter_slot = Some(built), Err(e) => { diff --git a/crates/pipeline/src/stats.rs b/crates/pipeline/src/stats.rs index ac17a112..3e65cb46 100644 --- a/crates/pipeline/src/stats.rs +++ b/crates/pipeline/src/stats.rs @@ -9,11 +9,11 @@ use web_time::{Duration, SystemTime}; -/// Wall-clock now() for wasm. `std::time::SystemTime::now()` is unimplemented on -/// `wasm32-unknown-unknown` (it panics/traps), so derive the time from JS -/// `Date.now()` (milliseconds since the Unix epoch). +/// Wall-clock now() for wasm. Delegates to `web_time::SystemTime::now()`, +/// which routes to JS `Date.now()` on `wasm32-unknown-unknown` (native +/// `std::time::SystemTime::now()` is unimplemented on that target and traps). fn now() -> SystemTime { - SystemTime::UNIX_EPOCH + Duration::from_millis(js_sys::Date::now() as u64) + SystemTime::now() } use bytes::Bytes; From d2187a8a86ce2902681900f73db8f30db87875b2 Mon Sep 17 00:00:00 2001 From: Jules Wiriath Date: Tue, 21 Jul 2026 14:13:59 +0200 Subject: [PATCH 10/14] docs: fix libdatadog's behavior when 0 is passed --- crates/pipeline/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/pipeline/src/lib.rs b/crates/pipeline/src/lib.rs index f6bc3247..baace39f 100644 --- a/crates/pipeline/src/lib.rs +++ b/crates/pipeline/src/lib.rs @@ -382,7 +382,7 @@ impl WasmSpanState { /// Enable telemetry on the lazily-built trace exporter. Off by default — /// dd-trace-js opts in from JS. `heartbeat_ms` sets the metric-flush cadence - /// (0 disables periodic heartbeats and only sends on shutdown), `runtime_id` + /// (0 defers to libdatadog's default interval), `runtime_id` /// tags telemetry payloads with the tracer's runtime id when provided, and /// `debug_enabled` toggles libdd-telemetry's verbose logging. /// From 72c003e0ae9d9d8e959e9c1294c53d51ae2b1ea4 Mon Sep 17 00:00:00 2001 From: Jules Wiriath Date: Tue, 21 Jul 2026 14:19:38 +0200 Subject: [PATCH 11/14] chore: js tomfoolery --- crates/pipeline/src/lib.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/pipeline/src/lib.rs b/crates/pipeline/src/lib.rs index baace39f..d913f2ad 100644 --- a/crates/pipeline/src/lib.rs +++ b/crates/pipeline/src/lib.rs @@ -381,10 +381,10 @@ impl WasmSpanState { } /// Enable telemetry on the lazily-built trace exporter. Off by default — - /// dd-trace-js opts in from JS. `heartbeat_ms` sets the metric-flush cadence - /// (0 defers to libdatadog's default interval), `runtime_id` - /// tags telemetry payloads with the tracer's runtime id when provided, and - /// `debug_enabled` toggles libdd-telemetry's verbose logging. + /// dd-trace-js opts in from JS. `heartbeat_ms`sets the metric-flush cadence + /// (0 defers to libdatadog's default interval), `runtime_id` tags telemetry + /// payloads with the tracer's runtime id when provided, and `debug_enabled` + /// toggles libdd-telemetry's verbose logging. /// /// Must be called before the first `sendPreparedChunk` — the exporter is /// built lazily on first send and telemetry config is fixed at build time, @@ -392,12 +392,12 @@ impl WasmSpanState { #[wasm_bindgen(js_name = "enableTelemetry")] pub fn enable_telemetry( &self, - heartbeat_ms: u64, + heartbeat_ms: u32, runtime_id: Option, debug_enabled: bool, ) { *self.telemetry_config.borrow_mut() = Some(TelemetryConfig { - heartbeat: heartbeat_ms, + heartbeat: heartbeat_ms as u64, runtime_id, debug_enabled, }); From 4adacd9b1906422b4fd552beca9538a7586cd7c9 Mon Sep 17 00:00:00 2001 From: Jules Wiriath Date: Mon, 27 Jul 2026 12:13:17 +0200 Subject: [PATCH 12/14] fix: comments --- crates/pipeline/src/lib.rs | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/crates/pipeline/src/lib.rs b/crates/pipeline/src/lib.rs index d913f2ad..89106e23 100644 --- a/crates/pipeline/src/lib.rs +++ b/crates/pipeline/src/lib.rs @@ -205,10 +205,9 @@ pub struct WasmSpanState { /// pairs. Only applied when `otlp_endpoint` is set. otlp_headers: RefCell>, /// When set, the lazily-built exporter has telemetry enabled with this - /// config (`heartbeat`/`runtime_id`/`debug_enabled` — see libdatadog's - /// `TelemetryConfig`). Only takes effect if set before the first send + /// config. Only takes effect if set before the first send /// (when the exporter is built). - telemetry_config: RefCell>, + telemetry_config: Cell>, /// Latched message from a failed lazy `build_async`. Building is one-shot and /// a failure is fatal (bad config), so once set every send returns it (as a /// distinguishable error) instead of a misleading "builder already consumed", @@ -343,7 +342,7 @@ impl WasmSpanState { otlp_endpoint: RefCell::new(None), otlp_protocol: Cell::new(None), otlp_headers: RefCell::new(Vec::new()), - telemetry_config: RefCell::new(None), + telemetry_config: Cell::new(None), build_error: RefCell::new(None), }) } @@ -380,15 +379,17 @@ impl WasmSpanState { Ok(()) } - /// Enable telemetry on the lazily-built trace exporter. Off by default — - /// dd-trace-js opts in from JS. `heartbeat_ms`sets the metric-flush cadence - /// (0 defers to libdatadog's default interval), `runtime_id` tags telemetry - /// payloads with the tracer's runtime id when provided, and `debug_enabled` - /// toggles libdd-telemetry's verbose logging. + /// Enable telemetry on the lazily-built trace exporter. /// - /// Must be called before the first `sendPreparedChunk` — the exporter is - /// built lazily on first send and telemetry config is fixed at build time, - /// so later calls have no effect. + /// Must be called before the first `sendPreparedChunk`. Later calls have + /// no effect. + /// + /// # Arguments + /// + /// - `heartbeat_ms`: sets the metric-flush cadence. Set to 0 to defer to + /// libdatadog's default interval. + /// - `runtime_id`: tags telemetry payloads with the tracer's runtime id when provided + /// - `debug_enabled`: toggles libdd-telemetry's verbose logging #[wasm_bindgen(js_name = "enableTelemetry")] pub fn enable_telemetry( &self, @@ -396,11 +397,11 @@ impl WasmSpanState { runtime_id: Option, debug_enabled: bool, ) { - *self.telemetry_config.borrow_mut() = Some(TelemetryConfig { + self.telemetry_config.set(Some(TelemetryConfig { heartbeat: heartbeat_ms as u64, runtime_id, debug_enabled, - }); + })); } /// Set extra HTTP headers for OTLP export as a flat `[key, value, ...]` @@ -559,7 +560,7 @@ impl WasmSpanState { builder.set_otlp_headers(headers.clone()); } } - if let Some(cfg) = self.telemetry_config.borrow().clone() { + if let Some(cfg) = self.telemetry_config.take() { builder.enable_telemetry(cfg); } match builder.build_async::().await { From cf9b3564c302f073634354d404633fa273a22b57 Mon Sep 17 00:00:00 2001 From: Jules Wiriath Date: Mon, 27 Jul 2026 17:24:15 +0200 Subject: [PATCH 13/14] fix: use Cells uniformaly --- Cargo.lock | 33 ++++++++++++++++++++++++++++++++- crates/pipeline/Cargo.toml | 1 - crates/pipeline/src/lib.rs | 22 +++++++++++----------- 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 377fa066..848464cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1031,7 +1031,7 @@ dependencies = [ "libc", "libdd-common 5.0.0", "libdd-libunwind-sys", - "libdd-telemetry", + "libdd-telemetry 5.0.1", "nix 0.29.0", "num-derive", "num-traits", @@ -1070,6 +1070,7 @@ dependencies = [ "libdd-ddsketch 1.1.0", "libdd-dogstatsd-client", "libdd-shared-runtime 2.0.0", + "libdd-telemetry 6.0.0", "libdd-tinybytes", "libdd-trace-normalization", "libdd-trace-protobuf 4.0.0 (git+https://github.com/DataDog/libdatadog.git?rev=3081603d3c74f209be4e3be951f78a1a7469397f)", @@ -1204,6 +1205,35 @@ dependencies = [ "winver", ] +[[package]] +name = "libdd-telemetry" +version = "6.0.0" +source = "git+https://github.com/DataDog/libdatadog.git?rev=3081603d3c74f209be4e3be951f78a1a7469397f#3081603d3c74f209be4e3be951f78a1a7469397f" +dependencies = [ + "anyhow", + "async-trait", + "base64", + "bytes", + "futures", + "getrandom 0.2.17", + "hashbrown 0.15.5", + "http", + "libc", + "libdd-capabilities 2.1.0", + "libdd-common 5.1.0", + "libdd-ddsketch 1.1.0", + "libdd-shared-runtime 2.0.0", + "serde", + "serde_json", + "sys-info", + "tokio", + "tokio-util", + "tracing", + "uuid", + "web-time", + "winver", +] + [[package]] name = "libdd-tinybytes" version = "1.1.1" @@ -1274,6 +1304,7 @@ dependencies = [ "libdd-ddsketch 1.1.0", "libdd-dogstatsd-client", "libdd-shared-runtime 2.0.0", + "libdd-telemetry 6.0.0", "libdd-trace-obfuscation", "libdd-trace-protobuf 4.0.0 (git+https://github.com/DataDog/libdatadog.git?rev=3081603d3c74f209be4e3be951f78a1a7469397f)", "libdd-trace-utils", diff --git a/crates/pipeline/Cargo.toml b/crates/pipeline/Cargo.toml index 4eb8fe30..8056816e 100644 --- a/crates/pipeline/Cargo.toml +++ b/crates/pipeline/Cargo.toml @@ -21,7 +21,6 @@ libdd-trace-utils = { git = "https://github.com/DataDog/libdatadog.git", rev = " libdd-trace-stats = { git = "https://github.com/DataDog/libdatadog.git", rev = "3081603d3c74f209be4e3be951f78a1a7469397f", default-features = false } libdd-trace-protobuf = { git = "https://github.com/DataDog/libdatadog.git", rev = "3081603d3c74f209be4e3be951f78a1a7469397f", default-features = false } libdd-shared-runtime = { git = "https://github.com/DataDog/libdatadog.git", rev = "3081603d3c74f209be4e3be951f78a1a7469397f", default-features = false } -web-time = "1" rmp-serde = "1" bytes = "1" http = "1" diff --git a/crates/pipeline/src/lib.rs b/crates/pipeline/src/lib.rs index 89106e23..4ff36bb3 100644 --- a/crates/pipeline/src/lib.rs +++ b/crates/pipeline/src/lib.rs @@ -197,13 +197,13 @@ pub struct WasmSpanState { /// Datadog agent. libdatadog maps its internal traces to OTLP, so no /// JS-formatted spans are involved. Like `use_v05`, only takes effect if /// set before the first send (when the exporter is built). - otlp_endpoint: RefCell>, + otlp_endpoint: Cell>, /// OTLP wire protocol (`http/json` default, or `http/protobuf`). Only /// applied when `otlp_endpoint` is set. otlp_protocol: Cell>, /// Extra HTTP headers for OTLP export (e.g. collector auth), as key/value /// pairs. Only applied when `otlp_endpoint` is set. - otlp_headers: RefCell>, + otlp_headers: Cell>, /// When set, the lazily-built exporter has telemetry enabled with this /// config. Only takes effect if set before the first send /// (when the exporter is built). @@ -339,9 +339,9 @@ impl WasmSpanState { prepared_spans: RefCell::new(Vec::new()), sending: Cell::new(false), use_v05: Cell::new(false), - otlp_endpoint: RefCell::new(None), + otlp_endpoint: Cell::new(None), otlp_protocol: Cell::new(None), - otlp_headers: RefCell::new(Vec::new()), + otlp_headers: Cell::new(Vec::new()), telemetry_config: Cell::new(None), build_error: RefCell::new(None), }) @@ -364,7 +364,7 @@ impl WasmSpanState { /// Takes precedence over `setUseV05` (OTLP bypasses the agent entirely). #[wasm_bindgen(js_name = "setOtlpEndpoint")] pub fn set_otlp_endpoint(&self, url: String) { - *self.otlp_endpoint.borrow_mut() = Some(url); + self.otlp_endpoint.set(Some(url)); } /// Select the OTLP wire protocol: `http/json` (default) or `http/protobuf`. @@ -416,7 +416,7 @@ impl WasmSpanState { .chunks_exact(2) .map(|pair| (pair[0].clone(), pair[1].clone())) .collect(); - *self.otlp_headers.borrow_mut() = headers; + self.otlp_headers.set(headers); } #[wasm_bindgen] @@ -550,14 +550,14 @@ impl WasmSpanState { // When an OTLP endpoint is configured, libdatadog exports traces via // OTLP HTTP to that endpoint instead of the Datadog agent (mutually // exclusive with the agent v0.4/v0.5 path). - if let Some(url) = self.otlp_endpoint.borrow().as_deref() { - builder.set_otlp_endpoint(url); - if let Some(protocol) = self.otlp_protocol.get() { + if let Some(url) = self.otlp_endpoint.take() { + builder.set_otlp_endpoint(&url); + if let Some(protocol) = self.otlp_protocol.take() { builder.set_otlp_protocol(protocol); } - let headers = self.otlp_headers.borrow(); + let headers = self.otlp_headers.take(); if !headers.is_empty() { - builder.set_otlp_headers(headers.clone()); + builder.set_otlp_headers(headers); } } if let Some(cfg) = self.telemetry_config.take() { From c2f0eed5137b6105812e12241a028ee0e4b48454 Mon Sep 17 00:00:00 2001 From: Jules Wiriath Date: Tue, 11 Aug 2026 12:03:37 +0200 Subject: [PATCH 14/14] feat: make the losslessness obvious --- crates/pipeline/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/pipeline/src/lib.rs b/crates/pipeline/src/lib.rs index 4ff36bb3..eb25866e 100644 --- a/crates/pipeline/src/lib.rs +++ b/crates/pipeline/src/lib.rs @@ -398,7 +398,7 @@ impl WasmSpanState { debug_enabled: bool, ) { self.telemetry_config.set(Some(TelemetryConfig { - heartbeat: heartbeat_ms as u64, + heartbeat: u64::from(heartbeat_ms), runtime_id, debug_enabled, }));