Skip to content

Add debug logging for feature flag onboarding - #12897

Closed
ethan-romero-dg wants to merge 9 commits into
masterfrom
ethan.romero/FFL-3401/ffe-debug-logging
Closed

ethan-romero-dg wants to merge 9 commits into
masterfrom
ethan.romero/FFL-3401/ffe-debug-logging

Conversation

@ethan-romero-dg

@ethan-romero-dg ethan-romero-dg commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Improve feature flag onboarding diagnostics following the SDK logging checklist.

  • Log configuration source, retries, usable/rejected flags, configuration identity, environment differences, and readiness/recovery. Remote Config reports flags=missing when a payload has no flags member, separately from an empty configuration (flags=0).
  • Include raw values, defaults, targeting context, metadata, and errors in provider resolution and hook-observed evaluation logs.
  • Report exposure suppression, delivery, and shutdown outcomes, including the final best-effort flush added in Harden Agentless Feature Flags EVP delivery #12477, exposures dropped after shutdown starts, and a shutdown wait that expires before the final delivery returns.
  • Gate provider debug logs on DD_TRACE_DEBUG and the application's SLF4J DEBUG level, and skip raw-value formatting when either is off. When DD_TRACE_DEBUG is on but the application logger blocks DEBUG, the provider logs one WARN naming the missing setting.
  • Isolate provider debug diagnostics from logging and formatting failures during construction, initialization, readiness events, evaluation, and shutdown. Agent-side code logs through the tracer's internal logger and uses plain SLF4J loggers.
  • Document enablement, logger categories, and diagnostic boundaries.

Validation

At 92567d1, rebased on master fab3bc8b10:

  • Provider: 557 unit tests and 26 forked tests passed on JVM 11.
  • Library: 377 tests passed on JVM 8 and 11. Agent: 26 tests passed on JVM 8 and 11; the library and agent coverage gates passed on JVM 8.
  • Spotless and SpotBugs pass for all three modules; JMH sources compile.

The last commit adapts the exposure shutdown diagnostics, README, and two tests to master's drain-and-flush shutdown.

From an earlier revision, not re-run after the logger simplification: agent/provider jars built, and a Java dogfooding app against local agentless/EVP fixtures returned the expected results for eight evaluations in each run. Provider debug lines: 30 with DD_TRACE_DEBUG=true, zero with false or unset, while Logback remained at DEBUG.

Enablement

Set DD_TRACE_DEBUG=true (or -Ddd.trace.debug=true) and enable DEBUG for the application's datadog.trace.api.openfeature logger before constructing the provider. DD_TRACE_DEBUG is read once, when the provider is first constructed; changing it requires restarting the application. Other log levels retain their normal behavior.

Known gaps

  • Existing WARN/ERROR calls in the provider, such as the missing-OpenTelemetry warning at construction and the unknown-feature-destination warning during evaluation, are not isolated from a logging backend that throws. This matches master.
  • A Remote Config payload without flags still marks the provider ready, and evaluations return GENERAL. The flags=missing log makes this visible; rejecting such payloads is a follow-up.

🤖 Generated with Claude Code

FFE reviewer selection

Reviewers: @btthomas, @typotter

Why: Guided and observable onboarding — The PR adds local SDK debug logging for Java OpenFeature provider onboarding, gated on DD_TRACE_DEBUG plus the application's SLF4J DEBUG level, with a WARN when the second… Reviewers come from the matched pod.

🤖 Automated selection.

Selection details

Initiative: project / guided-onboarding

Why this match: The PR adds local SDK debug logging for Java OpenFeature provider onboarding, gated on DD_TRACE_DEBUG plus the application's SLF4J DEBUG level, with a WARN when the second setting is missing. The diff logs provider initialization, readiness, and recovery after configuration returns. It also logs when configuration changes or is unavailable, and Remote Config and agentless configuration receipt (separating flags=missing from flags=0). A new DebugLoggingHook logs evaluation details such as values, defaults, context, and errors. Exposure delivery and shutdown outcomes are logged too, and the README documents how to enable all of this. This matches guided-onboarding: SDK lifecycle and local debug logging that separate 'not observed' from 'failed' across provider readiness, configuration receipt, and evaluation. Exposure transport only gets diagnostic logging, which supports onboarding rather than being transport maintenance.

  • @btthomas — primary (sdk-onboarding): Matched pod member. Existing review coverage preserved.
  • @typotter — floating (sdk-onboarding): Coverage and knowledge sharing. Existing review coverage preserved.

The PR author was excluded. Existing reviewers and teams were preserved.

Debug or fix a problem

Workflow run · Bits Code session

  • Wrong project or pod? Clarify this PR’s purpose, or correct the reviewer registry and publish the change. Sheet edits take effect after publication.
  • Need another reviewer now? Request them in GitHub. This workflow only adds requests and keeps existing reviewers.
  • Stuck beyond five minutes or failed? Inspect the run’s failed step and available Bits session. After fixing the cause, rerun the workflow with this PR URL and dry_run=false. Unchanged completed runs are skipped. If the receipt says needs_reconciliation or a write outcome is uncertain, ask the workflow owner to reconcile GitHub and the receipt before retrying; do not delete the lock blindly.
  • Report a problem: Comment on the reviewer-routing skill PR with this PR URL, the workflow run link, and the expected initiative or reviewer correction.
Evidence and run details

✅ Complete — reviewer coverage verified. The workflow checked pending requests or submitted reviews on the evaluated head.

Generated asynchronously by the FFE reviewer-routing workflow.

Changed paths: products/feature-flagging/feature-flagging-api/src/main/java/datadog/trace/api/openfeature/Provider.java, products/feature-flagging/feature-flagging-api/src/main/java/datadog/trace/api/openfeature/DebugLoggingHook.java, products/feature-flagging/feature-flagging-api/src/main/java/datadog/trace/api/openfeature/DDEvaluator.java, products/feature-flagging/feature-flagging-lib/src/main/java/com/datadog/featureflag/RemoteConfigServiceImpl.java, products/feature-flagging/feature-flagging-lib/src/main/java/com/datadog/featureflag/AgentlessConfigurationSource.java, products/feature-flagging/feature-flagging-lib/src/main/java/com/datadog/featureflag/UniversalFlagConfigParser.java, products/feature-flagging/feature-flagging-lib/src/main/java/com/datadog/featureflag/ExposureWriterImpl.java, products/feature-flagging/feature-flagging-api/README.md

Evaluated head: 92567d114008b8d7383cedf2d4628e0425e6165c

Registry revision: revision:1946aa2a-530e-487a-84a7-83aff44a1089

Pinned skill source: d214cfc65ae154b97d39af5cf32caf48aaec9bf0

Instructions: workflow-injected snapshot (no plugin loaded)

Instruction snapshot SHA-256: f23b2a88c13c0da90e2dd58246a14f257af91e2195c555039bd555b52e6f3516

@ethan-romero-dg ethan-romero-dg added type: feature Enhancements and improvements tag: diagnostics Diagnostics related changes tag: ai generated Largely based on code generated by an AI or LLM comp: openfeature OpenFeature labels Oct 15, 2026
@datadog-official

datadog-official Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

🎯 Code Coverage (details)
• Patch Coverage: 95.37%
• Overall Coverage: 80.66% (+20.84%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 92567d1 | Docs | Give us feedback!

@dd-octo-sts

dd-octo-sts Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

🟢 Java Benchmark SLOs — All performance SLOs passed

Suite Status
Startup 🟢 pass

SLO thresholds are defined here based on automatically generated metrics. A warning is raised when results are within 5% of the threshold.

PR vs. master results
Scenario Candidate master Δ (95% CI of mean)
startup:insecure-bank:iast:Agent 13.96 s 13.90 s [-0.4%; +1.2%] (no difference)
startup:insecure-bank:tracing:Agent 12.83 s 12.96 s [-1.7%; -0.3%] (maybe better)
startup:petclinic:appsec:Agent 17.08 s 16.91 s [+0.1%; +1.9%] (maybe worse)
startup:petclinic:iast:Agent 16.88 s 16.99 s [-1.4%; +0.0%] (no difference)
startup:petclinic:profiling:Agent 16.73 s 16.53 s [+0.3%; +2.1%] (maybe worse)
startup:petclinic:sca:Agent 17.07 s 16.91 s [+0.1%; +1.8%] (maybe worse)
startup:petclinic:tracing:Agent 16.09 s 16.21 s [-1.7%; +0.3%] (no difference)

Commit: 92567d11 · CI Pipeline · Benchmarking Platform UI


Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion.

@ethan-romero-dg
ethan-romero-dg force-pushed the ethan.romero/FFL-3401/ffe-debug-logging branch from ea88d17 to 21bc161 Compare October 15, 2026 19:46
@ethan-romero-dg
ethan-romero-dg marked this pull request as ready for review October 16, 2026 13:24
@ethan-romero-dg
ethan-romero-dg requested review from a team as code owners October 16, 2026 13:24
@ethan-romero-dg
ethan-romero-dg requested review from AlexeyKuznetsov-DD and removed request for a team October 16, 2026 13:24
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-22T17:05:30.638513Z 92567d1 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@datadog-official datadog-official Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bits Code Review: PASS

More details

Runtime behavior remains unverified: the specialist assessed source and existing tests without executing them, and production telemetry was intentionally excluded.

Was this helpful? React 👍 or 👎

Open Bits AI session

🤖 Bits Code Review · Commit 4fb95d7

@ethan-romero-dg
ethan-romero-dg marked this pull request as draft October 21, 2026 14:51
@ethan-romero-dg

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🚀

Reviewed commit: 5d7ef0233f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

ethan-romero-dg and others added 9 commits October 22, 2026 14:33
Replace the DiagnosticLogger wrappers with plain SLF4J loggers. Agent-side
code logs through the tracer's internal logger, so it needs no isolation
wrapper. The provider keeps isolation where an application logging backend
is involved: debug diagnostics go through debugQuietly and DebugLoggingHook,
which cache DD_TRACE_DEBUG and swallow logging and formatting failures.

- Warn at provider construction when DD_TRACE_DEBUG is enabled but the
  application logger blocks DEBUG for datadog.trace.api.openfeature.
- Report flags=missing for Remote Config payloads without a flags member,
  separately from an empty configuration.
- Drop the racy inFlight shutdown field and the duplicate parser summary.
- Update the README to match.

Environment: Datadog workspace

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
DD_TRACE_DEBUG gates the lifecycle, evaluation, and exposure diagnostics,
but a few existing debug messages for rare failures follow only the
application logger level. Only debug diagnostics are isolated from logging
failures; WARN and ERROR messages are not.

Environment: Datadog workspace

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Master now drains pending exposures and makes one final best-effort flush
on shutdown instead of interrupting the processor. Update the shutdown
messages and README to match, and log exposures dropped after shutdown
starts and a shutdown wait that expires before the final delivery returns.
Rework the affected tests to inject the backend directly, since route
selection moved into the backend factory.

Note in Provider that evaluation telemetry opt-out does not apply to local
debug diagnostics.

Environment: Datadog workspace

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ethan-romero-dg
ethan-romero-dg force-pushed the ethan.romero/FFL-3401/ffe-debug-logging branch from 4dbc190 to 92567d1 Compare October 22, 2026 15:27
@ethan-romero-dg

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Delightful!

Reviewed commit: 92567d1140

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@ethan-romero-dg
ethan-romero-dg marked this pull request as ready for review October 22, 2026 17:02

@datadog-official datadog-official Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bits Code Review: PASS

More details

Static review found that the diagnostics preserve existing flag-evaluation and exposure-delivery behavior. Human PR discussion supplied no additional correctness concerns.

Was this helpful? React 👍 or 👎

Open Bits AI session

🤖 Bits Code Review · Commit 92567d1

queue.offer(event);
final boolean queued = queue.offer(event);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Feature Flagging exposure {}", queued ? "queued" : "dropped: queue full");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--- found with local Claude review

P1 — Logging once per exposure on the application thread makes overload worse.

accept() runs on the application thread, and the agent logger writes to System.err synchronously through a PrintStream that takes a lock. The queue (65,536 entries) only fills during an exposure burst. That is exactly when dropped: queue full fires once per event, so every request thread then waits on the stderr lock. The queued line has the same cost for every exposure that isn't cached. With per-user targeting keys the LRU cache keeps evicting entries, so that can be close to once per request.

Suggestion: don't log on the producer path. Count drops (and queued events, if useful) in an AtomicLong, and report dropped=N from the serializer once per flush. Or use RatelimitedLogger.

buffer.add(event);
return true;
}
LOGGER.debug("Feature Flagging exposure suppressed: duplicate");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--- found with local Claude review

P2 — suppressed: duplicate is logged once per duplicate event.

This runs on the serializer thread, but each stderr write slows draining. Under load the queue then fills sooner, which triggers the dropped: queue full path above. Suggestion: count duplicates and add the count to the existing per-flush debug line.

}
try {
log.debug(
"Feature Flagging evaluation observed at finallyAfter: flag={}, targetingKey={}, context={}, defaultValue={}, observedValue={}, variant={}, reason={}, errorCode={}, errorMessage={}, metadata={}",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--- found with local Claude review

P2 — Each evaluation logs the same raw data twice, which adds up to a flood.

With DD_TRACE_DEBUG=true and the app logger at DEBUG (common when root is at DEBUG in dev or staging), one evaluation produces resolution + finallyAfter + the ExposureLoggingHook suppressed/dispatched line. That is 3 lines, or 5–6 when the exposure isn't cached or the evaluation errors. Both resolution and finallyAfter print the full raw context, default value and resolved value, so large object variants and contexts are written twice. At 10–50 flags per request, that is hundreds of lines per request.

Suggestion: keep only one of the two raw lines (the other could log just flag, variant and reason). Also consider capping the length of raw(...) output.

final ErrorCode code,
final String errorMessage,
final boolean observeFullEvaluationData) {
DebugLoggingHook.resolutionError(key, code, errorMessage);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--- found with local Claude review

P2 — The raw errorMessage is logged before the consent filter runs.

The comment just below says these messages "can echo raw evaluation-context values, so they must never reach any consumer". resolutionError logs errorMessage before that filter applies, even when observeFullEvaluationData is off. App logs are often shipped to log management, so context values (emails, user IDs) would leave the process.

Suggestion: pass the consent-filtered message, or explicitly document in the README that debug mode logs raw PII. The same question applies to the raw context in resolution/finallyAfter.

final int status = response.code();
if (response.isSuccessful()) {
LOGGER.debug(
"Feature Flagging agentless configuration poll returned HTTP {}", status);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--- found with local Claude review

P3 — The HTTP status is logged twice on every poll.

shouldRetry(Response) runs for every response, so a normal poll logs both poll returned HTTP 200 and poll attempt 1/3 returned HTTP 200. Suggestion: remove one of them.

public boolean shouldRetry(final Exception exception) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"Feature Flagging agentless configuration poll attempt {}/{} failed: {}",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--- found with local Claude review

P3 — A failed poll logs up to 4 stack traces.

The full stack trace is logged on each attempt here, and again in the final ratelimitedLogger.warn (which logs every time when debug is on). Suggestion: log only the exception class/message per attempt, and keep the stack trace for the final warning.

: raw(result.getFlagMetadata().asUnmodifiableMap()),
config == null || config.environment == null ? null : config.environment.name,
config == null ? null : config.createdAt);
} catch (Throwable failure) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--- found with local Claude review

P3 — catch (Throwable) also swallows OutOfMemoryError and StackOverflowError.

For example, formatting a huge or deeply nested context could throw one of these. formattingFailed would then try to log again, with the throwable. Consider catch (Exception | LinkageError e) so JVM-level errors still propagate.

testImplementation(libs.bundles.mockito)
testImplementation(libs.moshi)
// OpenFeature uses SLF4J 2; the shared Java 8 test binding targets SLF4J 1.7.
testImplementation("ch.qos.logback:logback-classic:1.6.5")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--- found with local Claude review

P3 — Nit: the version is hardcoded (logback-classic:1.6.5). Prefer adding it to the version catalog (libs), like the other test dependencies.

Copy link
Copy Markdown
Contributor Author

Superseded by #13091 (agent-side logs) and #13092 (dd-openfeature SDK diagnostics, stacked on #13091). The provider code this PR logged from was removed in #12844 and now lives in the SDK module. The review feedback here is addressed in #13092.

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

Labels

comp: openfeature OpenFeature tag: ai generated Largely based on code generated by an AI or LLM tag: diagnostics Diagnostics related changes type: feature Enhancements and improvements

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants