Skip to content

Add OTLP http/json trace export (OTEL_TRACES_EXPORTER=otlp)#5888

Draft
bm1549 wants to merge 2 commits into
masterfrom
brian.marks/otlp-trace-export
Draft

Add OTLP http/json trace export (OTEL_TRACES_EXPORTER=otlp)#5888
bm1549 wants to merge 2 commits into
masterfrom
brian.marks/otlp-trace-export

Conversation

@bm1549

@bm1549 bm1549 commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds OTLP http/json trace export to the datadog gem. When OTEL_TRACES_EXPORTER=otlp is set, finished traces are exported over OTLP http/json to an OpenTelemetry collector instead of the Datadog agent. The exporter is hand-rolled in pure Ruby (no new heavy dependency), wired into Tracing::Component#build_writer as an alternate transport, mirroring dd-trace-js.

Configuration (tracing.otlp settings + env mapping):

  • OTEL_TRACES_EXPORTERotlp enables OTLP export; none disables tracing
  • OTEL_EXPORTER_OTLP_TRACES_ENDPOINT → fallback OTEL_EXPORTER_OTLP_ENDPOINT + /v1/traces → default http://<agent_host>:4318/v1/traces
  • OTEL_EXPORTER_OTLP_TRACES_HEADERS / _TIMEOUT (10s) / _PROTOCOL (only http/json honored this phase; warns otherwise)
  • OTEL_TRACES_SAMPLER / _ARG (reuses the existing mapping)

Behavior: DD_TRACE_AGENT_PROTOCOL_VERSION disables OTLP; DD_TRACE_SAMPLE_RATE/rules take precedence; only sampled spans are exported; the agent transport (and Remote Config / /info) is unaffected when OTLP is off.

Motivation:

Part of the cross-language OTLP Traces Export effort (RFC "OTLP Traces Export 2026Q1", Phase 1): let the Datadog SDK emit traces over OTLP for users running an OpenTelemetry collector.

Change log entry

Yes. Add OTLP trace export: set OTEL_TRACES_EXPORTER=otlp to export traces over OTLP (http/json) to an OpenTelemetry collector.

Additional Notes:

Yes — the AI-generated code was read and understood. 47+ OTLP-specific specs plus config/gate specs (374 examples in the combined run, 0 failures); RBS signatures added; StandardRB/Steep clean. Scope is RFC Phase 1: http/json only (no gRPC, no W3C TraceContext L2 / probability sampling, no trace stats, no OTel semantic conventions).

How to test the change?

Covered by new RSpec specs under spec/datadog/tracing/transport/otlp*. End-to-end coverage is added in system-tests (Test_Otel_Tracing_OTLP, activated for ruby). Manually: set OTEL_TRACES_EXPORTER=otlp and OTEL_EXPORTER_OTLP_TRACES_ENDPOINT to a collector and confirm spans arrive as OTLP http/json.

Related PRs (cross-language OTLP traces export):

Merge order: libdatadog #2101 → dd-trace-php #3971 (re-bump the submodule to the released libdatadog commit). system-tests #7121 validates rust/ruby/php once the tracer branches are available.

🤖 Generated with Claude Code

bm1549 and others added 2 commits June 9, 2026 18:42
Export finished traces over OTLP http/json instead of Datadog MessagePack
when OTEL_TRACES_EXPORTER=otlp. The exporter is hand-rolled in pure Ruby
(modeled on dd-trace-js), reusing the gem's JSON and Net::HTTP facilities;
no opentelemetry-exporter-otlp dependency is added on the trace path.

- New pure-Ruby OTLP transport: encoder (DD -> OTLP span/resource/scope
  JSON), Net::HTTP exporter, and a transport implementing the Writer's
  send_traces contract.
- Config settings + env mapping under tracing.otlp: endpoint (+ fallback),
  headers (+ fallback, sensitive), timeout, protocol. Endpoint precedence:
  OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, else OTEL_EXPORTER_OTLP_ENDPOINT with
  /v1/traces appended, else http://<agent_host>:4318/v1/traces.
- Exporter selection in Component#build_writer. Gated off when
  DD_TRACE_AGENT_PROTOCOL_VERSION is set (logs a notice). Agent /info,
  Remote Config, and telemetry continue to use the agent URL.
- Reuses the existing OTEL_TRACES_SAMPLER -> DD_TRACE_SAMPLE_RATE mapping;
  only sampled traces (priority >= AUTO_KEEP) are exported.
- RBS signatures, supported-configurations entries, GettingStarted/OTel docs.
- RSpec coverage for encoding shape, endpoint/header/timeout resolution,
  the protocol-version disable gate, and unsampled-drop.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Stop logging the misleading 'Traces will be sent to Datadog' warning when
  OTEL_TRACES_EXPORTER=otlp (the documented value); tracing stays enabled and
  the OTLP exporter is selected via tracing.otlp.
- Add OTLP::Response#service_rates (nil) so the writer's after-send
  priority-sampler callback short-circuits instead of raising/swallowing a
  NoMethodError on every flush.
- Warn (not fail) when a non-http/json OTLP protocol is configured, matching
  dd-trace-rs, so a protocol misconfiguration isn't a silent no-op.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@bm1549 bm1549 added the AI Generated Largely based on code generated by an AI or LLM. This label is the same across all dd-trace-* repos label Jun 10, 2026
