feat(data-pipeline): add fork-safe OTLP gRPC trace transport - #2273
feat(data-pipeline): add fork-safe OTLP gRPC trace transport#2273bm1549 wants to merge 4 commits into
Conversation
Adds a plaintext gRPC-over-HTTP/2 (h2c) trace-export transport for OTLP, implemented as a custom tower::Service plugged into tonic's Grpc<T>. It holds no persistent connection or background task: each send opens a fresh connection driven by ephemeral per-request tasks that are torn down when the send completes, so nothing is orphaned across fork(2). Includes a minimal prost codec (tonic 0.14 moved ProstCodec to a separate crate; hand-rolled here to avoid the extra dependency), endpoint validation (plaintext http:// only; https:// rejected), gRPC-status-to-error mapping that recovers nested IO errors, per-request metadata (validated once at build), and unit plus in-process h2 integration tests. This is the transport primitive; the OTLP export path is wired to it in a follow-up change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
📚 Documentation Check Results📦
|
🔒 Cargo Deny Results📦
|
🎉 All green!🧪 All tests passed 🎯 Code Coverage (details) 🔗 Commit SHA: ad17de3 | Docs | View more details | Give us feedback! |
Artifact Size Benchmark Reportaarch64-alpine-linux-musl
aarch64-unknown-linux-gnu
libdatadog-x64-windows
libdatadog-x86-windows
x86_64-alpine-linux-musl
x86_64-unknown-linux-gnu
|
BenchmarksComparisonBenchmark execution time: 2026-08-20 20:52:36 Comparing candidate commit ad17de3 in PR branch Found 0 performance improvements and 0 performance regressions! Performance is the same for 152 metrics, 0 unstable metrics.
|
yannham
left a comment
There was a problem hiding this comment.
Overall LGTM, nothing major blocking.
Though the same caveat as in the original PR applies about the size issue:
A first naive question: could/should this be feature-gated? I assume you'd want all tracers to support otlp out of the box, so the answers might be no, bu asking just > in case.
A good test for the impact of the binary size increase: would you mind trying to create a PR against dd-trace-py pointing to this PR's libdatadog ref, and see if it triggers the size gates for e.g. serverless?
## Description When native OTLP trace export fails, `NativeWriter` currently reports its configured Datadog Agent intake URL even though libdatadog attempted the OTLP endpoint. This makes an OTLP protocol or connectivity error look like an Agent fallback. Use the configured OTLP trace endpoint in failure diagnostics whenever OTLP trace export is active. Agent export diagnostics keep their existing behavior. The original investigation also exposed a separate protocol mismatch: released versions send OTLP traces over HTTP/JSON, while the repro targeted the collector's gRPC port. HTTP/protobuf and gRPC support are already tracked by #18609, DataDog/libdatadog#2171, and DataDog/libdatadog#2273; this PR does not duplicate those transport changes. ## Testing - `scripts/run-tests --venv 1ef5a52 -- -- tests/tracer/test_writer.py -k 'native_writer_reports_otlp_intake_endpoint or native_writer_stores_otlp_endpoint'` (Python 3.13; 2 passed) - `scripts/lint checks` - `scripts/lint style -- ddtrace/internal/writer/writer.py tests/tracer/test_writer.py` - `scripts/lint spelling -- releasenotes/notes/fix-otlp-export-error-endpoint-6e7b7c8f756ef3a7.yaml` - `riot run reno lint` ## Risks Low. The change only affects the endpoint displayed in trace-export failure diagnostics when an OTLP endpoint is configured. It does not change routing, serialization, or retry behavior. ## Additional Notes The OTLP/DDOT reference and the original handoff document were corrected separately to record that the old port-4317 reproduction was testing an HTTP exporter against a gRPC receiver. Co-authored-by: munir.abdinur <munir.abdinur@datadoghq.com>
…-comments # Conflicts: # Cargo.lock # libdd-data-pipeline/Cargo.toml
What does this PR do?
Adds a plaintext gRPC-over-HTTP/2 (h2c) trace-export transport for OTLP to
libdd-data-pipeline. It's a customtower::Service(H2Service) plugged into tonic's genericGrpc<T>client, so the exporter holds no persistent connection or background task: each send opens a fresh connection driven by ephemeral per-request tasks that are torn down when the send completes.This is the transport primitive only. The OTLP export path is wired to it in a follow-up PR that rebases on top of this one.
Motivation
The earlier OTLP gRPC approach built a tonic
Channel, which spawns a persistent background task. Because libdatadog rebuilds its runtime acrossfork(2), that task dies silently after a fork, which forced aWorkerpluswatch-channel to rebuild it. Making the transport per-request and task-free removes that machinery and is fork-safe by construction.Additional Notes
default-features = falseon tonic (notransport/channel/server); a minimal hand-rolled prost codec replacesProstCodec(which moved to the separatetonic-prostcrate) to avoid the extra dependency.https://endpoints are rejected at build time; terminate TLS in a proxy in front of the endpoint if encryption is needed.How to test the change?
cargo nextest run -p libdd-data-pipelinecovers unit tests plus in-processh2integration tests: a real unary send decoded by a mock gRPC server, and connection-refused, timeout, and post-connect-reset cases that all map toTraceExporterError::Io.cargo clippy -p libdd-data-pipeline --all-targets -- -D warnings,cargo +nightly fmt --check, andcargo check -p libdd-data-pipeline --target wasm32-unknown-unknown --no-default-features(the gRPC path is wasm-gated) pass.