From 742586dfe13e82bf75cedea9f077f4918d8b7a74 Mon Sep 17 00:00:00 2001 From: Bruce Bujon Date: Tue, 13 Oct 2026 09:02:59 +0200 Subject: [PATCH 1/6] feat(ffe): Resolve Feature Flags enablement from instrumenter config Move the Feature Flags enablement resolution to InstrumenterConfig and add the FEATURE_FLAGS instrumentation target system, so instrumentations can be gated on the product being enabled. The agent now reads it from Config instead of resolving the settings itself at premain. Co-Authored-By: Claude Opus 5.5 --- .../java/datadog/trace/bootstrap/Agent.java | 44 +------------------ .../trace/agent/tooling/AgentInstaller.java | 3 ++ .../agent/tooling/InstrumenterModule.java | 2 + .../main/java/datadog/trace/api/Config.java | 6 +-- .../datadog/trace/api/InstrumenterConfig.java | 15 +++++++ 5 files changed, 24 insertions(+), 46 deletions(-) diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java index 522b7a0e117..b477468fc63 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java @@ -44,7 +44,6 @@ import datadog.trace.api.config.TraceInstrumentationConfig; import datadog.trace.api.config.TracerConfig; import datadog.trace.api.config.UsmConfig; -import datadog.trace.api.featureflag.config.FeatureFlaggingConfig; import datadog.trace.api.gateway.RequestContextSlot; import datadog.trace.api.gateway.SubscriptionService; import datadog.trace.api.git.EmbeddedGitInfoBuilder; @@ -195,7 +194,7 @@ public boolean isEnabledByDefault() { private static boolean distributedDebuggerEnabled = false; private static boolean agentlessLogSubmissionEnabled = false; private static boolean appLogsCollectionEnabled = false; - private static boolean featureFlaggingEnabled = false; + private static volatile boolean featureFlaggingEnabled = false; private static void safelySetContextClassLoader(ClassLoader classLoader) { try { @@ -284,7 +283,6 @@ public static void start( agentlessLogSubmissionEnabled = isFeatureEnabled(AgentFeature.AGENTLESS_LOG_SUBMISSION); appLogsCollectionEnabled = isFeatureEnabled(AgentFeature.APP_LOGS_COLLECTION); llmObsEnabled = isFeatureEnabled(AgentFeature.LLMOBS); - featureFlaggingEnabled = isFeatureFlaggingEnabled(); // setup writers when llmobs is enabled to accomodate apm and llmobs if (llmObsEnabled) { @@ -1278,6 +1276,7 @@ private static void maybeStartLLMObs(Instrumentation inst, Class scoClass, Ob } private static void maybeStartFeatureFlagging(final Class scoClass, final Object sco) { + featureFlaggingEnabled = Config.get().isFeatureFlaggingProviderEnabled(); if (featureFlaggingEnabled) { StaticEventLogger.begin("Feature Flagging"); @@ -1796,45 +1795,6 @@ private static boolean isFeatureEnabled(AgentFeature feature) { } } - private static boolean isFeatureFlaggingEnabled() { - final Boolean providerEnabled = - featureFlaggingBooleanSetting(FeatureFlaggingConfig.FEATURE_FLAGS_ENABLED); - final String configurationSource = - featureFlaggingSetting(FeatureFlaggingConfig.FEATURE_FLAGS_CONFIGURATION_SOURCE); - final Boolean legacyProviderEnabled = - featureFlaggingBooleanSetting(FeatureFlaggingConfig.EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED); - - return FeatureFlaggingConfig.resolveConfiguration( - providerEnabled, configurationSource, legacyProviderEnabled) - .isEnabled(); - } - - @SuppressFBWarnings( - value = "NP_BOOLEAN_RETURN_NULL", - justification = "A null value preserves the distinction between absent and explicitly false") - private static Boolean featureFlaggingBooleanSetting(final String configKey) { - final String value = featureFlaggingSetting(configKey); - if (value == null) { - return null; - } - return Boolean.parseBoolean(value) || "1".equals(value); - } - - private static String featureFlaggingSetting(final String configKey) { - final String systemProperty = propertyNameToSystemPropertyName(configKey); - String value = SystemProperties.get(systemProperty); - if (value == null) { - value = getStableConfig(FLEET, configKey); - } - if (value == null) { - value = ddGetEnv(systemProperty); - } - if (value == null) { - value = getStableConfig(LOCAL, configKey); - } - return value; - } - /** * @see datadog.trace.api.ProductActivation#fromString(String) */ diff --git a/dd-java-agent/agent-installer/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java b/dd-java-agent/agent-installer/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java index 1dae022b06c..63068a7ceb0 100644 --- a/dd-java-agent/agent-installer/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java +++ b/dd-java-agent/agent-installer/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java @@ -347,6 +347,9 @@ public static Set getEnabledSystems() { if (cfg.isLlmObsEnabled()) { enabledSystems.add(InstrumenterModule.TargetSystem.LLMOBS); } + if (cfg.isFeatureFlaggingEnabled()) { + enabledSystems.add(InstrumenterModule.TargetSystem.FEATURE_FLAGS); + } return enabledSystems; } diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/InstrumenterModule.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/InstrumenterModule.java index 6baeca12b62..ab2c4d78637 100644 --- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/InstrumenterModule.java +++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/InstrumenterModule.java @@ -44,6 +44,7 @@ public abstract class InstrumenterModule implements Instrumenter { *
  • {@link TargetSystem#USM usm} *
  • {@link TargetSystem#CONTEXT_TRACKING context-tracking} *
  • {@link TargetSystem#RASP rasp} + *
  • {@link TargetSystem#FEATURE_FLAGS feature-flags} * */ public enum TargetSystem { @@ -56,6 +57,7 @@ public enum TargetSystem { LLMOBS, CONTEXT_TRACKING, RASP, + FEATURE_FLAGS, } private static final Logger log = LoggerFactory.getLogger(InstrumenterModule.class); diff --git a/internal-api/src/main/java/datadog/trace/api/Config.java b/internal-api/src/main/java/datadog/trace/api/Config.java index b1707b77a71..f43d595754a 100644 --- a/internal-api/src/main/java/datadog/trace/api/Config.java +++ b/internal-api/src/main/java/datadog/trace/api/Config.java @@ -1258,7 +1258,6 @@ public static String getHostName() { private final int remoteConfigMaxExtraServices; - private final boolean featureFlaggingProviderEnabled; private final String featureFlaggingConfigurationSource; private final String featureFlaggingConfigurationSourceAgentlessBaseUrl; private final int featureFlaggingConfigurationSourcePollIntervalSeconds; @@ -2976,7 +2975,6 @@ PROFILING_DATADOG_PROFILER_ENABLED, isDatadogProfilerSafeInCurrentEnvironment()) "Unsupported Feature Flagging configuration source; provider disabled: '{}'", resolvedFeatureFlaggingConfiguration.getSource()); } - featureFlaggingProviderEnabled = resolvedFeatureFlaggingConfiguration.isEnabled(); featureFlaggingConfigurationSource = resolvedFeatureFlaggingConfiguration.getSource(); featureFlaggingConfigurationSourceAgentlessBaseUrl = configProvider.getStringNotEmpty( @@ -4940,7 +4938,7 @@ public int getRemoteConfigMaxExtraServices() { } public boolean isFeatureFlaggingProviderEnabled() { - return featureFlaggingProviderEnabled; + return instrumenterConfig.isFeatureFlaggingEnabled(); } public String getFeatureFlaggingConfigurationSource() { @@ -6916,7 +6914,7 @@ public String toString() { + ", remoteConfigIntegrityCheckEnabled=" + remoteConfigIntegrityCheckEnabled + ", featureFlaggingProviderEnabled=" - + featureFlaggingProviderEnabled + + isFeatureFlaggingProviderEnabled() + ", featureFlaggingConfigurationSource=" + featureFlaggingConfigurationSource + ", featureFlaggingConfigurationSourceAgentlessBaseUrl=" diff --git a/internal-api/src/main/java/datadog/trace/api/InstrumenterConfig.java b/internal-api/src/main/java/datadog/trace/api/InstrumenterConfig.java index 76b65d43036..6af92039fe3 100644 --- a/internal-api/src/main/java/datadog/trace/api/InstrumenterConfig.java +++ b/internal-api/src/main/java/datadog/trace/api/InstrumenterConfig.java @@ -98,6 +98,10 @@ import static datadog.trace.api.config.TraceInstrumentationConfig.UNSAFE_CLASS_INJECTION; import static datadog.trace.api.config.TraceInstrumentationConfig.VISITOR_CLASS_PARSING; import static datadog.trace.api.config.UsmConfig.USM_ENABLED; +import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED; +import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.FEATURE_FLAGS_CONFIGURATION_SOURCE; +import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.FEATURE_FLAGS_ENABLED; +import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.resolveConfiguration; import static datadog.trace.util.CollectionUtils.tryMakeImmutableList; import static datadog.trace.util.CollectionUtils.tryMakeImmutableSet; import static java.util.Collections.singletonList; @@ -175,6 +179,7 @@ public class InstrumenterConfig { private final boolean usmEnabled; private final boolean telemetryEnabled; private final boolean llmObsEnabled; + private final boolean featureFlaggingEnabled; private final String traceExtensionsPath; @@ -276,6 +281,12 @@ private InstrumenterConfig() { CODE_ORIGIN_FOR_SPANS_INTERFACE_SUPPORT, DEFAULT_CODE_ORIGIN_FOR_SPANS_INTERFACE_SUPPORT); traceEnabled = configProvider.getBoolean(TRACE_ENABLED, DEFAULT_TRACE_ENABLED); + featureFlaggingEnabled = + resolveConfiguration( + configProvider.getBoolean(FEATURE_FLAGS_ENABLED), + configProvider.getString(FEATURE_FLAGS_CONFIGURATION_SOURCE), + configProvider.getBoolean(EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED)) + .isEnabled(); traceOtelEnabled = configProvider.getBoolean(TRACE_OTEL_ENABLED, DEFAULT_TRACE_OTEL_ENABLED); metricsOtelEnabled = configProvider.getBoolean(METRICS_OTEL_ENABLED, DEFAULT_METRICS_OTEL_ENABLED); @@ -491,6 +502,10 @@ public boolean isTraceEnabled() { return traceEnabled; } + public boolean isFeatureFlaggingEnabled() { + return featureFlaggingEnabled; + } + public boolean isTraceOtelEnabled() { return traceOtelEnabled; } From dc341190ad255cddb45f2619ccbf7fd5bc453926 Mon Sep 17 00:00:00 2001 From: Bruce Bujon Date: Wed, 14 Oct 2026 06:51:50 +0200 Subject: [PATCH 2/6] fix(ffe): Detect Feature Flags activation like other products Resolve the Feature Flags product activation in Agent.start() with the other products, so it no longer needs to be volatile. --- .../src/main/java/datadog/trace/bootstrap/Agent.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java index b477468fc63..25d3ddff60b 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java @@ -44,6 +44,7 @@ import datadog.trace.api.config.TraceInstrumentationConfig; import datadog.trace.api.config.TracerConfig; import datadog.trace.api.config.UsmConfig; +import datadog.trace.api.featureflag.config.FeatureFlaggingConfig; import datadog.trace.api.gateway.RequestContextSlot; import datadog.trace.api.gateway.SubscriptionService; import datadog.trace.api.git.EmbeddedGitInfoBuilder; @@ -134,7 +135,9 @@ private enum AgentFeature { AGENTLESS_LOG_SUBMISSION(GeneralConfig.AGENTLESS_LOG_SUBMISSION_ENABLED, false), APP_LOGS_COLLECTION(GeneralConfig.APP_LOGS_COLLECTION_ENABLED, false), LLMOBS(LlmObsConfig.LLMOBS_ENABLED, false), - LLMOBS_AGENTLESS(LlmObsConfig.LLMOBS_AGENTLESS_ENABLED, false); + LLMOBS_AGENTLESS(LlmObsConfig.LLMOBS_AGENTLESS_ENABLED, false), + FEATURE_FLAGS(FeatureFlaggingConfig.FEATURE_FLAGS_ENABLED, true), + DEPRECATED_FEATURE_FLAGS(FeatureFlaggingConfig.EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED, true); private final String configKey; private final String systemProp; @@ -194,7 +197,7 @@ public boolean isEnabledByDefault() { private static boolean distributedDebuggerEnabled = false; private static boolean agentlessLogSubmissionEnabled = false; private static boolean appLogsCollectionEnabled = false; - private static volatile boolean featureFlaggingEnabled = false; + private static boolean featureFlaggingEnabled = false; private static void safelySetContextClassLoader(ClassLoader classLoader) { try { @@ -283,6 +286,9 @@ public static void start( agentlessLogSubmissionEnabled = isFeatureEnabled(AgentFeature.AGENTLESS_LOG_SUBMISSION); appLogsCollectionEnabled = isFeatureEnabled(AgentFeature.APP_LOGS_COLLECTION); llmObsEnabled = isFeatureEnabled(AgentFeature.LLMOBS); + featureFlaggingEnabled = + isFeatureEnabled(AgentFeature.FEATURE_FLAGS) + && isFeatureEnabled(AgentFeature.DEPRECATED_FEATURE_FLAGS); // setup writers when llmobs is enabled to accomodate apm and llmobs if (llmObsEnabled) { @@ -1276,7 +1282,6 @@ private static void maybeStartLLMObs(Instrumentation inst, Class scoClass, Ob } private static void maybeStartFeatureFlagging(final Class scoClass, final Object sco) { - featureFlaggingEnabled = Config.get().isFeatureFlaggingProviderEnabled(); if (featureFlaggingEnabled) { StaticEventLogger.begin("Feature Flagging"); From 3fa4ce1f8e8b0c0baf87d77a4cd4bd972c31ca93 Mon Sep 17 00:00:00 2001 From: Bruce Bujon Date: Wed, 14 Oct 2026 07:24:57 +0200 Subject: [PATCH 3/6] refactor(ffe): Resolve Feature Flags configuration in InstrumenterConfig Resolve the product switch and configuration source once in InstrumenterConfig, with Config delegating to it, instead of resolving them twice through feature-flagging-config. Co-Authored-By: Claude Opus 5.5 --- .../main/java/datadog/trace/api/Config.java | 30 ++------- .../datadog/trace/api/InstrumenterConfig.java | 58 +++++++++++++++-- ...InstrumenterConfigFeatureFlaggingTest.java | 62 ++++++++++++++++++ .../config/FeatureFlaggingConfig.java | 64 ------------------- .../config/FeatureFlaggingConfigTest.java | 55 ---------------- 5 files changed, 120 insertions(+), 149 deletions(-) create mode 100644 internal-api/src/test/java/datadog/trace/api/InstrumenterConfigFeatureFlaggingTest.java delete mode 100644 products/feature-flagging/feature-flagging-config/src/test/java/datadog/trace/api/featureflag/config/FeatureFlaggingConfigTest.java diff --git a/internal-api/src/main/java/datadog/trace/api/Config.java b/internal-api/src/main/java/datadog/trace/api/Config.java index f43d595754a..f28b9fe6f1c 100644 --- a/internal-api/src/main/java/datadog/trace/api/Config.java +++ b/internal-api/src/main/java/datadog/trace/api/Config.java @@ -750,8 +750,6 @@ import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_POLL_INTERVAL_SECONDS; import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_REQUEST_TIMEOUT_SECONDS; import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.FEATURE_FLAGS_ENABLED; -import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.isSupportedConfigurationSource; -import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.resolveConfiguration; import static datadog.trace.api.telemetry.LogCollector.SEND_TELEMETRY; import static datadog.trace.bootstrap.instrumentation.api.WriterConstants.MULTI_WRITER_TYPE; import static datadog.trace.bootstrap.instrumentation.api.WriterConstants.OTLP_WRITER_TYPE; @@ -769,7 +767,6 @@ import datadog.trace.api.config.OtlpConfig; import datadog.trace.api.config.ProfilingConfig; import datadog.trace.api.config.TracerConfig; -import datadog.trace.api.featureflag.config.FeatureFlaggingConfig; import datadog.trace.api.iast.IastContext; import datadog.trace.api.iast.IastDetectionMode; import datadog.trace.api.iast.telemetry.Verbosity; @@ -1258,7 +1255,6 @@ public static String getHostName() { private final int remoteConfigMaxExtraServices; - private final String featureFlaggingConfigurationSource; private final String featureFlaggingConfigurationSourceAgentlessBaseUrl; private final int featureFlaggingConfigurationSourcePollIntervalSeconds; private final int featureFlaggingConfigurationSourceRequestTimeoutSeconds; @@ -2950,32 +2946,20 @@ PROFILING_DATADOG_PROFILER_ENABLED, isDatadogProfilerSafeInCurrentEnvironment()) configProvider.getInteger( REMOTE_CONFIG_MAX_EXTRA_SERVICES, DEFAULT_REMOTE_CONFIG_MAX_EXTRA_SERVICES); - final Boolean configuredFeatureFlaggingProviderEnabled = - configProvider.getBoolean(FEATURE_FLAGS_ENABLED); - final Boolean legacyFeatureFlaggingProviderEnabled = - configProvider.getBoolean(EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED); - final String configuredFeatureFlaggingConfigurationSource = - configProvider.isSet(FEATURE_FLAGS_CONFIGURATION_SOURCE) - ? configProvider.getString(FEATURE_FLAGS_CONFIGURATION_SOURCE) - : null; - final FeatureFlaggingConfig.Resolution resolvedFeatureFlaggingConfiguration = - resolveConfiguration( - configuredFeatureFlaggingProviderEnabled, - configuredFeatureFlaggingConfigurationSource, - legacyFeatureFlaggingProviderEnabled); - if (legacyFeatureFlaggingProviderEnabled != null) { + if (configProvider.getBoolean(EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED) != null) { log.warn( "Setting {} is deprecated. Use {} and {} instead.", EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED, FEATURE_FLAGS_ENABLED, FEATURE_FLAGS_CONFIGURATION_SOURCE); } - if (!isSupportedConfigurationSource(configuredFeatureFlaggingConfigurationSource)) { + final String featureFlaggingConfigurationSource = + instrumenterConfig.getFeatureFlaggingConfigurationSource(); + if (!InstrumenterConfig.isSupportedFeatureFlaggingSource(featureFlaggingConfigurationSource)) { log.warn( "Unsupported Feature Flagging configuration source; provider disabled: '{}'", - resolvedFeatureFlaggingConfiguration.getSource()); + featureFlaggingConfigurationSource); } - featureFlaggingConfigurationSource = resolvedFeatureFlaggingConfiguration.getSource(); featureFlaggingConfigurationSourceAgentlessBaseUrl = configProvider.getStringNotEmpty( FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL, null); @@ -4942,7 +4926,7 @@ public boolean isFeatureFlaggingProviderEnabled() { } public String getFeatureFlaggingConfigurationSource() { - return featureFlaggingConfigurationSource; + return instrumenterConfig.getFeatureFlaggingConfigurationSource(); } public String getFeatureFlaggingConfigurationSourceAgentlessBaseUrl() { @@ -6916,7 +6900,7 @@ public String toString() { + ", featureFlaggingProviderEnabled=" + isFeatureFlaggingProviderEnabled() + ", featureFlaggingConfigurationSource=" - + featureFlaggingConfigurationSource + + getFeatureFlaggingConfigurationSource() + ", featureFlaggingConfigurationSourceAgentlessBaseUrl=" + featureFlaggingConfigurationSourceAgentlessBaseUrl + ", featureFlaggingConfigurationSourcePollIntervalSeconds=" diff --git a/internal-api/src/main/java/datadog/trace/api/InstrumenterConfig.java b/internal-api/src/main/java/datadog/trace/api/InstrumenterConfig.java index 6af92039fe3..d004b6c7ce6 100644 --- a/internal-api/src/main/java/datadog/trace/api/InstrumenterConfig.java +++ b/internal-api/src/main/java/datadog/trace/api/InstrumenterConfig.java @@ -98,13 +98,16 @@ import static datadog.trace.api.config.TraceInstrumentationConfig.UNSAFE_CLASS_INJECTION; import static datadog.trace.api.config.TraceInstrumentationConfig.VISITOR_CLASS_PARSING; import static datadog.trace.api.config.UsmConfig.USM_ENABLED; +import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.CONFIGURATION_SOURCE_AGENTLESS; +import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.CONFIGURATION_SOURCE_REMOTE_CONFIG; import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED; import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.FEATURE_FLAGS_CONFIGURATION_SOURCE; import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.FEATURE_FLAGS_ENABLED; -import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.resolveConfiguration; import static datadog.trace.util.CollectionUtils.tryMakeImmutableList; import static datadog.trace.util.CollectionUtils.tryMakeImmutableSet; +import static java.lang.Boolean.FALSE; import static java.util.Collections.singletonList; +import static java.util.Locale.ROOT; import datadog.environment.JavaVirtualMachine; import datadog.trace.api.profiling.ProfilingEnablement; @@ -180,6 +183,7 @@ public class InstrumenterConfig { private final boolean telemetryEnabled; private final boolean llmObsEnabled; private final boolean featureFlaggingEnabled; + private final String featureFlaggingConfigurationSource; private final String traceExtensionsPath; @@ -281,12 +285,26 @@ private InstrumenterConfig() { CODE_ORIGIN_FOR_SPANS_INTERFACE_SUPPORT, DEFAULT_CODE_ORIGIN_FOR_SPANS_INTERFACE_SUPPORT); traceEnabled = configProvider.getBoolean(TRACE_ENABLED, DEFAULT_TRACE_ENABLED); - featureFlaggingEnabled = - resolveConfiguration( - configProvider.getBoolean(FEATURE_FLAGS_ENABLED), - configProvider.getString(FEATURE_FLAGS_CONFIGURATION_SOURCE), - configProvider.getBoolean(EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED)) - .isEnabled(); + // The product switch wins, then an explicit source, then the deprecated experimental toggle. + final Boolean featureFlagsEnabled = configProvider.getBoolean(FEATURE_FLAGS_ENABLED); + final String featureFlagsSource = + normalizeFeatureFlaggingSource( + configProvider.getString(FEATURE_FLAGS_CONFIGURATION_SOURCE)); + final Boolean legacyFeatureFlagsEnabled = + configProvider.getBoolean(EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED); + if (FALSE.equals(featureFlagsEnabled) || featureFlagsSource != null) { + featureFlaggingEnabled = + !FALSE.equals(featureFlagsEnabled) + && isSupportedFeatureFlaggingSource(featureFlagsSource); + featureFlaggingConfigurationSource = featureFlagsSource; + } else if (legacyFeatureFlagsEnabled != null) { + featureFlaggingEnabled = legacyFeatureFlagsEnabled; + featureFlaggingConfigurationSource = + legacyFeatureFlagsEnabled ? CONFIGURATION_SOURCE_REMOTE_CONFIG : null; + } else { + featureFlaggingEnabled = true; + featureFlaggingConfigurationSource = CONFIGURATION_SOURCE_AGENTLESS; + } traceOtelEnabled = configProvider.getBoolean(TRACE_OTEL_ENABLED, DEFAULT_TRACE_OTEL_ENABLED); metricsOtelEnabled = configProvider.getBoolean(METRICS_OTEL_ENABLED, DEFAULT_METRICS_OTEL_ENABLED); @@ -506,6 +524,32 @@ public boolean isFeatureFlaggingEnabled() { return featureFlaggingEnabled; } + /** + * @return the normalized Feature Flags configuration source, unsupported values included, or + * {@code null} when none is configured. + */ + public String getFeatureFlaggingConfigurationSource() { + return featureFlaggingConfigurationSource; + } + + /** + * @param source the normalized configuration source. + * @return whether the source is absent or supported. + */ + static boolean isSupportedFeatureFlaggingSource(final String source) { + return source == null + || CONFIGURATION_SOURCE_AGENTLESS.equals(source) + || CONFIGURATION_SOURCE_REMOTE_CONFIG.equals(source); + } + + private static String normalizeFeatureFlaggingSource(final String source) { + if (source == null) { + return null; + } + final String normalized = source.trim().toLowerCase(ROOT); + return normalized.isEmpty() ? null : normalized; + } + public boolean isTraceOtelEnabled() { return traceOtelEnabled; } diff --git a/internal-api/src/test/java/datadog/trace/api/InstrumenterConfigFeatureFlaggingTest.java b/internal-api/src/test/java/datadog/trace/api/InstrumenterConfigFeatureFlaggingTest.java new file mode 100644 index 00000000000..1d0673e2e87 --- /dev/null +++ b/internal-api/src/test/java/datadog/trace/api/InstrumenterConfigFeatureFlaggingTest.java @@ -0,0 +1,62 @@ +package datadog.trace.api; + +import static datadog.trace.api.InstrumenterConfig.isSupportedFeatureFlaggingSource; +import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED; +import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.FEATURE_FLAGS_CONFIGURATION_SOURCE; +import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.FEATURE_FLAGS_ENABLED; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import datadog.trace.bootstrap.config.provider.ConfigProvider; +import java.util.Properties; +import org.tabletest.junit.TableTest; + +class InstrumenterConfigFeatureFlaggingTest { + + @TableTest({ + "scenario | enabled | source | legacy | resolvedEnabled | resolvedSource ", + "defaults | | | | true | 'agentless' ", + "blank source | | ' ' | | true | 'agentless' ", + "legacy enabled | | | 'true' | true | 'remote_config'", + "legacy disabled | | | 'false' | false | ", + "source wins over legacy | | 'agentless' | 'true' | true | 'agentless' ", + "normalized source | | ' remote_CONFIG ' | 'false' | true | 'remote_config'", + "disabled wins over source | 'false' | 'agentless' | 'true' | false | 'agentless' ", + "disabled without source | 'false' | | | false | ", + "unsupported source | | 'invalid' | | false | 'invalid' ", + "unsupported normalized | | ' OFFLINE ' | 'true' | false | 'offline' " + }) + void resolvesFeatureFlagging( + final String enabled, + final String source, + final String legacy, + final boolean resolvedEnabled, + final String resolvedSource) { + final Properties properties = new Properties(); + if (enabled != null) { + properties.setProperty(FEATURE_FLAGS_ENABLED, enabled); + } + if (source != null) { + properties.setProperty(FEATURE_FLAGS_CONFIGURATION_SOURCE, source); + } + if (legacy != null) { + properties.setProperty(EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED, legacy); + } + + final InstrumenterConfig config = + new InstrumenterConfig(ConfigProvider.withPropertiesOverride(properties)); + + assertEquals(resolvedEnabled, config.isFeatureFlaggingEnabled()); + assertEquals(resolvedSource, config.getFeatureFlaggingConfigurationSource()); + } + + @TableTest({ + "scenario | source | supported", + "absent | | true ", + "agentless | 'agentless' | true ", + "remote config | 'remote_config' | true ", + "unsupported | 'offline' | false " + }) + void recognizesSupportedSources(final String source, final boolean supported) { + assertEquals(supported, isSupportedFeatureFlaggingSource(source)); + } +} diff --git a/products/feature-flagging/feature-flagging-config/src/main/java/datadog/trace/api/featureflag/config/FeatureFlaggingConfig.java b/products/feature-flagging/feature-flagging-config/src/main/java/datadog/trace/api/featureflag/config/FeatureFlaggingConfig.java index 1edfd5bcb26..c772051e389 100644 --- a/products/feature-flagging/feature-flagging-config/src/main/java/datadog/trace/api/featureflag/config/FeatureFlaggingConfig.java +++ b/products/feature-flagging/feature-flagging-config/src/main/java/datadog/trace/api/featureflag/config/FeatureFlaggingConfig.java @@ -5,12 +5,6 @@ public class FeatureFlaggingConfig { public static final String CONFIGURATION_SOURCE_AGENTLESS = "agentless"; public static final String CONFIGURATION_SOURCE_REMOTE_CONFIG = "remote_config"; - private static final Resolution DISABLED_RESOLUTION = new Resolution(false, null); - private static final Resolution AGENTLESS_CONFIGURATION = - new Resolution(true, CONFIGURATION_SOURCE_AGENTLESS); - private static final Resolution REMOTE_CONFIG_CONFIGURATION = - new Resolution(true, CONFIGURATION_SOURCE_REMOTE_CONFIG); - public static final String FEATURE_FLAGS_ENABLED = "feature.flags.enabled"; public static final String EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED = "experimental.flagging.provider.enabled"; @@ -39,62 +33,4 @@ public class FeatureFlaggingConfig { "feature.flags.configuration.source.agentless.poll.interval.seconds"; public static final String FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_REQUEST_TIMEOUT_SECONDS = "feature.flags.configuration.source.agentless.request.timeout.seconds"; - - public static Resolution resolveConfiguration( - final Boolean providerEnabled, - final String explicitSource, - final Boolean legacyProviderEnabled) { - final String normalizedSource = normalizeConfigurationSource(explicitSource); - if (Boolean.FALSE.equals(providerEnabled)) { - return new Resolution(false, normalizedSource); - } - if (normalizedSource != null) { - if (CONFIGURATION_SOURCE_AGENTLESS.equals(normalizedSource)) { - return AGENTLESS_CONFIGURATION; - } - if (CONFIGURATION_SOURCE_REMOTE_CONFIG.equals(normalizedSource)) { - return REMOTE_CONFIG_CONFIGURATION; - } - return new Resolution(false, normalizedSource); - } - if (legacyProviderEnabled != null) { - return legacyProviderEnabled ? REMOTE_CONFIG_CONFIGURATION : DISABLED_RESOLUTION; - } - return AGENTLESS_CONFIGURATION; - } - - public static boolean isSupportedConfigurationSource(final String source) { - final String normalizedSource = normalizeConfigurationSource(source); - return normalizedSource == null - || CONFIGURATION_SOURCE_AGENTLESS.equals(normalizedSource) - || CONFIGURATION_SOURCE_REMOTE_CONFIG.equals(normalizedSource); - } - - private static String normalizeConfigurationSource(final String source) { - if (source == null) { - return null; - } - final String normalized = source.trim().toLowerCase(java.util.Locale.ROOT); - return normalized.isEmpty() ? null : normalized; - } - - public static final class Resolution { - private final boolean enabled; - private final String source; - - private Resolution(final boolean enabled, final String source) { - this.enabled = enabled; - this.source = source; - } - - public boolean isEnabled() { - return enabled; - } - - public String getSource() { - return source; - } - } - - private FeatureFlaggingConfig() {} } diff --git a/products/feature-flagging/feature-flagging-config/src/test/java/datadog/trace/api/featureflag/config/FeatureFlaggingConfigTest.java b/products/feature-flagging/feature-flagging-config/src/test/java/datadog/trace/api/featureflag/config/FeatureFlaggingConfigTest.java deleted file mode 100644 index 16283869d71..00000000000 --- a/products/feature-flagging/feature-flagging-config/src/test/java/datadog/trace/api/featureflag/config/FeatureFlaggingConfigTest.java +++ /dev/null @@ -1,55 +0,0 @@ -package datadog.trace.api.featureflag.config; - -import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.CONFIGURATION_SOURCE_AGENTLESS; -import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.CONFIGURATION_SOURCE_REMOTE_CONFIG; -import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.isSupportedConfigurationSource; -import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.resolveConfiguration; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; - -class FeatureFlaggingConfigTest { - - @Test - void appliesConfigurationPrecedence() { - assertResolution(true, CONFIGURATION_SOURCE_AGENTLESS, null, null, null); - assertResolution(true, CONFIGURATION_SOURCE_AGENTLESS, null, " ", null); - assertResolution(true, CONFIGURATION_SOURCE_REMOTE_CONFIG, null, null, true); - assertResolution(false, null, null, null, false); - assertResolution(true, CONFIGURATION_SOURCE_AGENTLESS, null, "agentless", true); - assertResolution(true, CONFIGURATION_SOURCE_REMOTE_CONFIG, null, " remote_CONFIG ", false); - assertResolution(false, CONFIGURATION_SOURCE_AGENTLESS, false, "agentless", true); - assertResolution(false, "invalid", null, "invalid", null); - assertResolution(false, "offline", null, " OFFLINE ", true); - } - - @Test - void recognizesSupportedExplicitSources() { - assertTrue(isSupportedConfigurationSource(null)); - assertTrue(isSupportedConfigurationSource(" ")); - assertTrue(isSupportedConfigurationSource("agentless")); - assertTrue(isSupportedConfigurationSource(" REMOTE_CONFIG ")); - assertFalse(isSupportedConfigurationSource("invalid")); - assertFalse(isSupportedConfigurationSource("offline")); - } - - private static void assertResolution( - final boolean enabled, - final String source, - final Boolean providerEnabled, - final String explicitSource, - final Boolean legacyProviderEnabled) { - final FeatureFlaggingConfig.Resolution resolution = - resolveConfiguration(providerEnabled, explicitSource, legacyProviderEnabled); - - assertEquals(enabled, resolution.isEnabled()); - if (source == null) { - assertNull(resolution.getSource()); - } else { - assertEquals(source, resolution.getSource()); - } - } -} From 3ecd8ac8b2d200f126b62e598accdcd3f5b789f2 Mon Sep 17 00:00:00 2001 From: Bruce Bujon Date: Wed, 14 Oct 2026 07:55:46 +0200 Subject: [PATCH 4/6] refactor(ffe): Parse the Feature Flags configuration source as an enum Replace the hand-written source normalization with the agent enum config parsing. A blank source is unset, and an unknown or padded one is invalid. Config keeps exposing the source as a string for the existing feature flagging code. Co-Authored-By: Claude Opus 5.5 --- .../main/java/datadog/trace/api/Config.java | 10 ++-- .../trace/api/FeatureFlaggingSource.java | 13 +++++ .../datadog/trace/api/InstrumenterConfig.java | 54 ++++++++----------- .../java/datadog/trace/api/ConfigTest.java | 22 ++++---- ...InstrumenterConfigFeatureFlaggingTest.java | 37 +++++-------- 5 files changed, 64 insertions(+), 72 deletions(-) create mode 100644 internal-api/src/main/java/datadog/trace/api/FeatureFlaggingSource.java diff --git a/internal-api/src/main/java/datadog/trace/api/Config.java b/internal-api/src/main/java/datadog/trace/api/Config.java index f28b9fe6f1c..3471750c347 100644 --- a/internal-api/src/main/java/datadog/trace/api/Config.java +++ b/internal-api/src/main/java/datadog/trace/api/Config.java @@ -2953,12 +2953,11 @@ PROFILING_DATADOG_PROFILER_ENABLED, isDatadogProfilerSafeInCurrentEnvironment()) FEATURE_FLAGS_ENABLED, FEATURE_FLAGS_CONFIGURATION_SOURCE); } - final String featureFlaggingConfigurationSource = - instrumenterConfig.getFeatureFlaggingConfigurationSource(); - if (!InstrumenterConfig.isSupportedFeatureFlaggingSource(featureFlaggingConfigurationSource)) { + if (instrumenterConfig.getFeatureFlaggingConfigurationSource() + == FeatureFlaggingSource.INVALID) { log.warn( "Unsupported Feature Flagging configuration source; provider disabled: '{}'", - featureFlaggingConfigurationSource); + configProvider.getString(FEATURE_FLAGS_CONFIGURATION_SOURCE)); } featureFlaggingConfigurationSourceAgentlessBaseUrl = configProvider.getStringNotEmpty( @@ -4926,7 +4925,8 @@ public boolean isFeatureFlaggingProviderEnabled() { } public String getFeatureFlaggingConfigurationSource() { - return instrumenterConfig.getFeatureFlaggingConfigurationSource(); + final FeatureFlaggingSource source = instrumenterConfig.getFeatureFlaggingConfigurationSource(); + return source == FeatureFlaggingSource.UNSET ? null : source.name().toLowerCase(Locale.ROOT); } public String getFeatureFlaggingConfigurationSourceAgentlessBaseUrl() { diff --git a/internal-api/src/main/java/datadog/trace/api/FeatureFlaggingSource.java b/internal-api/src/main/java/datadog/trace/api/FeatureFlaggingSource.java new file mode 100644 index 00000000000..bf8c9921822 --- /dev/null +++ b/internal-api/src/main/java/datadog/trace/api/FeatureFlaggingSource.java @@ -0,0 +1,13 @@ +package datadog.trace.api; + +/** The Feature Flags configuration source. */ +public enum FeatureFlaggingSource { + /** No source is configured. */ + UNSET, + /** The configured source is not supported. */ + INVALID, + /** The flag configuration is polled from the Datadog CDN. */ + AGENTLESS, + /** The flag configuration is received through Remote Configuration. */ + REMOTE_CONFIG +} diff --git a/internal-api/src/main/java/datadog/trace/api/InstrumenterConfig.java b/internal-api/src/main/java/datadog/trace/api/InstrumenterConfig.java index d004b6c7ce6..3c92d01665b 100644 --- a/internal-api/src/main/java/datadog/trace/api/InstrumenterConfig.java +++ b/internal-api/src/main/java/datadog/trace/api/InstrumenterConfig.java @@ -29,6 +29,10 @@ import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_OTEL_ENABLED; import static datadog.trace.api.ConfigDefaults.DEFAULT_USM_ENABLED; import static datadog.trace.api.ConfigDefaults.DEFAULT_WEBSOCKET_MESSAGES_ENABLED; +import static datadog.trace.api.FeatureFlaggingSource.AGENTLESS; +import static datadog.trace.api.FeatureFlaggingSource.INVALID; +import static datadog.trace.api.FeatureFlaggingSource.REMOTE_CONFIG; +import static datadog.trace.api.FeatureFlaggingSource.UNSET; import static datadog.trace.api.config.AppSecConfig.API_SECURITY_ENDPOINT_COLLECTION_ENABLED; import static datadog.trace.api.config.AppSecConfig.APPSEC_ENABLED; import static datadog.trace.api.config.AppSecConfig.APPSEC_RASP_ENABLED; @@ -98,8 +102,6 @@ import static datadog.trace.api.config.TraceInstrumentationConfig.UNSAFE_CLASS_INJECTION; import static datadog.trace.api.config.TraceInstrumentationConfig.VISITOR_CLASS_PARSING; import static datadog.trace.api.config.UsmConfig.USM_ENABLED; -import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.CONFIGURATION_SOURCE_AGENTLESS; -import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.CONFIGURATION_SOURCE_REMOTE_CONFIG; import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED; import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.FEATURE_FLAGS_CONFIGURATION_SOURCE; import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.FEATURE_FLAGS_ENABLED; @@ -107,7 +109,6 @@ import static datadog.trace.util.CollectionUtils.tryMakeImmutableSet; import static java.lang.Boolean.FALSE; import static java.util.Collections.singletonList; -import static java.util.Locale.ROOT; import datadog.environment.JavaVirtualMachine; import datadog.trace.api.profiling.ProfilingEnablement; @@ -183,7 +184,7 @@ public class InstrumenterConfig { private final boolean telemetryEnabled; private final boolean llmObsEnabled; private final boolean featureFlaggingEnabled; - private final String featureFlaggingConfigurationSource; + private final FeatureFlaggingSource featureFlaggingConfigurationSource; private final String traceExtensionsPath; @@ -287,23 +288,18 @@ private InstrumenterConfig() { traceEnabled = configProvider.getBoolean(TRACE_ENABLED, DEFAULT_TRACE_ENABLED); // The product switch wins, then an explicit source, then the deprecated experimental toggle. final Boolean featureFlagsEnabled = configProvider.getBoolean(FEATURE_FLAGS_ENABLED); - final String featureFlagsSource = - normalizeFeatureFlaggingSource( - configProvider.getString(FEATURE_FLAGS_CONFIGURATION_SOURCE)); + final FeatureFlaggingSource featureFlagsSource = featureFlaggingSource(configProvider); final Boolean legacyFeatureFlagsEnabled = configProvider.getBoolean(EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED); - if (FALSE.equals(featureFlagsEnabled) || featureFlagsSource != null) { - featureFlaggingEnabled = - !FALSE.equals(featureFlagsEnabled) - && isSupportedFeatureFlaggingSource(featureFlagsSource); + if (FALSE.equals(featureFlagsEnabled) || featureFlagsSource != UNSET) { + featureFlaggingEnabled = !FALSE.equals(featureFlagsEnabled) && featureFlagsSource != INVALID; featureFlaggingConfigurationSource = featureFlagsSource; } else if (legacyFeatureFlagsEnabled != null) { featureFlaggingEnabled = legacyFeatureFlagsEnabled; - featureFlaggingConfigurationSource = - legacyFeatureFlagsEnabled ? CONFIGURATION_SOURCE_REMOTE_CONFIG : null; + featureFlaggingConfigurationSource = legacyFeatureFlagsEnabled ? REMOTE_CONFIG : UNSET; } else { featureFlaggingEnabled = true; - featureFlaggingConfigurationSource = CONFIGURATION_SOURCE_AGENTLESS; + featureFlaggingConfigurationSource = AGENTLESS; } traceOtelEnabled = configProvider.getBoolean(TRACE_OTEL_ENABLED, DEFAULT_TRACE_OTEL_ENABLED); metricsOtelEnabled = @@ -525,29 +521,23 @@ public boolean isFeatureFlaggingEnabled() { } /** - * @return the normalized Feature Flags configuration source, unsupported values included, or - * {@code null} when none is configured. + * @return the Feature Flags configuration source, {@link FeatureFlaggingSource#UNSET} when none + * is configured or {@link FeatureFlaggingSource#INVALID} when unsupported. */ - public String getFeatureFlaggingConfigurationSource() { + public FeatureFlaggingSource getFeatureFlaggingConfigurationSource() { return featureFlaggingConfigurationSource; } - /** - * @param source the normalized configuration source. - * @return whether the source is absent or supported. - */ - static boolean isSupportedFeatureFlaggingSource(final String source) { - return source == null - || CONFIGURATION_SOURCE_AGENTLESS.equals(source) - || CONFIGURATION_SOURCE_REMOTE_CONFIG.equals(source); - } - - private static String normalizeFeatureFlaggingSource(final String source) { - if (source == null) { - return null; + private static FeatureFlaggingSource featureFlaggingSource(final ConfigProvider configProvider) { + final String value = configProvider.getString(FEATURE_FLAGS_CONFIGURATION_SOURCE); + if (value == null || value.trim().isEmpty()) { + return UNSET; } - final String normalized = source.trim().toLowerCase(ROOT); - return normalized.isEmpty() ? null : normalized; + final FeatureFlaggingSource source = + configProvider.getEnum( + FEATURE_FLAGS_CONFIGURATION_SOURCE, FeatureFlaggingSource.class, INVALID); + // UNSET is not a valid value to configure. + return source == UNSET ? INVALID : source; } public boolean isTraceOtelEnabled() { diff --git a/internal-api/src/test/java/datadog/trace/api/ConfigTest.java b/internal-api/src/test/java/datadog/trace/api/ConfigTest.java index 526e15a1a00..bbea22a73fa 100644 --- a/internal-api/src/test/java/datadog/trace/api/ConfigTest.java +++ b/internal-api/src/test/java/datadog/trace/api/ConfigTest.java @@ -3667,16 +3667,16 @@ void agentlessFeatureFlagTimingUsesPositiveConfiguredValues() throws Exception { } @TableTest({ - "scenario | value | expected ", - "not set | | agentless ", - "empty | '' | agentless ", - "blank | ' ' | agentless ", - "mixed case padded | ' ReMoTe_ConFiG ' | remote_config ", - "unknown source | not-a-real-source | not-a-real-source", - "padded uppercase | ' OFFLINE ' | offline " + "scenario | value | expected ", + "not set | | agentless ", + "empty | '' | agentless ", + "blank | ' ' | agentless ", + "case insensitive | ReMoTe_ConFiG | remote_config", + "padded | ' remote_config ' | invalid ", + "unknown source | not-a-real-source | invalid ", + "unset is not a value | unset | invalid " }) - void featureFlagConfigurationSourceNormalizesValue(String value, String expected) - throws Exception { + void featureFlagConfigurationSourceParsesValue(String value, String expected) throws Exception { Properties properties = new Properties(); if (value != null) { properties.setProperty(FEATURE_FLAGS_CONFIGURATION_SOURCE, value); @@ -3697,8 +3697,8 @@ void featureFlagConfigurationSourceNormalizesValue(String value, String expected "source wins over legacy disabled | | remote_config | false | true | remote_config ", "provider disabled wins over legacy | false | agentless | true | false | agentless ", "legacy disabled wins over provider | true | | false | false | ", - "unknown source | | not-a-source | | false | not-a-source ", - "offline source | | offline | true | false | offline " + "unknown source | | not-a-source | | false | invalid ", + "offline source | | offline | true | false | invalid " }) void featureFlagConfigurationAppliesMigrationPrecedence( Boolean providerEnabled, diff --git a/internal-api/src/test/java/datadog/trace/api/InstrumenterConfigFeatureFlaggingTest.java b/internal-api/src/test/java/datadog/trace/api/InstrumenterConfigFeatureFlaggingTest.java index 1d0673e2e87..5a10341d0dc 100644 --- a/internal-api/src/test/java/datadog/trace/api/InstrumenterConfigFeatureFlaggingTest.java +++ b/internal-api/src/test/java/datadog/trace/api/InstrumenterConfigFeatureFlaggingTest.java @@ -1,6 +1,5 @@ package datadog.trace.api; -import static datadog.trace.api.InstrumenterConfig.isSupportedFeatureFlaggingSource; import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED; import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.FEATURE_FLAGS_CONFIGURATION_SOURCE; import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.FEATURE_FLAGS_ENABLED; @@ -13,24 +12,25 @@ class InstrumenterConfigFeatureFlaggingTest { @TableTest({ - "scenario | enabled | source | legacy | resolvedEnabled | resolvedSource ", - "defaults | | | | true | 'agentless' ", - "blank source | | ' ' | | true | 'agentless' ", - "legacy enabled | | | 'true' | true | 'remote_config'", - "legacy disabled | | | 'false' | false | ", - "source wins over legacy | | 'agentless' | 'true' | true | 'agentless' ", - "normalized source | | ' remote_CONFIG ' | 'false' | true | 'remote_config'", - "disabled wins over source | 'false' | 'agentless' | 'true' | false | 'agentless' ", - "disabled without source | 'false' | | | false | ", - "unsupported source | | 'invalid' | | false | 'invalid' ", - "unsupported normalized | | ' OFFLINE ' | 'true' | false | 'offline' " + "scenario | enabled | source | legacy | resolvedEnabled | resolvedSource", + "defaults | | | | true | AGENTLESS ", + "blank source | | ' ' | | true | AGENTLESS ", + "legacy enabled | | | 'true' | true | REMOTE_CONFIG ", + "legacy disabled | | | 'false' | false | UNSET ", + "source wins over legacy | | 'agentless' | 'true' | true | AGENTLESS ", + "case insensitive source | | 'Remote_Config' | 'false' | true | REMOTE_CONFIG ", + "disabled wins over source | 'false' | 'agentless' | 'true' | false | AGENTLESS ", + "disabled without source | 'false' | | | false | UNSET ", + "unsupported source | | 'offline' | 'true' | false | INVALID ", + "padded source | | ' agentless ' | | false | INVALID ", + "unset is not a value | | 'unset' | | false | INVALID " }) void resolvesFeatureFlagging( final String enabled, final String source, final String legacy, final boolean resolvedEnabled, - final String resolvedSource) { + final FeatureFlaggingSource resolvedSource) { final Properties properties = new Properties(); if (enabled != null) { properties.setProperty(FEATURE_FLAGS_ENABLED, enabled); @@ -48,15 +48,4 @@ void resolvesFeatureFlagging( assertEquals(resolvedEnabled, config.isFeatureFlaggingEnabled()); assertEquals(resolvedSource, config.getFeatureFlaggingConfigurationSource()); } - - @TableTest({ - "scenario | source | supported", - "absent | | true ", - "agentless | 'agentless' | true ", - "remote config | 'remote_config' | true ", - "unsupported | 'offline' | false " - }) - void recognizesSupportedSources(final String source, final boolean supported) { - assertEquals(supported, isSupportedFeatureFlaggingSource(source)); - } } From 118201b46265ef26d2b5167a87fb521e80f0f0d8 Mon Sep 17 00:00:00 2001 From: Bruce Bujon Date: Wed, 14 Oct 2026 08:10:17 +0200 Subject: [PATCH 5/6] fix(ffe): Gate Feature Flags activation on the product switch only Checking the deprecated experimental toggle at agent start disabled the product even when an explicit configuration source wins over it. Only check the product switch there, and let the feature flagging system apply the full configuration resolution. Co-Authored-By: Claude Opus 5.5 --- .../src/main/java/datadog/trace/bootstrap/Agent.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java index 25d3ddff60b..d9888f23df4 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java @@ -136,8 +136,7 @@ private enum AgentFeature { APP_LOGS_COLLECTION(GeneralConfig.APP_LOGS_COLLECTION_ENABLED, false), LLMOBS(LlmObsConfig.LLMOBS_ENABLED, false), LLMOBS_AGENTLESS(LlmObsConfig.LLMOBS_AGENTLESS_ENABLED, false), - FEATURE_FLAGS(FeatureFlaggingConfig.FEATURE_FLAGS_ENABLED, true), - DEPRECATED_FEATURE_FLAGS(FeatureFlaggingConfig.EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED, true); + FEATURE_FLAGS(FeatureFlaggingConfig.FEATURE_FLAGS_ENABLED, true); private final String configKey; private final String systemProp; @@ -286,9 +285,7 @@ public static void start( agentlessLogSubmissionEnabled = isFeatureEnabled(AgentFeature.AGENTLESS_LOG_SUBMISSION); appLogsCollectionEnabled = isFeatureEnabled(AgentFeature.APP_LOGS_COLLECTION); llmObsEnabled = isFeatureEnabled(AgentFeature.LLMOBS); - featureFlaggingEnabled = - isFeatureEnabled(AgentFeature.FEATURE_FLAGS) - && isFeatureEnabled(AgentFeature.DEPRECATED_FEATURE_FLAGS); + featureFlaggingEnabled = isFeatureEnabled(AgentFeature.FEATURE_FLAGS); // setup writers when llmobs is enabled to accomodate apm and llmobs if (llmObsEnabled) { From 6b3d5a12d14c7c43233471c48b5e57f246c21421 Mon Sep 17 00:00:00 2001 From: Bruce Bujon Date: Wed, 14 Oct 2026 10:02:26 +0200 Subject: [PATCH 6/6] fix(ffe): Initialize FeatureFlaggingSource at native-image build time InstrumenterConfig is initialized at native-image build time and now holds a FeatureFlaggingSource value, so the enum must be initialized at build time too, like the other config enums. Co-Authored-By: Claude Opus 5.5 --- .../nativeimage/NativeImageGeneratorRunnerInstrumentation.java | 1 + 1 file changed, 1 insertion(+) diff --git a/dd-java-agent/instrumentation/graal/graal-native-image-20.0/src/main/java/datadog/trace/instrumentation/graal/nativeimage/NativeImageGeneratorRunnerInstrumentation.java b/dd-java-agent/instrumentation/graal/graal-native-image-20.0/src/main/java/datadog/trace/instrumentation/graal/nativeimage/NativeImageGeneratorRunnerInstrumentation.java index 8c8c6c06cb0..13998669551 100644 --- a/dd-java-agent/instrumentation/graal/graal-native-image-20.0/src/main/java/datadog/trace/instrumentation/graal/nativeimage/NativeImageGeneratorRunnerInstrumentation.java +++ b/dd-java-agent/instrumentation/graal/graal-native-image-20.0/src/main/java/datadog/trace/instrumentation/graal/nativeimage/NativeImageGeneratorRunnerInstrumentation.java @@ -85,6 +85,7 @@ public static void onEnter(@Advice.Argument(value = 0, readOnly = false) String[ + "datadog.trace.api.config.OtlpConfig$Protocol:build_time," + "datadog.trace.api.config.OtlpConfig$Temporality:build_time," + "datadog.trace.api.EventTracker:build_time," + + "datadog.trace.api.FeatureFlaggingSource:build_time," + "datadog.trace.api.InstrumenterConfig:build_time," + "datadog.trace.api.Functions:build_time," + "datadog.trace.api.Functions$NewInstance:build_time,"