Skip to content

feat(data-pipeline)!: add stdout log trace exporter#2074

Merged
gh-worker-dd-mergequeue-cf854d[bot] merged 3 commits into
mainfrom
bengl/trace-log-exporter
Jun 26, 2026
Merged

feat(data-pipeline)!: add stdout log trace exporter#2074
gh-worker-dd-mergequeue-cf854d[bot] merged 3 commits into
mainfrom
bengl/trace-log-exporter

Conversation

@bengl

@bengl bengl commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds a stdout "log exporter" transport to libdatadog's trace pipeline. When enabled, TraceExporter writes traces as newline-delimited JSON ({"traces":[[...]]}) to stdout in the format consumed by the Datadog Forwarder, instead of sending them to an agent over HTTP.

New pieces:

  • libdd-trace-utils::json_log_encoder — Forwarder JSON encoder: lowercase hex IDs (incl. 128-bit), integer error, ns start/duration, empty-field omission, greedy 256 KiB line batching, oversize-span drop.
  • TraceExporterBuilder::set_output_to_log(max_line_size) — selects a stdout destination that bypasses agent-info polling, client-side stats, V1 negotiation, and telemetry. Exposed over FFI as ddog_trace_exporter_config_set_output_to_log.
  • libdd_capabilities::LogWriterCapability — the write goes through a capability so the transport also works on wasm (where the host, e.g. JavaScript, supplies the sink). The native capability writes to stdout.

Motivation

Serverless runtimes (primarily AWS Lambda) often have no reachable Datadog agent or Lambda extension. The established fallback is to write traces to stdout, where the Datadog Forwarder tails the logs and submits them to the trace intake. Today each tracer (dd-trace-js/py/go/java) reimplements this independently; this lands a single reusable implementation in libdatadog that native-backed tracers can adopt.

