feat(telemetry): forward request spans to Datadog APM with RUM correlation - #813
Merged
Conversation
…ation Production tracing overlay (run-fleet.sh --enable-tracing): fleet-api OTLP spans -> OTel collector sidecar -> Datadog, with APM stats via the datadog connector. New FLEET_TELEMETRY_TRUST_INCOMING_TRACES parents server spans to the traceparent header Datadog RUM injects, linking RUM sessions to APM traces; the sampler caps remote-sampled parents at the configured rate so clients cannot bypass sampling. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
🔐 Codex Security Review
Review SummaryOverall Risk: LOW Findings[LOW] Client-Controlled Trace Context Can Suppress Or Force Server APM Sampling
NotesReviewed only Generated by Codex Security Review | |
ankitgoswami
approved these changes
Jul 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Reviewable diff: +206/-13 across 8 files (excludes generated, test, and story files).
Summary
Adds a production tracing overlay so operators can forward fleet-api request spans to Datadog APM with one flag (
run-fleet.sh --enable-tracing+DD_API_KEYin.env). Spans parent to thetraceparentheader the existing Datadog RUM integration already injects on API calls, so browser sessions link directly to backend traces in Datadog. The dev stack (Jaeger) is unchanged, and deployments without the overlay see no behavior change.How it works
fleetd's tracing pipeline was already vendor-neutral OTLP (built for the dev Jaeger stack); this PR wires it to Datadog in production and closes the RUM↔APM gap:
run-fleet.sh --enable-tracing(orENABLE_TRACING=truein.env) validates prerequisites (overlay file present,DD_API_KEYset) before any compose invocation — the overlay's${DD_API_KEY:?}interpolation would otherwise abort every compose command includingdown— then layersdocker-compose.tracing.yamlonto the stack.FLEET_TELEMETRY_ENABLED=trueon fleet-api. The existingotelhttpmiddleware creates a span per HTTP request and exports OTLP/HTTP to the collector. Host-networked fleet-api reaches it viaextra_hosts: otel-collector=127.0.0.1against a loopback-only published port.FLEET_TELEMETRY_TRUST_INCOMING_TRACES(overlay default: on) disablesotelhttp's public-endpoint mode, so server spans parent to the client's W3Ctraceparentinstead of starting a fresh trace with a link. Datadog RUM already injects that header on same-origin API calls (allowedTracingUrls), giving session→trace navigation in Datadog. The sampler caps remote-sampled parents atFLEET_TELEMETRY_SAMPLE_RATE, so a client's sampled flag cannot force ingestion past the configured rate.datadog/connector(computes APM hit/error/latency stats, which the exporter itself stopped doing in contrib 0.95), and both the re-emitted traces and the stats metrics export to Datadog keyed byDD_API_KEY/DD_SITE, tagged withenvfromDD_ENV.Diagrams
flowchart LR subgraph Browser RUM["Datadog RUM SDK"] end subgraph Host["Fleet host"] NGINX["fleet-client nginx"] API["fleet-api (otelhttp middleware)"] COLL["otel-collector sidecar"] end DD["Datadog APM"] RUM -- "fetch + traceparent" --> NGINX NGINX -- "/api-proxy strip" --> API API -- "OTLP HTTP (loopback 4318)" --> COLL COLL -- "traces + APM stats" --> DD RUM -. "RUM events" .-> DDflowchart TD REQ["Incoming request with traceparent"] --> TRUST{"TRUST_INCOMING_TRACES?"} TRUST -- "false (default)" --> NEW["New trace, client context kept as span link"] TRUST -- "true (overlay default)" --> PARENT["Span parents to client trace"] PARENT --> CAP["Sampler caps remote-sampled parents at SAMPLE_RATE"]Areas of the code involved
server/internal/infrastructure/fleet-telemetry/telemetry.goTrustIncomingTracesconfig; sampler addsWithRemoteParentSampled(TraceIDRatioBased)server/internal/handlers/middleware/telemetry.gootelhttppublic-endpoint modeserver/cmd/fleetd/main.godeployment-files/docker-compose.tracing.yamlDD_API_KEYfail-fastdeployment-files/server/otel-collector-config.datadog.yamldatadog/connectortwo-hop pipelinedeployment-files/run-fleet.sh--enable-tracingflag,.envtoggle, early prerequisite validationdeployment-files/README.md.github/workflows/proto-fleet-artifact-build.ymlKey technical decisions & trade-offs
datadogexporter over the Datadog Agent ordd-trace-go— fleetd stays vendor-neutral OTLP and dev keeps Jaeger; the Agent would add a heavy container for traces alone, anddd-trace-gowould fork the instrumentation path per backend./api-proxybefore fleet-api sees requests, so there is no clean server-side discriminator for RUM traffic.WithRemoteParentSampled(TraceIDRatioBased)instead of ParentBased defaults — the default honors any remote sampled flag, letting clients bypassSAMPLE_RATE(ingest cost). Capping is deterministic per trace ID, so correlation survives for traces that are recorded; an incoming not-sampled flag is still honored.${DD_API_KEY:?}in the overlay aborts every compose command once layered; validating late corrupted the data-volume reinit path (compose down fails, script proceeds).datadog/connectortwo-hop pipeline — since collector-contrib 0.95 the exporter no longer computes APM stats; without the connector, Trace Explorer works but service/resource pages stay empty.Testing & validation
just lintclean (buf, eslint, golangci-lint x3).go testgreen for both changed packages. New tests: middleware parent-vs-link semantics under both trust modes (span parents to client trace vs. new trace with link), and sampler regression tests proving a remote-sampled parent is dropped at rate 0 and kept at rate 1.otelcol-contrib:0.150.1image (validateexit 0).docker compose config),extra_hosts/depends_on/env merges confirmed; missingDD_API_KEYfails with the intended message.bash -nclean; CRLF-tolerant.envtoggle verified in a debian container.🤖 Generated with Claude Code