@dd-octo-sts dd-octo-sts Bot added core Involves Datadog core libraries tracing labels Jun 10, 2026
@dd-octo-sts

dd-octo-sts Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Typing analysis

Note: Ignored files are excluded from the next sections.

steep:ignore comments

This PR introduces 1 steep:ignore comment.

steep:ignore comments (+1-0)Introduced:
lib/datadog/tracing/transport/otlp/exporter.rb:38

Untyped methods

This PR introduces 23 partially typed methods. It decreases the percentage of typed methods from 65.14% to 64.63% (-0.51%).

Partially typed methods (+23-0)Introduced:
sig/datadog/tracing/transport/otlp/encoder.rbs:33
└── def initialize: (resource_attributes: Array[Hash[Symbol, untyped]], ?scope_version: String?, ?default_service: String?) -> void
sig/datadog/tracing/transport/otlp/encoder.rbs:35
└── def encode: (untyped trace) -> String
sig/datadog/tracing/transport/otlp/encoder.rbs:37
└── def payload: (untyped trace) -> Hash[Symbol, untyped]
sig/datadog/tracing/transport/otlp/encoder.rbs:41
└── def encode_span: (untyped span, String? trace_id_high) -> Hash[Symbol, untyped]
sig/datadog/tracing/transport/otlp/encoder.rbs:43
└── def attributes: (untyped span) -> Array[Hash[Symbol, untyped]]
sig/datadog/tracing/transport/otlp/encoder.rbs:45
└── def encode_link: (untyped link) -> Hash[Symbol, untyped]
sig/datadog/tracing/transport/otlp/encoder.rbs:47
└── def encode_event: (untyped event) -> Hash[Symbol, untyped]
sig/datadog/tracing/transport/otlp/encoder.rbs:49
└── def any_value: (untyped value) -> Hash[Symbol, untyped]
sig/datadog/tracing/transport/otlp/encoder.rbs:51
└── def numeric_value: (Numeric value) -> Hash[Symbol, untyped]
sig/datadog/tracing/transport/otlp/encoder.rbs:53
└── def integer_value: (Integer value) -> Hash[Symbol, untyped]
sig/datadog/tracing/transport/otlp/encoder.rbs:55
└── def span_kind: (untyped span) -> Integer
sig/datadog/tracing/transport/otlp/encoder.rbs:57
└── def status: (untyped span) -> Hash[Symbol, untyped]
sig/datadog/tracing/transport/otlp/encoder.rbs:63
└── def start_time_nano: (untyped span) -> Integer
sig/datadog/tracing/transport/otlp/encoder.rbs:65
└── def end_time_nano: (untyped span) -> Integer
sig/datadog/tracing/transport/otlp/encoder.rbs:67
└── def string_attribute: (String key, untyped value) -> Hash[Symbol, untyped]
sig/datadog/tracing/transport/otlp/exporter.rbs:18
└── def initialize: (endpoint: String, headers: Hash[String, String]?, timeout_millis: Integer, logger: untyped) -> void
sig/datadog/tracing/transport/otlp.rbs:5
└── def self?.build: (settings: untyped, agent_settings: untyped, ?logger: untyped) -> Transport
sig/datadog/tracing/transport/otlp.rbs:7
└── def self?.resolve_endpoint: (untyped otlp, untyped agent_settings) -> String
sig/datadog/tracing/transport/otlp.rbs:9
└── def self?.resource_attributes: (untyped settings) -> Array[Hash[Symbol, untyped]]
sig/datadog/tracing/transport/otlp.rbs:11
└── def self?.string_attribute: (String key, untyped value) -> Hash[Symbol, untyped]
sig/datadog/tracing/transport/otlp.rbs:24
└── def initialize: (exporter: Exporter, encoder: Encoder, logger: untyped) -> void
sig/datadog/tracing/transport/otlp.rbs:26
└── def send_traces: (untyped traces) -> Array[Response]
sig/datadog/tracing/transport/otlp.rbs:30
└── def drop?: (untyped trace) -> bool

Untyped other declarations

This PR introduces 4 untyped other declarations and 1 partially typed other declaration. It increases the percentage of typed other declarations from 82.59% to 82.7% (+0.11%).

Untyped other declarations (+4-0)Introduced:
sig/datadog/tracing/transport/otlp/exporter.rbs:11
└── @logger: untyped
sig/datadog/tracing/transport/otlp/exporter.rbs:16
└── attr_reader logger: untyped
sig/datadog/tracing/transport/otlp.rbs:16
└── @logger: untyped
sig/datadog/tracing/transport/otlp.rbs:21
└── attr_reader logger: untyped
Partially typed other declarations (+1-0)Introduced:
sig/datadog/tracing/transport/otlp/encoder.rbs:29
└── @resource_attributes: Array[Hash[Symbol, untyped]]

If you believe a method or an attribute is rightfully untyped or partially typed, you can add # untyped:accept on the line before the definition to remove it from the stats.

@datadog-prod-us1-4

datadog-prod-us1-4 Bot commented Jun 10, 2026

Copy link
Copy Markdown

Tests

🎉 All green!

🧪 All tests passed
❄️ No new flaky tests detected

🎯 Code Coverage (details)
Patch Coverage: 46.38%
Overall Coverage: 89.80% (-0.27%)

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI Generated Largely based on code generated by an AI or LLM. This label is the same across all dd-trace-* repos core Involves Datadog core libraries tracing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant