feat(otlp): support http/protobuf OTLP trace export#18609
Conversation
Bumps the libdatadog git rev from v35.0.0 to 664f16fb (the OTLP HTTP/protobuf branch, libdatadog#2115). Renames the renamed crate datadog-remote-config -> libdd-remote-config and ports tracer_flare.rs to the redesigned RemoteConfigContent parse API (per-product parsing via AgentConfigFile/AgentTaskFile instead of the removed RemoteConfigData enum). Regenerates Cargo.lock. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wires the new libdatadog set_otlp_protocol builder setter so OTLP trace export honors OTEL_EXPORTER_OTLP_TRACES_PROTOCOL. http/json (default) and http/protobuf are supported over HTTP; any other value (e.g. the OTel grpc default) falls back to http/json, preserving prior behavior. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Codeowners resolved as |
There was a problem hiding this comment.
This is to fix a breaking change in libdatadog
BenchmarksBenchmark execution time: 2026-06-24 16:47:08 Comparing candidate commit 132f024 in PR branch Found 0 performance improvements and 6 performance regressions! Performance is the same for 612 metrics, 10 unstable metrics. scenario:iast_aspects-re_match_noaspect
scenario:iastaspects-ljust_aspect
scenario:iastaspects-stringio_noaspect
scenario:iastaspectsospath-ospathbasename_aspect
scenario:span-start
scenario:telemetryaddmetric-1-count-metric-1-times
|
# What does this PR do?
Adds OTLP HTTP/protobuf as a trace-export encoding alongside HTTP/JSON, selectable via the OTel-standard `OTEL_EXPORTER_OTLP_TRACES_PROTOCOL` (`http/json` default, `http/protobuf`). The generated `prost` OTLP types are the single intermediate representation: the mapper builds them directly from native spans, protobuf is `prost::encode_to_vec`, and JSON is a serde serializer over the same types.
# Motivation
libdatadog's OTLP trace export only spoke HTTP/JSON. SDKs that honor `OTEL_EXPORTER_OTLP_TRACES_PROTOCOL` need `http/protobuf` to match the OTel default and to talk to collectors that expect protobuf.
# Additional Notes
- Prost is the single IR: no parallel hand-rolled JSON model, no string round-trip. `encode_otlp_protobuf` is `encode_to_vec`; `encode_otlp_json` is a serde serializer (`json_serializer.rs`) emitting OTLP-spec JSON (hex ids, base64 bytes, int64-as-string, lowerCamelCase, proto3 defaults omitted).
- `OtlpProtocol` is `{HttpJson, HttpProtobuf}`; `grpc` is rejected at the parse boundary (`FromStr`) rather than carried as an unsupported variant. `content_type()`/`encode()` live on the type.
- Integrates with the OTLP metrics exporter already on main: the shared low-level sender takes the content-type per request, so traces use the configured protocol while metrics stay JSON.
- Carries through `otel_trace_semantics_enabled` (OTel attribute compatibility, #2091): when set, the prost mapper omits the DD-specific span attributes and promotes the error message to the OTLP `Status`.
- Span-link W3C trace flags are carried through to OTLP `Link.flags`.
- Benchmarks for the encoder hot paths, plus allocation tuning (pre-sized Vecs, allocation-free id/timestamp/int serialization).
# How to test the change?
- `cargo test -p libdd-trace-utils -p libdd-data-pipeline -p libdd-data-pipeline-ffi`, `cargo test --doc`, clippy, fmt, and `cargo ffi-test` pass. A parity test asserts the JSON and protobuf encodings carry the same span from the one prost IR; a protobuf round-trip test asserts the encoding is lossless.
- End-to-end through dd-trace-py (DataDog/dd-trace-py#18609): emitted protobuf-only OTLP traces through a local Agent to the backend. Wire was `application/x-protobuf` (HTTP 200), spans ingested with correct service/resource and a preserved 128-bit trace id.
- Benchmarks: `cargo bench -p libdd-trace-utils --bench main -- otlp/`.
BREAKING CHANGE: removes the previously public `libdd_trace_utils::otlp_encoder::json_types` module (the hand-rolled OTLP JSON model). OTLP encoding now builds prost-generated types as the single IR. libdatadog consumers pin by version, so they pick this up on the next release.
Co-authored-by: brian.marks <brian.marks@datadoghq.com>
) DataDog/libdatadog#2115 merged to main as 4e8e6cc8c. Repoint the native extension's libdatadog git rev from the pre-merge branch SHA to that permanent commit, and regenerate Cargo.lock (libdd-* 35.0.0 -> 36.0.0 plus the transitive deps the merged tree pulls in). Add the AssignmentReason::Default -> Reason::Default arm in ffe.rs: the merged libdatadog FFE feature added that enum variant, so the From<AssignmentReason> for Reason match is otherwise non-exhaustive. Add a reno release note for the http/protobuf OTLP trace export feature. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…otocol stub - Default OTLP trace export to http/protobuf: coerce any non-HTTP protocol (e.g. the OTel-default grpc) to it instead of http/json. - Declare set_otlp_protocol in the _native type stub so mypy resolves the writer.py call (unblocks the prechecks typing lint). - Drop the repeated "grpc unsupported / fallback" comments, keeping one explanation at the coercion site. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The merged libdatadog FFE remaps per-flag config parse failures (bad regex, variant type mismatch) from ConfigurationParseError to the new FlagConfigurationInvalid variant. dd-trace-py's From<EvaluationError> match didn't handle it, so it fell through to ErrorCode::General and the feature_flag.evaluations metric reported error.type=general instead of parse_error, failing the FEATURE_FLAGGING_AND_EXPERIMENTATION system tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
d485fb5 to
a53e451
Compare
test_otlp_traces_sent_via_http now sets OTEL_EXPORTER_OTLP_TRACES_PROTOCOL= http/json explicitly (it validates the JSON encoding, which is no longer the default). Add test_otlp_traces_sent_via_http_protobuf_default asserting the new default emits application/x-protobuf and decodes to the expected span. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The opentelemetry riot venv doesn't ship opentelemetry-proto, so decoding the body via ExportTraceServiceRequest raised ModuleNotFoundError. Validate the protobuf default decode-free instead: protobuf content type, the OTLP resource_spans field-1 tag (0x0a), and the span name bytes in the payload. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Description
Adds
http/protobufas a second OTLP trace-export encoding alongside the existinghttp/jsonpath, and makes it the default. The encoding is selected from the OTel-standardOTEL_EXPORTER_OTLP_TRACES_PROTOCOLvalue and passed to libdatadog via the newset_otlp_protocolbuilder setter.http/protobuf(the default) andhttp/jsonare supported over HTTP.grpc) is coerced tohttp/protobuf.This PR also bumps libdatadog from
v35.0.0to the merged OTLP commit (4e8e6cc8c, DataDog/libdatadog#2115). That bump pulls in thedatadog-remote-config→libdd-remote-configrename and the redesigned remote-config parsing API, sotracer_flare.rsis ported to parse per-product viaRemoteConfigContent(AgentConfigFile/AgentTaskFile).DataDog/libdatadog#2115 is merged. The
revis pinned to the merge commit (4e8e6cc8c) because no libdatadog release includes it yet; repoint to a release tag once one ships.Testing
cargo check --features stats,profiling,crashtracker,ffe(the production feature set) passes against the merged libdatadog rev.application/x-protobuf(HTTP 200), and spans landed with correct service/resource names and a preserved 128-bit trace id.http/jsonregression also confirmed (application/jsonwire, HTTP 200).Risks
http/protobuf; setOTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/jsonfor the previous JSON wire format. Only the encoding changes; the endpoint (/v1/traceson port 4318) serves both._otlp_endpointbeing set; non-OTLP export is unaffected.Additional Notes
The protocol coercion happens on the Python side (
writer.py), so the nativeset_otlp_protocolonly ever receiveshttp/jsonorhttp/protobuf. This matches libdatadog, which rejectsgrpcat the parse boundary.