Allow telemetry-free OpenFeature providers - #12387
Conversation
Allow OpenFeature domains to evaluate flags without producing Datadog telemetry. Environment: Datadog workspace
Keep exposure dispatch in the evaluator and guard telemetry with a provider-scoped boolean. Environment: Datadog workspace
🟢 Java Benchmark SLOs — All performance SLOs passed
PR vs. master results
Commit: Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion. |
There was a problem hiding this comment.
🟢 Approval recommended
The implementation is backward-compatible and well tested; only a minor documentation wording correction remains.
Pull request overview
Adds provider-scoped suppression of OpenFeature telemetry while preserving flag evaluation behavior.
Changes:
- Adds
Options.telemetryEnabled(boolean), defaulting to enabled. - Suppresses telemetry hooks and exposure dispatch when disabled.
- Adds tests and named-domain usage documentation.
File summaries
| File | Description |
|---|---|
Provider.java |
Configures telemetry policy and hooks. |
DDEvaluator.java |
Guards exposure dispatch. |
ProviderTest.java |
Tests options and hook suppression. |
DDEvaluatorTest.java |
Tests exposure suppression. |
README.md |
Documents telemetry-free domains. |
Review details
- Files reviewed: 5/5 changed files
- Comments generated: 1
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
More details
The provider option stops exposure dispatch and all provider telemetry hooks. The default option keeps current telemetry behavior.
🤖 Datadog Autotest · Commit 43c2472 · What is Autotest? · @DataDog review to ask questions · Any feedback? Reach out in #autotest
|
🤖 From Codex working with Aaron: Ask: Use one process-wide, versioned configuration snapshot as the source for all providers. Keep the existing single poller and request path. Do not copy the configuration into each evaluator. The current register-and-replay flow can apply old configuration A after new configuration B. This can leave one domain on A until a later update. Publish one shared snapshot: record ConfigSnapshot(long version, ServerConfiguration config) {}
static void dispatch(ServerConfiguration config) {
ConfigSnapshot next =
new ConfigSnapshot(VERSION.incrementAndGet(), config);
CURRENT.set(next);
notifyListeners(next);
}Each This gives the required rule: an evaluation before publication uses A, and an evaluation after publication uses B. It is acceptable if two separate evaluations are on opposite sides of an update and return different results. A lock around registration and dispatch can prevent stale replay, but it still updates evaluators one at a time. The shared snapshot gives the stronger guarantee. The telemetry option stays local to each provider. Configuration requests stay shared for the JVM. Please add tests for these cases:
|
aarsilv
left a comment
There was a problem hiding this comment.
Thanks for taking care of this so quickly! I like how the shared downstream configuration requesting mechanisms avoid multiple config requests. Approving assuming you push the sharing down a level so there is config race condition (see comment from me working with Codex).
Comments from me working with Claude incoming, those you can take or leave as you see fit (but recommend taking, and I'm experimenting authoring them in a way they are hopefully easier to incorporate)
@aarsilv Interesting find thank you - I'll start with a TDD to demonstrate whether this is happening before addressing it. |
A hard requirement in my mind; I don't want to make customers pay twice for this. |
Move the process-wide configuration snapshot into the gateway so providers cannot diverge during concurrent registration. Environment: Datadog workspace
|
🎯 Code Coverage (details) 🔗 Commit SHA: ffdd4ff | Docs | View more details | Give us feedback! |
Cover telemetry suppression through reflective provider construction, snapshot provider options, and clarify agent compatibility failures. Environment: Datadog workspace
Document the 1.66.0 agent API floor without implying lockstep provider and agent versions. Environment: Datadog workspace
Expose the provider beside the agent in the existing public S3 snapshot job. Environment: Datadog workspace
There was a problem hiding this comment.
More details
The provider option guards exposure, metric, event, and span enrichment paths. Each evaluator reads the shared versioned process snapshot.
🤖 Datadog Autotest · Commit ffdd4ff · What is Autotest? · @DataDog review to ask questions · Any feedback? Reach out in #autotest
There was a problem hiding this comment.
Thanks for addressing this issue so quickly. Long term, we need to get away from requiring dd-openfeature to be in lockstep version-wise with dd-java-agent.
That's one of the general platform requirements to work with single step installer.
But until then, this gives a clear diagnostic indication that will be helpful to support, so I'm approving.
What Does This Do
Adds a provider-level option that allows an OpenFeature domain to evaluate Datadog feature flags without emitting Datadog telemetry.
Motivation
A customer needs to evaluate the same flag in two modes from one application:
Named clients alone share their domain's provider behavior. OpenFeature named domains let the application bind one Datadog provider to the live client and a separate telemetry-free Datadog provider to the peek client while both providers continue to consume the same Datadog flag configuration.
Changes
Provider.Options.telemetryEnabled(boolean), defaulting totrue.Provider.Options; this also fixesnew Provider(new Options()), which previously passed a null timeout unit into initialization.FeatureFlaggingGateway; evaluators read that shared snapshot once per evaluation, while listener callbacks only drive provider state events.Decisions
Provider, matching OpenFeature's domain-to-provider model. A single provider instance keeps one telemetry policy; applications that need both policies register two provider instances under separate domains.Provider.Optionsvalues at provider construction so timeout, unit, and telemetry policy have one consistent binding point.dd-openfeature1.66.0 and later to run withdd-java-agent1.66.0 or later; fail with a compatibility-focused error instead of falling back to per-evaluator configuration that can race.Compatibility
dd-openfeature1.66.0 and later requiresdd-java-agent1.66.0 or later because the shared configuration snapshot is provided by the agent bootstrap API. This is a minimum compatibility floor, not a lockstep requirement: provider and agent versions may differ once the agent meets that minimum. Falling back to the old per-evaluator state would reintroduce the configuration race this PR closes.Configuration Downloads and Billing
The two providers do not create two configuration download streams within the same JVM. This is true for both supported delivery modes:
RemoteConfigServiceImpl, which registers oneFFE_FLAGSlistener on the tracer's sharedConfigurationPoller.AgentlessConfigurationSourcepolling loop. Later provider activations do not initialize another source.FeatureFlaggingGateway. A configuration received by the single source is cached by the gateway and fanned out to every evaluator; evaluating through both clients performs two local evaluations but no per-evaluation network request.Datadog Feature Flags billing is based on Monthly Flag Configuration Requests, not individual evaluations or exposure events. Both Remote Configuration and agentless/CDN requests contribute to server-side Feature Flags billing, but adding the second provider in the same JVM does not add another poller and therefore does not add configuration requests or incremental MFCR usage.
This sharing guarantee is scoped to one JVM. Additional JVMs, processes, or containers can create additional configuration-delivery traffic; deployment-wide MFCR usage still depends on the deployment footprint and polling interval, with Agent Remote Configuration able to consolidate delivery through a shared Agent.
telemetryEnabled(false)suppresses the peek provider's Feature Flags exposure, evaluation metric, EVP flag-evaluation, and span-enrichment signals. It does not disable the shared configuration source or unrelated APM tracing of the surrounding application code.Dogfooding Validation
Revalidated end to end on 2026-09-03 in the private
ffe-dogfoodingapplication against dd-trace-java heada7b19afdf9a0ddc9771655cb0ae0c1b1a369f875and dogfooding main91c8cf523809ccb4facf8bd54a6eb9cac69f1533using this PR's locally builtdd-openfeatureanddd-java-agentartifacts. The full validation was then repeated with the immutable CI-produced dd-java-agent.jar, whose embeddeddd-java-agent.versionis1.66.0-SNAPSHOT~a7b19afdf9; it produced the same PASS results. The private repository is intentionally not linked here; the measured evidence is included below.The Java app registered
liveandpeekOpenFeature domains, pointed agentless delivery at a deterministic request-counting UFC endpoint, and set the polling interval to 60 seconds. Both providers reachedREADYwhile the process reported exactly one livedd-feature-flagging-http-pollerthread. The UFC endpoint recorded exactly one initial configuration request, rather than one request per provider.After clearing the local exposure, evaluation, metric, and trace captures:
liveevaluation produced exactly one exposure, one EVP flag-evaluation row withevaluation_count: 1, one OpenTelemetry evaluation metric, and a root span carryingffe_*enrichment tags.peekevaluation completed and flushed an ordinary application root span. After a 12-second settle window, exposure rows remained at 1, EVP rows remained at 1 with totalevaluation_count: 1, and the OpenTelemetry metric count remained at 1. Zero spans from the peek request carried anyffe_*tag.The reproducible validation completed with:
Additional Notes
Validated against
masterata1b6599a5f9f8d803cfac89fa1c3befe41b8c33fwith:./gradlew :products:feature-flagging:feature-flagging-bootstrap:test :products:feature-flagging:feature-flagging-api:test :products:feature-flagging:feature-flagging-lib:test :products:feature-flagging:feature-flagging-agent:testDD_EXPERIMENTAL_FLAGGING_PROVIDER_SPAN_ENRICHMENT_ENABLED=true ./gradlew --no-build-cache :products:feature-flagging:feature-flagging-api:cleanTest :products:feature-flagging:feature-flagging-api:test./gradlew :products:feature-flagging:feature-flagging-api:spotlessCheck :products:feature-flagging:feature-flagging-api:javadocContributor Checklist
type:andcomp:labels.