Skip to content

Commit 5bd54ec

Browse files
committed
feat(ffe): preserve legacy source selection
1 parent 488b477 commit 5bd54ec

8 files changed

Lines changed: 239 additions & 33 deletions

File tree

dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import datadog.trace.api.config.TracerConfig;
4545
import datadog.trace.api.config.UsmConfig;
4646
import datadog.trace.api.featureflag.config.FeatureFlaggingConfig;
47+
import datadog.trace.api.featureflag.config.FeatureFlaggingConfigurationSource;
4748
import datadog.trace.api.gateway.RequestContextSlot;
4849
import datadog.trace.api.gateway.SubscriptionService;
4950
import datadog.trace.api.git.EmbeddedGitInfoBuilder;
@@ -135,7 +136,7 @@ private enum AgentFeature {
135136
APP_LOGS_COLLECTION(GeneralConfig.APP_LOGS_COLLECTION_ENABLED, false),
136137
LLMOBS(LlmObsConfig.LLMOBS_ENABLED, false),
137138
LLMOBS_AGENTLESS(LlmObsConfig.LLMOBS_AGENTLESS_ENABLED, false),
138-
FEATURE_FLAGGING(FeatureFlaggingConfig.FLAGGING_PROVIDER_ENABLED, false);
139+
FEATURE_FLAGGING(FeatureFlaggingConfig.FLAGGING_PROVIDER_ENABLED, true);
139140

140141
private final String configKey;
141142
private final String systemProp;
@@ -282,7 +283,7 @@ public static void start(
282283
agentlessLogSubmissionEnabled = isFeatureEnabled(AgentFeature.AGENTLESS_LOG_SUBMISSION);
283284
appLogsCollectionEnabled = isFeatureEnabled(AgentFeature.APP_LOGS_COLLECTION);
284285
llmObsEnabled = isFeatureEnabled(AgentFeature.LLMOBS);
285-
featureFlaggingEnabled = isFeatureEnabled(AgentFeature.FEATURE_FLAGGING);
286+
featureFlaggingEnabled = isFeatureFlaggingEnabled();
286287

287288
// setup writers when llmobs is enabled to accomodate apm and llmobs
288289
if (llmObsEnabled) {
@@ -1648,6 +1649,42 @@ private static boolean isFeatureEnabled(AgentFeature feature) {
16481649
}
16491650
}
16501651

1652+
private static boolean isFeatureFlaggingEnabled() {
1653+
final Boolean providerEnabled =
1654+
featureFlaggingBooleanSetting(FeatureFlaggingConfig.FLAGGING_PROVIDER_ENABLED);
1655+
final String configurationSource =
1656+
featureFlaggingSetting(FeatureFlaggingConfig.FEATURE_FLAGS_CONFIGURATION_SOURCE);
1657+
final Boolean legacyProviderEnabled =
1658+
featureFlaggingBooleanSetting(FeatureFlaggingConfig.EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED);
1659+
1660+
return FeatureFlaggingConfigurationSource.resolve(
1661+
providerEnabled, configurationSource, legacyProviderEnabled)
1662+
!= FeatureFlaggingConfigurationSource.OFFLINE;
1663+
}
1664+
1665+
private static Boolean featureFlaggingBooleanSetting(final String configKey) {
1666+
final String value = featureFlaggingSetting(configKey);
1667+
if (value == null) {
1668+
return null;
1669+
}
1670+
return Boolean.parseBoolean(value) || "1".equals(value);
1671+
}
1672+
1673+
private static String featureFlaggingSetting(final String configKey) {
1674+
final String systemProperty = propertyNameToSystemPropertyName(configKey);
1675+
String value = SystemProperties.get(systemProperty);
1676+
if (value == null) {
1677+
value = getStableConfig(FLEET, configKey);
1678+
}
1679+
if (value == null) {
1680+
value = ddGetEnv(systemProperty);
1681+
}
1682+
if (value == null) {
1683+
value = getStableConfig(LOCAL, configKey);
1684+
}
1685+
return value;
1686+
}
1687+
16511688
/**
16521689
* @see datadog.trace.api.ProductActivation#fromString(String)
16531690
*/

internal-api/src/main/java/datadog/trace/api/Config.java

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@
8686
import static datadog.trace.api.ConfigDefaults.DEFAULT_ELASTICSEARCH_BODY_ENABLED;
8787
import static datadog.trace.api.ConfigDefaults.DEFAULT_ELASTICSEARCH_PARAMS_ENABLED;
8888
import static datadog.trace.api.ConfigDefaults.DEFAULT_EXPERIMENTATAL_JEE_SPLIT_BY_DEPLOYMENT;
89-
import static datadog.trace.api.ConfigDefaults.DEFAULT_FEATURE_FLAGGING_CONFIGURATION_SOURCE;
9089
import static datadog.trace.api.ConfigDefaults.DEFAULT_FEATURE_FLAGGING_CONFIGURATION_SOURCE_POLL_INTERVAL_SECONDS;
9190
import static datadog.trace.api.ConfigDefaults.DEFAULT_FEATURE_FLAGGING_CONFIGURATION_SOURCE_REQUEST_TIMEOUT_SECONDS;
9291
import static datadog.trace.api.ConfigDefaults.DEFAULT_GRPC_CLIENT_ERROR_STATUSES;
@@ -726,10 +725,12 @@
726725
import static datadog.trace.api.config.TracerConfig.WRITER_BAGGAGE_INJECT;
727726
import static datadog.trace.api.config.TracerConfig.WRITER_LINKS_INJECT;
728727
import static datadog.trace.api.config.TracerConfig.WRITER_TYPE;
728+
import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED;
729729
import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.FEATURE_FLAGS_CONFIGURATION_SOURCE;
730730
import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL;
731731
import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_POLL_INTERVAL_SECONDS;
732732
import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_REQUEST_TIMEOUT_SECONDS;
733+
import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.FLAGGING_PROVIDER_ENABLED;
733734
import static datadog.trace.api.telemetry.LogCollector.SEND_TELEMETRY;
734735
import static datadog.trace.bootstrap.instrumentation.api.WriterConstants.OTLP_WRITER_TYPE;
735736
import static datadog.trace.util.CollectionUtils.tryMakeImmutableList;
@@ -746,6 +747,7 @@
746747
import datadog.trace.api.config.OtlpConfig;
747748
import datadog.trace.api.config.ProfilingConfig;
748749
import datadog.trace.api.config.TracerConfig;
750+
import datadog.trace.api.featureflag.config.FeatureFlaggingConfigurationSource;
749751
import datadog.trace.api.iast.IastContext;
750752
import datadog.trace.api.iast.IastDetectionMode;
751753
import datadog.trace.api.iast.telemetry.Verbosity;
@@ -1221,6 +1223,7 @@ public static String getHostName() {
12211223

12221224
private final int remoteConfigMaxExtraServices;
12231225

1226+
private final boolean featureFlaggingProviderEnabled;
12241227
private final String featureFlaggingConfigurationSource;
12251228
private final String featureFlaggingConfigurationSourceAgentlessBaseUrl;
12261229
private final int featureFlaggingConfigurationSourcePollIntervalSeconds;
@@ -2851,10 +2854,35 @@ PROFILING_DATADOG_PROFILER_ENABLED, isDatadogProfilerSafeInCurrentEnvironment())
28512854
configProvider.getInteger(
28522855
REMOTE_CONFIG_MAX_EXTRA_SERVICES, DEFAULT_REMOTE_CONFIG_MAX_EXTRA_SERVICES);
28532856

2854-
featureFlaggingConfigurationSource =
2855-
normalizeFeatureFlaggingConfigurationSource(
2856-
configProvider.getString(
2857-
FEATURE_FLAGS_CONFIGURATION_SOURCE, DEFAULT_FEATURE_FLAGGING_CONFIGURATION_SOURCE));
2857+
final boolean configuredFeatureFlaggingProviderEnabled =
2858+
configProvider.getBoolean(FLAGGING_PROVIDER_ENABLED, true);
2859+
final Boolean legacyFeatureFlaggingProviderEnabled =
2860+
configProvider.getBoolean(EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED);
2861+
final String configuredFeatureFlaggingConfigurationSource =
2862+
configProvider.isSet(FEATURE_FLAGS_CONFIGURATION_SOURCE)
2863+
? configProvider.getString(FEATURE_FLAGS_CONFIGURATION_SOURCE)
2864+
: null;
2865+
final FeatureFlaggingConfigurationSource resolvedFeatureFlaggingConfigurationSource =
2866+
FeatureFlaggingConfigurationSource.resolve(
2867+
configuredFeatureFlaggingProviderEnabled,
2868+
configuredFeatureFlaggingConfigurationSource,
2869+
legacyFeatureFlaggingProviderEnabled);
2870+
if (legacyFeatureFlaggingProviderEnabled != null) {
2871+
log.warn(
2872+
"Setting {} is deprecated. Use {} and {} instead.",
2873+
EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED,
2874+
FLAGGING_PROVIDER_ENABLED,
2875+
FEATURE_FLAGS_CONFIGURATION_SOURCE);
2876+
}
2877+
if (!FeatureFlaggingConfigurationSource.isSupportedExplicitSource(
2878+
configuredFeatureFlaggingConfigurationSource)) {
2879+
log.warn(
2880+
"Unsupported Feature Flagging configuration source: {}. Disabling Feature Flagging",
2881+
configuredFeatureFlaggingConfigurationSource);
2882+
}
2883+
featureFlaggingProviderEnabled =
2884+
resolvedFeatureFlaggingConfigurationSource != FeatureFlaggingConfigurationSource.OFFLINE;
2885+
featureFlaggingConfigurationSource = resolvedFeatureFlaggingConfigurationSource.value();
28582886
featureFlaggingConfigurationSourceAgentlessBaseUrl =
28592887
configProvider.getStringNotEmpty(
28602888
FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL, null);
@@ -3797,28 +3825,6 @@ public boolean isInferredProxyPropagationEnabled() {
37973825
return traceInferredProxyEnabled;
37983826
}
37993827

3800-
private static String normalizeFeatureFlaggingConfigurationSource(final String source) {
3801-
if (source == null) {
3802-
return DEFAULT_FEATURE_FLAGGING_CONFIGURATION_SOURCE;
3803-
}
3804-
final String normalized = source.trim().toLowerCase(Locale.ROOT);
3805-
if (normalized.isEmpty()) {
3806-
return DEFAULT_FEATURE_FLAGGING_CONFIGURATION_SOURCE;
3807-
}
3808-
switch (normalized) {
3809-
case "agentless":
3810-
case "remote_config":
3811-
case "offline":
3812-
return normalized;
3813-
default:
3814-
log.warn(
3815-
"Unsupported Feature Flagging configuration source: {}. Defaulting to {}",
3816-
source,
3817-
DEFAULT_FEATURE_FLAGGING_CONFIGURATION_SOURCE);
3818-
return DEFAULT_FEATURE_FLAGGING_CONFIGURATION_SOURCE;
3819-
}
3820-
}
3821-
38223828
public boolean isBaggageExtract() {
38233829
return tracePropagationStylesToExtract.contains(TracePropagationStyle.BAGGAGE);
38243830
}
@@ -4736,6 +4742,10 @@ public int getRemoteConfigMaxExtraServices() {
47364742
return remoteConfigMaxExtraServices;
47374743
}
47384744

4745+
public boolean isFeatureFlaggingProviderEnabled() {
4746+
return featureFlaggingProviderEnabled;
4747+
}
4748+
47394749
public String getFeatureFlaggingConfigurationSource() {
47404750
return featureFlaggingConfigurationSource;
47414751
}
@@ -6584,6 +6594,8 @@ public String toString() {
65846594
+ remoteConfigMaxPayloadSize
65856595
+ ", remoteConfigIntegrityCheckEnabled="
65866596
+ remoteConfigIntegrityCheckEnabled
6597+
+ ", featureFlaggingProviderEnabled="
6598+
+ featureFlaggingProviderEnabled
65876599
+ ", featureFlaggingConfigurationSource="
65886600
+ featureFlaggingConfigurationSource
65896601
+ ", featureFlaggingConfigurationSourceAgentlessBaseUrl="

internal-api/src/test/groovy/datadog/trace/api/ConfigTest.groovy

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,11 @@ import static datadog.trace.api.config.GeneralConfig.TAGS
5757
import static datadog.trace.api.config.GeneralConfig.TRACER_METRICS_IGNORED_RESOURCES
5858
import static datadog.trace.api.config.GeneralConfig.TRACE_OTEL_SEMANTICS_ENABLED
5959
import static datadog.trace.api.config.GeneralConfig.VERSION
60+
import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED
6061
import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.FEATURE_FLAGS_CONFIGURATION_SOURCE
6162
import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_POLL_INTERVAL_SECONDS
6263
import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_REQUEST_TIMEOUT_SECONDS
64+
import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.FLAGGING_PROVIDER_ENABLED
6365
import static datadog.trace.api.config.JmxFetchConfig.JMX_FETCH_CHECK_PERIOD
6466
import static datadog.trace.api.config.JmxFetchConfig.JMX_FETCH_ENABLED
6567
import static datadog.trace.api.config.JmxFetchConfig.JMX_FETCH_METRICS_CONFIGS
@@ -3517,7 +3519,39 @@ class ConfigTest extends DDSpecification {
35173519
"" | "agentless"
35183520
" " | "agentless"
35193521
" ReMoTe_ConFiG " | "remote_config"
3520-
"not-a-real-source" | "agentless"
3522+
"not-a-real-source" | "offline"
3523+
}
3524+
3525+
def "feature flag configuration applies migration precedence"() {
3526+
setup:
3527+
Properties properties = new Properties()
3528+
if (providerEnabled != null) {
3529+
properties.setProperty(FLAGGING_PROVIDER_ENABLED, providerEnabled.toString())
3530+
}
3531+
if (source != null) {
3532+
properties.setProperty(FEATURE_FLAGS_CONFIGURATION_SOURCE, source)
3533+
}
3534+
if (legacyProviderEnabled != null) {
3535+
properties.setProperty(EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED, legacyProviderEnabled.toString())
3536+
}
3537+
3538+
when:
3539+
def config = new Config(ConfigProvider.withPropertiesOverride(properties))
3540+
3541+
then:
3542+
config.featureFlaggingProviderEnabled == expectedEnabled
3543+
config.featureFlaggingConfigurationSource == expectedSource
3544+
3545+
where:
3546+
providerEnabled | source | legacyProviderEnabled | expectedEnabled | expectedSource
3547+
null | null | null | true | "agentless"
3548+
null | null | true | true | "remote_config"
3549+
null | null | false | false | "offline"
3550+
null | "agentless" | true | true | "agentless"
3551+
null | "remote_config" | false | true | "remote_config"
3552+
false | "agentless" | true | false | "offline"
3553+
true | null | false | false | "offline"
3554+
null | "offline" | null | false | "offline"
35213555
}
35223556
35233557
def "agentless feature flag timing falls back for non-positive values"() {

metadata/supported-configurations.json

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1497,6 +1497,14 @@
14971497
"aliases": []
14981498
}
14991499
],
1500+
"DD_FEATURE_FLAGGING_PROVIDER_ENABLED": [
1501+
{
1502+
"version": "A",
1503+
"type": "boolean",
1504+
"default": "true",
1505+
"aliases": []
1506+
}
1507+
],
15001508
"DD_FEATURE_FLAGS_CONFIGURATION_SOURCE": [
15011509
{
15021510
"version": "A",
@@ -1533,7 +1541,7 @@
15331541
{
15341542
"version": "A",
15351543
"type": "boolean",
1536-
"default": "false",
1544+
"default": null,
15371545
"aliases": []
15381546
}
15391547
],
@@ -11986,5 +11994,7 @@
1198611994
}
1198711995
]
1198811996
},
11989-
"deprecations": {}
11997+
"deprecations": {
11998+
"DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED": "DD_FEATURE_FLAGGING_PROVIDER_ENABLED"
11999+
}
1199012000
}

products/feature-flagging/feature-flagging-config/build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,7 @@ extra["excludedClassesCoverage"] = listOf(
1010
// Constants-only holder — no executable logic to cover.
1111
"datadog.trace.api.featureflag.config.FeatureFlaggingConfig",
1212
)
13+
14+
dependencies {
15+
testImplementation(libs.bundles.junit5)
16+
}

products/feature-flagging/feature-flagging-config/src/main/java/datadog/trace/api/featureflag/config/FeatureFlaggingConfig.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
public class FeatureFlaggingConfig {
44

5-
public static final String FLAGGING_PROVIDER_ENABLED = "experimental.flagging.provider.enabled";
5+
public static final String FLAGGING_PROVIDER_ENABLED = "feature.flagging.provider.enabled";
6+
public static final String EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED =
7+
"experimental.flagging.provider.enabled";
68

79
/**
810
* Opt-in gate for APM span enrichment with feature-flag evaluation metadata. DISTINCT from {@link
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package datadog.trace.api.featureflag.config;
2+
3+
import java.util.Locale;
4+
5+
/** Resolves Feature Flagging provider and delivery-source configuration. */
6+
public enum FeatureFlaggingConfigurationSource {
7+
AGENTLESS("agentless"),
8+
REMOTE_CONFIG("remote_config"),
9+
OFFLINE("offline");
10+
11+
private final String value;
12+
13+
FeatureFlaggingConfigurationSource(final String value) {
14+
this.value = value;
15+
}
16+
17+
public String value() {
18+
return value;
19+
}
20+
21+
/**
22+
* Applies the migration-window precedence rules. Unsupported explicit sources fail closed.
23+
*
24+
* @param providerEnabled stable provider kill switch, or {@code null} when absent
25+
* @param explicitSource explicitly configured delivery source, or {@code null} when absent
26+
* @param legacyProviderEnabled legacy provider setting, or {@code null} when absent
27+
*/
28+
public static FeatureFlaggingConfigurationSource resolve(
29+
final Boolean providerEnabled,
30+
final String explicitSource,
31+
final Boolean legacyProviderEnabled) {
32+
if (Boolean.FALSE.equals(providerEnabled)) {
33+
return OFFLINE;
34+
}
35+
36+
final String normalized = normalize(explicitSource);
37+
if (normalized != null) {
38+
for (final FeatureFlaggingConfigurationSource source : values()) {
39+
if (source.value.equals(normalized)) {
40+
return source;
41+
}
42+
}
43+
return OFFLINE;
44+
}
45+
46+
if (legacyProviderEnabled != null) {
47+
return legacyProviderEnabled ? REMOTE_CONFIG : OFFLINE;
48+
}
49+
return AGENTLESS;
50+
}
51+
52+
public static boolean isSupportedExplicitSource(final String source) {
53+
final String normalized = normalize(source);
54+
if (normalized == null) {
55+
return true;
56+
}
57+
for (final FeatureFlaggingConfigurationSource configurationSource : values()) {
58+
if (configurationSource.value.equals(normalized)) {
59+
return true;
60+
}
61+
}
62+
return false;
63+
}
64+
65+
public static FeatureFlaggingConfigurationSource fromResolved(final String source) {
66+
final String normalized = normalize(source);
67+
for (final FeatureFlaggingConfigurationSource configurationSource : values()) {
68+
if (configurationSource.value.equals(normalized)) {
69+
return configurationSource;
70+
}
71+
}
72+
throw new IllegalArgumentException(
73+
"Unsupported Feature Flagging configuration source: " + source);
74+
}
75+
76+
private static String normalize(final String source) {
77+
if (source == null) {
78+
return null;
79+
}
80+
final String normalized = source.trim().toLowerCase(Locale.ROOT);
81+
return normalized.isEmpty() ? null : normalized;
82+
}
83+
}

0 commit comments

Comments
 (0)