You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
# 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?
```bash
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`.
Co-authored-by: ekump <edmund.kump@datadoghq.com>
0 commit comments