Additional Notes

  • Breaking (Rust API): TraceExporter<C> (and TraceExporterBuilder::build/build_async, trace_buffer::DefaultExport<C>) now require C: LogWriterCapability. Callers using a custom capability bundle must implement libdd_capabilities::LogWriterCapability; NativeCapabilities already implements it, so callers using NativeCapabilities are unaffected. PR title carries the ! marker accordingly.
  • Selection policy stays with the caller — there is no env-reading helper in libdatadog (an earlier recommended_log_output() was removed per review; reading env from libdatadog has caused production crashes). The SDK detects Lambda/agent/extension itself and calls set_output_to_log.
  • meta_struct is intentionally omitted (raw msgpack the log intake can't interpret), matching the reference exporters.
  • Writes are synchronous/blocking — intended for single-threaded serverless runtimes where there is no shared async reactor to stall. Log output takes precedence over an OTLP endpoint if both are configured.
  • No new third-party dependencies (serde_json already in tree) → no Cargo.lock / LICENSE-3rdparty.csv churn.

How to test the change?

cargo nextest run -p libdd-trace-utils -p libdd-data-pipeline -p libdd-data-pipeline-ffi -E '!test(tracing_integration_tests::)'
cargo +stable clippy -p libdd-capabilities -p libdd-capabilities-impl -p libdd-trace-utils -p libdd-data-pipeline -p libdd-data-pipeline-ffi --all-targets --all-features -- -D warnings
cargo ffi-test   # FFI crate touched

Key tests:

  • json_log_encoder — golden bytes, Forwarder is_trace contract, hex (incl. 128-bit), size-cap batching, oversize-drop, non-finite-metric → null, multi-trace flatten, span_links/span_events present-case.
  • trace_exporter::tests::test_log_mode_send_writes_forwarder_json — full send(msgpack) → decode → log branch → capability bytes (via a capturing test capability).
  • trace_exporter::tests::test_log_mode_makes_no_agent_requests — zero agent calls + info_fetcher not spawned in log mode.
  • log_writer helper tests; FFI config_output_to_log_test.

@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Clippy Allow Annotation Report

Comparing clippy allow annotations between branches:

  • Base Branch: origin/main
  • PR Branch: origin/bengl/trace-log-exporter

Summary by Rule

Rule Base Branch PR Branch Change
unwrap_used 3 3 No change (0%)
Total 3 3 No change (0%)

Annotation Counts by File

File Base Branch PR Branch Change
libdd-data-pipeline/src/trace_buffer/mod.rs 1 1 No change (0%)
libdd-data-pipeline/src/trace_exporter/mod.rs 2 2 No change (0%)

Annotation Stats by Crate

Crate Base Branch PR Branch Change
clippy-annotation-reporter 5 5 No change (0%)
datadog-ffe-ffi 1 1 No change (0%)
datadog-ipc 22 22 No change (0%)
datadog-live-debugger 4 4 No change (0%)
datadog-live-debugger-ffi 10 10 No change (0%)
datadog-profiling-replayer 4 4 No change (0%)
datadog-sidecar 45 45 No change (0%)
libdd-common 13 13 No change (0%)
libdd-common-ffi 12 12 No change (0%)
libdd-data-pipeline 6 6 No change (0%)
libdd-ddsketch 2 2 No change (0%)
libdd-dogstatsd-client 1 1 No change (0%)
libdd-profiling 13 13 No change (0%)
libdd-remote-config 3 3 No change (0%)
libdd-telemetry 20 20 No change (0%)
libdd-tinybytes 4 4 No change (0%)
libdd-trace-normalization 2 2 No change (0%)
libdd-trace-obfuscation 3 3 No change (0%)
libdd-trace-stats 1 1 No change (0%)
libdd-trace-utils 11 11 No change (0%)
Total 182 182 No change (0%)

About This Report

This report tracks Clippy allow annotations for specific rules, showing how they've changed in this PR. Decreasing the number of these annotations generally improves code quality.

@datadog-prod-us1-5

datadog-prod-us1-5 Bot commented Jun 3, 2026

Copy link
Copy Markdown

Tests

🎉 All green!

🧪 All tests passed
❄️ No new flaky tests detected

🎯 Code Coverage (details)
Patch Coverage: 93.71%
Overall Coverage: 74.03% (+0.08%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 8fe6e6e | Docs | Datadog PR Page | Give us feedback!

@codecov-commenter

codecov-commenter commented Jun 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.53312% with 41 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.68%. Comparing base (a1da9fc) to head (c71fb77).
⚠️ Report is 69 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2074      +/-   ##
==========================================
+ Coverage   73.44%   73.68%   +0.24%     
==========================================
  Files         465      478      +13     
  Lines       77949    79613    +1664     
==========================================
+ Hits        57248    58664    +1416     
- Misses      20701    20949     +248     
Components Coverage Δ
libdd-crashtracker 65.34% <ø> (-0.14%) ⬇️
libdd-crashtracker-ffi 37.68% <ø> (ø)
libdd-alloc 98.77% <ø> (ø)
libdd-data-pipeline 86.30% <85.71%> (-0.85%) ⬇️
libdd-data-pipeline-ffi 73.97% <75.00%> (-3.07%) ⬇️
libdd-common 79.93% <ø> (+0.04%) ⬆️
libdd-common-ffi 74.41% <ø> (ø)
libdd-telemetry 73.34% <ø> (-0.03%) ⬇️
libdd-telemetry-ffi 31.36% <ø> (ø)
libdd-dogstatsd-client 82.64% <ø> (ø)
datadog-ipc 76.36% <ø> (+0.14%) ⬆️
libdd-profiling 81.69% <ø> (ø)
libdd-profiling-ffi 64.79% <ø> (ø)
libdd-sampling 97.48% <ø> (+0.06%) ⬆️
datadog-sidecar 36.51% <ø> (+1.89%) ⬆️
datdog-sidecar-ffi 12.23% <ø> (+3.61%) ⬆️
spawn-worker 48.86% <ø> (ø)
libdd-tinybytes 93.80% <ø> (ø)
libdd-trace-normalization 81.71% <ø> (ø)
libdd-trace-obfuscation 87.30% <ø> (ø)
libdd-trace-protobuf 68.25% <ø> (ø)
libdd-trace-utils 89.76% <98.78%> (+0.54%) ⬆️
libdd-tracer-flare 86.57% <ø> (-0.31%) ⬇️
libdd-log 74.83% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@bengl bengl force-pushed the bengl/trace-log-exporter branch from ca7c877 to a090331 Compare June 3, 2026 15:16
@dd-octo-sts

dd-octo-sts Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Artifact Size Benchmark Report

aarch64-alpine-linux-musl
Artifact Baseline Commit Change
/aarch64-alpine-linux-musl/lib/libdatadog_profiling.so 7.82 MB 7.82 MB +0% (+48 B) 👌
/aarch64-alpine-linux-musl/lib/libdatadog_profiling.a 84.87 MB 85.14 MB +.32% (+279.54 KB) 🔍
aarch64-unknown-linux-gnu
Artifact Baseline Commit Change
/aarch64-unknown-linux-gnu/lib/libdatadog_profiling.a 96.00 MB 96.28 MB +.29% (+287.86 KB) 🔍
/aarch64-unknown-linux-gnu/lib/libdatadog_profiling.so 10.44 MB 10.51 MB +.65% (+69.59 KB) 🔍
libdatadog-x64-windows
Artifact Baseline Commit Change
/libdatadog-x64-windows/debug/dynamic/datadog_profiling_ffi.dll 25.05 MB 25.14 MB +.35% (+91.50 KB) 🔍
/libdatadog-x64-windows/debug/dynamic/datadog_profiling_ffi.lib 87.68 KB 88.04 KB +.40% (+360 B) 🔍
/libdatadog-x64-windows/debug/dynamic/datadog_profiling_ffi.pdb 182.80 MB 183.18 MB +.20% (+384.00 KB) 🔍
/libdatadog-x64-windows/debug/static/datadog_profiling_ffi.lib 936.10 MB 937.84 MB +.18% (+1.74 MB) 🔍
/libdatadog-x64-windows/release/dynamic/datadog_profiling_ffi.dll 8.20 MB 8.22 MB +.28% (+24.00 KB) 🔍
/libdatadog-x64-windows/release/dynamic/datadog_profiling_ffi.lib 87.68 KB 88.04 KB +.40% (+360 B) 🔍
/libdatadog-x64-windows/release/dynamic/datadog_profiling_ffi.pdb 24.23 MB 24.31 MB +.35% (+88.00 KB) 🔍
/libdatadog-x64-windows/release/static/datadog_profiling_ffi.lib 48.34 MB 48.47 MB +.26% (+132.88 KB) 🔍
libdatadog-x86-windows
Artifact Baseline Commit Change
/libdatadog-x86-windows/debug/dynamic/datadog_profiling_ffi.dll 21.71 MB 21.80 MB +.38% (+85.00 KB) 🔍
/libdatadog-x86-windows/debug/dynamic/datadog_profiling_ffi.lib 89.06 KB 89.42 KB +.39% (+364 B) 🔍
/libdatadog-x86-windows/debug/dynamic/datadog_profiling_ffi.pdb 186.80 MB 187.19 MB +.20% (+400.00 KB) 🔍
/libdatadog-x86-windows/debug/static/datadog_profiling_ffi.lib 924.73 MB 926.46 MB +.18% (+1.73 MB) 🔍
/libdatadog-x86-windows/release/dynamic/datadog_profiling_ffi.dll 6.33 MB 6.35 MB +.34% (+22.50 KB) 🔍
/libdatadog-x86-windows/release/dynamic/datadog_profiling_ffi.lib 89.06 KB 89.42 KB +.39% (+364 B) 🔍
/libdatadog-x86-windows/release/dynamic/datadog_profiling_ffi.pdb 25.99 MB 26.09 MB +.39% (+104.00 KB) 🔍
/libdatadog-x86-windows/release/static/datadog_profiling_ffi.lib 45.96 MB 46.11 MB +.32% (+152.57 KB) 🔍
x86_64-alpine-linux-musl
Artifact Baseline Commit Change
/x86_64-alpine-linux-musl/lib/libdatadog_profiling.a 75.64 MB 75.89 MB +.33% (+255.93 KB) 🔍
/x86_64-alpine-linux-musl/lib/libdatadog_profiling.so 8.68 MB 8.70 MB +.27% (+24.06 KB) 🔍
x86_64-unknown-linux-gnu
Artifact Baseline Commit Change
/x86_64-unknown-linux-gnu/lib/libdatadog_profiling.a 91.09 MB 91.35 MB +.28% (+262.67 KB) 🔍
/x86_64-unknown-linux-gnu/lib/libdatadog_profiling.so 10.57 MB 10.59 MB +.22% (+24.00 KB) 🔍

@bengl bengl force-pushed the bengl/trace-log-exporter branch from a090331 to cb8bad8 Compare June 3, 2026 15:59
@bengl bengl marked this pull request as ready for review June 3, 2026 16:08
@bengl bengl requested review from a team as code owners June 3, 2026 16:08

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cb8bad815b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread libdd-data-pipeline/src/serverless.rs Outdated
Comment on lines +27 to +31
const AGENT_ENDPOINT_ENV_VARS: [&str; 3] = [
"DD_AGENT_HOST",
"DD_TRACE_AGENT_URL",
"DATADOG_TRACE_AGENT_HOSTNAME",
];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include DD_TRACE_AGENT_PORT in endpoint detection

When this helper is used in Lambda, a deployment that sets only DD_TRACE_AGENT_PORT to point at a non-default local/extension agent is still classified as having no explicit agent endpoint, so recommended_log_output() returns true and callers may switch to stdout instead of the configured agent. Other libdatadog config paths treat DD_TRACE_AGENT_PORT as part of trace-agent endpoint resolution (for example telemetry/crashtracker read it alongside DD_AGENT_HOST and DD_TRACE_AGENT_URL), so this list should include it to avoid misrouting traces in that configuration.

Useful? React with 👍 / 👎.

Comment thread libdd-data-pipeline/src/serverless.rs Outdated
fn has_explicit_agent_endpoint() -> bool {
AGENT_ENDPOINT_ENV_VARS
.iter()
.any(|var| matches!(env::var(var), Ok(value) if !value.is_empty()))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getting / setting env vars in libdatadog should be avoided, it's led to production crashes in the past. (See here and here). It's best if the SDKs just pass us the values via the builder.

/// which serializes spans directly to stdout.
#[cfg(not(target_arch = "wasm32"))]
pub fn send_trace_chunks<T: TraceData>(
pub fn send_trace_chunks<T: TraceData + serde::Serialize>(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@iunanua (asking you since you've been working on the semver checks recently) - I'm curious why semver checks aren't failing here? Shouldn't this be considered a breaking change?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is clearly a breaking change, if the parent modules of this one are public


/// Create a writer targeting a custom sink (used in tests).
#[cfg(test)]
pub(crate) fn with_sink(out: Box<dyn Write + Send>, max_line_size: usize) -> Self {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is currently gated to #[cfg(test)]. Isn't this going to be needed as part of the public API in order for this to work with wasm? I don't think libdatadog can write to stdout when wasm is the target?

And speaking of wasm, should we be leveraging the capabilities crate for the log writer?

// until the page/module is torn down).
#[cfg(target_arch = "wasm32")]
let _ = info_fetcher_handle;
let _ = &info_fetcher_handle;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel this is useless, because instead of dropping the info_fetcher_handle, we drop a reference to it, which doesn't do much. Was this change intentional?

/// flush. On a multi-threaded async runtime this can stall the reactor, so log
/// mode is intended for serverless / current-thread runtimes (e.g. AWS Lambda).
pub(crate) struct LogTraceWriter {
out: Mutex<Box<dyn Write + Send>>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think stdout() is already using a mutex internally. Since it's Send + Sync and &Stdout: Write, I believe you can just use Stdout here without an outer mutex, if we always send to Stdout.


/// Create a writer targeting a custom sink (used in tests).
#[cfg(test)]
pub(crate) fn with_sink(out: Box<dyn Write + Send>, max_line_size: usize) -> Self {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, you need a more generic form for tests... we could define enum Sink { Stdout(Stdout), Other(Mutex<Box<dyn Write + Send>>). Not sure it's worth it, if the mutex is never contended and just here to please the compiler, though.

/// which serializes spans directly to stdout.
#[cfg(not(target_arch = "wasm32"))]
pub fn send_trace_chunks<T: TraceData>(
pub fn send_trace_chunks<T: TraceData + serde::Serialize>(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is clearly a breaking change, if the parent modules of this one are public

"Wrote traces to log exporter"
);
return Ok(AgentResponse::Unchanged);
}

@yannham yannham Jun 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: since this is a totally different codepath that returns early, might be clearer to move that logic into its own synchronous helper (rustc is pretty good at inlining local functions, so this should be free).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: since this is a totally different codepath that returns early, might be clearer to move that logic into its own synchronous helper (rustc is pretty good at inlining local functions, so this should be free).

Up this nitpick (although not blocking, I'll let you decide);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Applied your suggestion


/// Shared in-memory sink for asserting bytes written in log-export mode.
#[derive(Clone, Default)]
struct SharedSink(std::sync::Arc<std::sync::Mutex<Vec<u8>>>);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Arc is imported already? At least in the parent module?

Suggested change
struct SharedSink(std::sync::Arc<std::sync::Mutex<Vec<u8>>>);
struct SharedSink(Arc<std::sync::Mutex<Vec<u8>>>);

Comment thread libdd-data-pipeline/src/serverless.rs Outdated
/// deliberately not exposed over the FFI: host SDKs typically already have their
/// own serverless/agent detection and may implement their own recommendation.
pub fn recommended_log_output() -> bool {
recommended_log_output_inner(Path::new(DATADOG_EXTENSION_PATH))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: should probably inline this function here. It's not like the outer function adds much logic/wrapping.

Comment on lines +83 to +100
if has_type {
len += 1;
}
if !span.meta.is_empty() {
len += 1;
}
if !span.metrics.is_empty() {
len += 1;
}
// `meta_struct` is intentionally NOT emitted: it holds raw msgpack bytes
// that would serialize as a JSON number array the intake cannot interpret.
// The reference log exporters (JS/Go/Py/Java) omit it as well.
if !span.span_links.is_empty() {
len += 1;
}
if !span.span_events.is_empty() {
len += 1;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if has_type {
len += 1;
}
if !span.meta.is_empty() {
len += 1;
}
if !span.metrics.is_empty() {
len += 1;
}
// `meta_struct` is intentionally NOT emitted: it holds raw msgpack bytes
// that would serialize as a JSON number array the intake cannot interpret.
// The reference log exporters (JS/Go/Py/Java) omit it as well.
if !span.span_links.is_empty() {
len += 1;
}
if !span.span_events.is_empty() {
len += 1;
}
len += has_type as usize;
len += (!span.meta.is_empty()) as usize;
len += (!span.metrics.is_empty()) as usize;
// `meta_struct` is intentionally NOT emitted: it holds raw msgpack bytes
// that would serialize as a JSON number array the intake cannot interpret.
// The reference log exporters (JS/Go/Py/Java) omit it as well.
len += (!span.span_links.is_empty()) as usize;
len += (!span.span_events.is_empty()) as usize;

@bengl bengl force-pushed the bengl/trace-log-exporter branch from cb8bad8 to bc7d64d Compare June 4, 2026 13:58
@bengl bengl requested a review from a team as a code owner June 4, 2026 13:58
@bengl bengl force-pushed the bengl/trace-log-exporter branch 2 times, most recently from b7ac109 to 7a39e74 Compare June 4, 2026 15:46
@bengl bengl changed the title feat(data-pipeline): add stdout log trace exporter feat(data-pipeline)!: add stdout log trace exporter Jun 4, 2026
/// synchronous/blocking, so this mode targets single-threaded serverless
/// runtimes where blocking stdout writes do not stall a shared async reactor.
///
/// Takes precedence over an OTLP endpoint: if both this and `set_otlp_endpoint`

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is non-blocking and can be addressed in a separate PR...

I'm not a fan of the exporter containing precedence logic for configuration. I left a similar comment on @paullegranddc's agentless PR. I believe config logic should live in the SDKs as much as possible, and if the builder receives conflicting config settings, we should return an error. If we want to support precedence to avoid setup friction for customers that should be handled by the SDKs. I don't think there is any harm in merging it as is for now. Let's just capture it in a Jira ticket.

// The handle is currently only tracked for shutdown on native; on wasm
// it is dropped here (the worker keeps running on the JS event loop
// until the page/module is torn down).
// In log-export mode there is no agent to poll; skip spawning the worker

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left a comment about agent info and telemetry workers being unnecessarily spawned in @paullegranddc's agentless PR. Beyond the scope of this PR, but after these two PRs land we should discuss refactoring the builder a little bit to handle this a bit more elegantly.

assert!(out.is_empty());
}

/// Mirrors the Datadog Forwarder `is_trace` detection contract (spec §6.1):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor: Since these are technically public docs, if we are going to refer to specs in them by section, the spec should be public too. There is an rfc folder in the libdatadog workspace where you can place the spec as markdown and link to. Alternatively, you could just not mention the spec in the rustdoc comments and not worry about it.

@ekump ekump Jun 5, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, hold on, this is a test. There shouldn't be rustdoc comments at all. I'd suggest just changing the /// to //. I don't have any issue with the spec language in the comment if it's just a code comment and not a rustdoc comment.

@ekump ekump left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of minor comments, mostly calling out future work for the team. LGTM.

@yannham yannham left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the log capability, even is slightly more verbose, since we get mocking cleanly. Beside one previous nitpick that still applies, LGTM;

// event loop until the page/module is torn down).
#[cfg(target_arch = "wasm32")]
let _ = info_fetcher_handle;
drop(info_fetcher_handle);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude can't refrain from rewriting unrelated stuff with bad taste, can't he 😛 (I'm mostly joking, I don't care what syntax people use to drop value, although I do personally prefer the former)

"Wrote traces to log exporter"
);
return Ok(AgentResponse::Unchanged);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: since this is a totally different codepath that returns early, might be clearer to move that logic into its own synchronous helper (rustc is pretty good at inlining local functions, so this should be free).

Up this nitpick (although not blocking, I'll let you decide);

@bengl bengl force-pushed the bengl/trace-log-exporter branch from 7a39e74 to c71fb77 Compare June 5, 2026 20:23
Add a "log exporter" transport that writes traces as newline-delimited
JSON to stdout in the format consumed by the Datadog Forwarder, instead
of sending them to an agent over HTTP. This is the serverless fallback
used in AWS Lambda when no Datadog agent or Lambda extension is
reachable, matching the log writers in dd-trace-js/py/go/java.

- json_log_encoder (libdd-trace-utils): encodes spans to
  {"traces":[[...]]} lines with lowercase hex ids (incl. 128-bit),
  integer error, ns start/duration, empty-field omission, and greedy
  256 KiB line batching that drops oversize single spans.
- TraceExporterBuilder::set_output_to_log selects a stdout destination
  that bypasses agent-info polling, client-side stats, V1 negotiation,
  and telemetry. Exposed over FFI as
  ddog_trace_exporter_config_set_output_to_log.
- The write goes through a new LogWriterCapability so the transport also
  works on wasm (where the host, e.g. JavaScript, supplies the sink);
  the native capability writes to stdout.

Writes are synchronous and intended for single-threaded serverless
runtimes. meta_struct is omitted (raw msgpack the log intake can't read).

BREAKING CHANGE: TraceExporter<C> (and TraceExporterBuilder::build /
build_async and trace_buffer::DefaultExport<C>) now require
C: LogWriterCapability. Callers using a custom capability bundle must
implement libdd_capabilities::LogWriterCapability; NativeCapabilities
already implements it, so callers using NativeCapabilities are unaffected.
@ekump ekump force-pushed the bengl/trace-log-exporter branch from c71fb77 to 0e871e4 Compare June 26, 2026 16:51
@gh-worker-dd-mergequeue-cf854d gh-worker-dd-mergequeue-cf854d Bot merged commit c2751ef into main Jun 26, 2026
131 checks passed
@gh-worker-dd-mergequeue-cf854d gh-worker-dd-mergequeue-cf854d Bot deleted the bengl/trace-log-exporter branch June 26, 2026 19:51
gh-worker-dd-mergequeue-cf854d Bot pushed a commit that referenced this pull request Jun 29, 2026
## chore: release v37.0.0

Bumps the workspace version from `36.0.0` to `37.0.0` in `Cargo.toml` and regenerates `Cargo.lock`.

## Post-merge steps

1. Trigger the `create_release` job on GitLab — builds artifacts and creates a draft GitHub release.
2. Ask someone from `libdatadog-core` or `libdatadog-release` to publish the draft release.
3. Trigger the `release-proposal-dispatch` GitHub Actions workflow for per-crate crates.io publishing:
   crates: libdd-data-pipeline,libdd-trace-stats,libdd-trace-utils,libdd-ddsketch

## Notable changes since v36.0.0

- `feat(data-pipeline)`: export client-computed span stats as OTLP trace metrics (#2067)
- `feat(otel-thread-ctx)`: add self check capability (#2095)
- `fix(trace-stats)`: add `grpc_method` to aggregation key (#2151)
- `feat(data-pipeline)`: add stdout log trace exporter (#2074)
- `feat(remote-config)`: use the proto file from the agent (#2165)
- `feat(sidecar)`: expose `default_service_name` for `svc.*` process tags (#2053)
- feat(otlp)!: Export OTLP spans with attribute-level OTel compatibility (#2091)

Co-authored-by: munir.abdinur <munir.abdinur@datadoghq.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants