Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
b8bbd26
Define Feature Flagging configuration source contract
leoromanovsky Jul 11, 2026
4baefdb
Add Datadog-managed agentless UFC polling
leoromanovsky Jul 11, 2026
d378350
Support custom agentless UFC endpoints
leoromanovsky Jul 11, 2026
f9e70e6
Select the Feature Flagging configuration source
leoromanovsky Jul 11, 2026
17a02ad
Warn on agentless authentication failures
leoromanovsky Jul 13, 2026
0f02a2a
Merge remote-tracking branch 'origin/master' into rewrite/java-agentl…
leoromanovsky Jul 15, 2026
5057539
fix(ffe): handle nullable agentless response bodies
leoromanovsky Jul 15, 2026
7aa2181
Merge branch 'master' into leo.romanovsky/ffl-2693-java-agentless-con…
leoromanovsky Jul 15, 2026
ffbf970
Address agentless poller review feedback
leoromanovsky Jul 15, 2026
3b9b97c
Fix feature flag config test imports
leoromanovsky Jul 15, 2026
eaaea27
Address additional agentless source review feedback
leoromanovsky Jul 15, 2026
76917d6
Support the UFC CDN response contract
leoromanovsky Jul 15, 2026
2cee183
Harden agentless configuration responses
leoromanovsky Jul 17, 2026
ebc28ca
Separate UFC transport parsers
leoromanovsky Jul 17, 2026
0984370
Fix feature flagging test formatting
leoromanovsky Jul 17, 2026
c146f09
Fix feature flagging parser coverage
leoromanovsky Jul 17, 2026
488b477
Address final feature flagging review feedback
leoromanovsky Jul 17, 2026
c0575ac
Merge branch 'master' into leo.romanovsky/ffl-2693-java-agentless-con…
vjfridge Jul 21, 2026
3ae9cf0
Merge branch 'master' into leo.romanovsky/ffl-2693-java-agentless-con…
leoromanovsky Jul 24, 2026
eaa99ce
fix(feature-flags): align configuration source semantics
leoromanovsky Jul 24, 2026
2c0ee6c
feat(feature-flags): delay agentless polling until provider use
leoromanovsky Jul 24, 2026
bd0e347
feat(communication): support mapped retried HTTP calls
leoromanovsky Jul 24, 2026
cfaca97
fix(feature-flags): align agentless HTTP behavior
leoromanovsky Jul 24, 2026
6df48d1
fix(feature-flags): complete initial poll during activation
leoromanovsky Jul 24, 2026
0e14ef5
chore(feature-flags): remove unused agent feature
leoromanovsky Jul 24, 2026
5ce7fa3
Merge branch 'master' into leo.romanovsky/ffl-2693-java-agentless-con…
leoromanovsky Jul 24, 2026
43bfeff
Fix race in agentless config source listener test
vjfridge Jul 24, 2026
ad7d626
test(feature-flags): cover agentless lifecycle branches
leoromanovsky Jul 24, 2026
29c1fd4
chore(agent): raise jar size budget to 34 MiB
leoromanovsky Jul 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class OpenFeatureProviderSmokeTest extends AbstractServerSmokeTest {
command.addAll(['-jar', springBootShadowJar, "--server.port=${httpPort}".toString()])
final builder = new ProcessBuilder(command).directory(new File(buildDirectory))
builder.environment().put('DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED', 'true')
builder.environment().put('DD_FEATURE_FLAGS_CONFIGURATION_SOURCE', 'remote_config')
return builder
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ public final class ConfigDefaults {
static final boolean DEFAULT_INJECT_DATADOG_ATTRIBUTE = true;
static final String DEFAULT_SITE = "datadoghq.com";

public static final String DEFAULT_FEATURE_FLAGGING_CONFIGURATION_SOURCE = "agentless";
Comment thread
leoromanovsky marked this conversation as resolved.
Outdated
public static final int DEFAULT_FEATURE_FLAGGING_CONFIGURATION_SOURCE_POLL_INTERVAL_SECONDS = 30;
public static final int DEFAULT_FEATURE_FLAGGING_CONFIGURATION_SOURCE_REQUEST_TIMEOUT_SECONDS = 2;
Comment thread
leoromanovsky marked this conversation as resolved.
Outdated

static final boolean DEFAULT_CODE_ORIGIN_FOR_SPANS_INTERFACE_SUPPORT = false;
static final int DEFAULT_CODE_ORIGIN_MAX_USER_FRAMES = 8;
static final boolean DEFAULT_TRACE_ENABLED = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,15 @@
public class FeatureFlaggingConfig {

public static final String FLAGGING_PROVIDER_ENABLED = "experimental.flagging.provider.enabled";

public static final String FEATURE_FLAGS_CONFIGURATION_SOURCE =
"feature.flags.configuration.source";
public static final String FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL =
"feature.flags.configuration.source.agentless.base.url";
public static final String FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_POLL_INTERVAL_SECONDS =
"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";

private FeatureFlaggingConfig() {}
}
78 changes: 78 additions & 0 deletions internal-api/src/main/java/datadog/trace/api/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@
import static datadog.trace.api.ConfigDefaults.DEFAULT_ELASTICSEARCH_BODY_ENABLED;
import static datadog.trace.api.ConfigDefaults.DEFAULT_ELASTICSEARCH_PARAMS_ENABLED;
import static datadog.trace.api.ConfigDefaults.DEFAULT_EXPERIMENTATAL_JEE_SPLIT_BY_DEPLOYMENT;
import static datadog.trace.api.ConfigDefaults.DEFAULT_FEATURE_FLAGGING_CONFIGURATION_SOURCE;
import static datadog.trace.api.ConfigDefaults.DEFAULT_FEATURE_FLAGGING_CONFIGURATION_SOURCE_POLL_INTERVAL_SECONDS;
import static datadog.trace.api.ConfigDefaults.DEFAULT_FEATURE_FLAGGING_CONFIGURATION_SOURCE_REQUEST_TIMEOUT_SECONDS;
import static datadog.trace.api.ConfigDefaults.DEFAULT_GRPC_CLIENT_ERROR_STATUSES;
import static datadog.trace.api.ConfigDefaults.DEFAULT_GRPC_SERVER_ERROR_STATUSES;
import static datadog.trace.api.ConfigDefaults.DEFAULT_HEALTH_METRICS_ENABLED;
Expand Down Expand Up @@ -361,6 +364,10 @@
import static datadog.trace.api.config.DebuggerConfig.THIRD_PARTY_EXCLUDES;
import static datadog.trace.api.config.DebuggerConfig.THIRD_PARTY_INCLUDES;
import static datadog.trace.api.config.DebuggerConfig.THIRD_PARTY_SHADING_IDENTIFIERS;
import static datadog.trace.api.config.FeatureFlaggingConfig.FEATURE_FLAGS_CONFIGURATION_SOURCE;
import static datadog.trace.api.config.FeatureFlaggingConfig.FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL;
import static datadog.trace.api.config.FeatureFlaggingConfig.FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_POLL_INTERVAL_SECONDS;
import static datadog.trace.api.config.FeatureFlaggingConfig.FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_REQUEST_TIMEOUT_SECONDS;
import static datadog.trace.api.config.GeneralConfig.AGENTLESS_LOG_SUBMISSION_LEVEL;
import static datadog.trace.api.config.GeneralConfig.AGENTLESS_LOG_SUBMISSION_QUEUE_SIZE;
import static datadog.trace.api.config.GeneralConfig.AGENTLESS_LOG_SUBMISSION_URL;
Expand Down Expand Up @@ -1213,6 +1220,11 @@ public static String getHostName() {

private final int remoteConfigMaxExtraServices;

private final String featureFlaggingConfigurationSource;
private final String featureFlaggingConfigurationSourceAgentlessBaseUrl;
private final int featureFlaggingConfigurationSourcePollIntervalSeconds;
private final int featureFlaggingConfigurationSourceRequestTimeoutSeconds;

private final boolean dbmInjectSqlBaseHash;
private final String dbmPropagationMode;
private final boolean dbmTracePreparedStatements;
Expand Down Expand Up @@ -2837,6 +2849,40 @@ PROFILING_DATADOG_PROFILER_ENABLED, isDatadogProfilerSafeInCurrentEnvironment())
configProvider.getInteger(
REMOTE_CONFIG_MAX_EXTRA_SERVICES, DEFAULT_REMOTE_CONFIG_MAX_EXTRA_SERVICES);

featureFlaggingConfigurationSource =
normalizeFeatureFlaggingConfigurationSource(
configProvider.getString(
FEATURE_FLAGS_CONFIGURATION_SOURCE, DEFAULT_FEATURE_FLAGGING_CONFIGURATION_SOURCE));
featureFlaggingConfigurationSourceAgentlessBaseUrl =
configProvider.getStringNotEmpty(
FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL, null);
int configuredFeatureFlaggingPollIntervalSeconds =
configProvider.getInteger(
FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_POLL_INTERVAL_SECONDS,
DEFAULT_FEATURE_FLAGGING_CONFIGURATION_SOURCE_POLL_INTERVAL_SECONDS);
if (configuredFeatureFlaggingPollIntervalSeconds <= 0) {
log.warn(
"Invalid Feature Flagging agentless poll interval: {}. The value must be positive",
configuredFeatureFlaggingPollIntervalSeconds);
configuredFeatureFlaggingPollIntervalSeconds =
DEFAULT_FEATURE_FLAGGING_CONFIGURATION_SOURCE_POLL_INTERVAL_SECONDS;
}
featureFlaggingConfigurationSourcePollIntervalSeconds =
configuredFeatureFlaggingPollIntervalSeconds;
int configuredFeatureFlaggingRequestTimeoutSeconds =
configProvider.getInteger(
FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_REQUEST_TIMEOUT_SECONDS,
DEFAULT_FEATURE_FLAGGING_CONFIGURATION_SOURCE_REQUEST_TIMEOUT_SECONDS);
if (configuredFeatureFlaggingRequestTimeoutSeconds <= 0) {
log.warn(
"Invalid Feature Flagging agentless request timeout: {}. The value must be positive",
configuredFeatureFlaggingRequestTimeoutSeconds);
configuredFeatureFlaggingRequestTimeoutSeconds =
DEFAULT_FEATURE_FLAGGING_CONFIGURATION_SOURCE_REQUEST_TIMEOUT_SECONDS;
}
featureFlaggingConfigurationSourceRequestTimeoutSeconds =
configuredFeatureFlaggingRequestTimeoutSeconds;

dynamicInstrumentationEnabled =
configProvider.getBoolean(
DYNAMIC_INSTRUMENTATION_ENABLED, DEFAULT_DYNAMIC_INSTRUMENTATION_ENABLED);
Expand Down Expand Up @@ -3749,6 +3795,14 @@ public boolean isInferredProxyPropagationEnabled() {
return traceInferredProxyEnabled;
}

private static String normalizeFeatureFlaggingConfigurationSource(final String source) {
Comment thread
leoromanovsky marked this conversation as resolved.
Outdated
Comment thread
leoromanovsky marked this conversation as resolved.
Outdated
if (source == null) {
return DEFAULT_FEATURE_FLAGGING_CONFIGURATION_SOURCE;
}
final String normalized = source.trim().toLowerCase(Locale.ROOT);
return normalized.isEmpty() ? DEFAULT_FEATURE_FLAGGING_CONFIGURATION_SOURCE : normalized;
}

public boolean isBaggageExtract() {
return tracePropagationStylesToExtract.contains(TracePropagationStyle.BAGGAGE);
}
Expand Down Expand Up @@ -4656,6 +4710,22 @@ public int getRemoteConfigMaxExtraServices() {
return remoteConfigMaxExtraServices;
}

public String getFeatureFlaggingConfigurationSource() {
return featureFlaggingConfigurationSource;
}

public String getFeatureFlaggingConfigurationSourceAgentlessBaseUrl() {
return featureFlaggingConfigurationSourceAgentlessBaseUrl;
}

public int getFeatureFlaggingConfigurationSourcePollIntervalSeconds() {
return featureFlaggingConfigurationSourcePollIntervalSeconds;
}

public int getFeatureFlaggingConfigurationSourceRequestTimeoutSeconds() {
return featureFlaggingConfigurationSourceRequestTimeoutSeconds;
}

public boolean isDynamicInstrumentationEnabled() {
return dynamicInstrumentationEnabled;
}
Expand Down Expand Up @@ -6488,6 +6558,14 @@ public String toString() {
+ remoteConfigMaxPayloadSize
+ ", remoteConfigIntegrityCheckEnabled="
+ remoteConfigIntegrityCheckEnabled
+ ", featureFlaggingConfigurationSource="
+ featureFlaggingConfigurationSource
+ ", featureFlaggingConfigurationSourceAgentlessBaseUrl="
+ featureFlaggingConfigurationSourceAgentlessBaseUrl
+ ", featureFlaggingConfigurationSourcePollIntervalSeconds="
+ featureFlaggingConfigurationSourcePollIntervalSeconds
+ ", featureFlaggingConfigurationSourceRequestTimeoutSeconds="
+ featureFlaggingConfigurationSourceRequestTimeoutSeconds
+ ", debuggerEnabled="
+ dynamicInstrumentationEnabled
+ ", debuggerUploadTimeout="
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ public enum AgentThread {

LLMOBS_EVALS_PROCESSOR("dd-llmobs-evals-processor"),

FEATURE_FLAG_EXPOSURE_PROCESSOR("dd-ffe-exposure-processor");
FEATURE_FLAG_EXPOSURE_PROCESSOR("dd-ffe-exposure-processor"),
FEATURE_FLAG_CONFIGURATION_POLLER("dd-feature-flagging-http-poller");

public final String threadName;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package datadog.trace.api

import static datadog.trace.api.ConfigDefaults.DEFAULT_HTTP_CLIENT_ERROR_STATUSES
import static datadog.trace.api.ConfigDefaults.DEFAULT_HTTP_SERVER_ERROR_STATUSES
import static datadog.trace.api.ConfigDefaults.DEFAULT_FEATURE_FLAGGING_CONFIGURATION_SOURCE_POLL_INTERVAL_SECONDS
import static datadog.trace.api.ConfigDefaults.DEFAULT_FEATURE_FLAGGING_CONFIGURATION_SOURCE_REQUEST_TIMEOUT_SECONDS
import static datadog.trace.api.ConfigDefaults.DEFAULT_PARTIAL_FLUSH_MIN_SPANS
import static datadog.trace.api.ConfigDefaults.DEFAULT_SERVICE_NAME
import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_LONG_RUNNING_FLUSH_INTERVAL
Expand Down Expand Up @@ -55,6 +57,8 @@ import static datadog.trace.api.config.GeneralConfig.TAGS
import static datadog.trace.api.config.GeneralConfig.TRACER_METRICS_IGNORED_RESOURCES
import static datadog.trace.api.config.GeneralConfig.TRACE_OTEL_SEMANTICS_ENABLED
import static datadog.trace.api.config.GeneralConfig.VERSION
import static datadog.trace.api.config.FeatureFlaggingConfig.FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_POLL_INTERVAL_SECONDS
import static datadog.trace.api.config.FeatureFlaggingConfig.FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_REQUEST_TIMEOUT_SECONDS
Comment thread
leoromanovsky marked this conversation as resolved.
Outdated
import static datadog.trace.api.config.JmxFetchConfig.JMX_FETCH_CHECK_PERIOD
import static datadog.trace.api.config.JmxFetchConfig.JMX_FETCH_ENABLED
import static datadog.trace.api.config.JmxFetchConfig.JMX_FETCH_METRICS_CONFIGS
Expand Down Expand Up @@ -3478,4 +3482,32 @@ class ConfigTest extends DDSpecification {
"1" | true
"0" | false
}

def "agentless feature flag timing uses positive configured values"() {
setup:
Properties properties = new Properties()
properties.setProperty(FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_POLL_INTERVAL_SECONDS, "60")
properties.setProperty(FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_REQUEST_TIMEOUT_SECONDS, "4")

when:
def config = new Config(ConfigProvider.withPropertiesOverride(properties))

then:
config.featureFlaggingConfigurationSourcePollIntervalSeconds == 60
config.featureFlaggingConfigurationSourceRequestTimeoutSeconds == 4
}

def "agentless feature flag timing falls back for non-positive values"() {
setup:
Properties properties = new Properties()
properties.setProperty(FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_POLL_INTERVAL_SECONDS, "0")
properties.setProperty(FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_REQUEST_TIMEOUT_SECONDS, "-1")

when:
def config = new Config(ConfigProvider.withPropertiesOverride(properties))

then:
config.featureFlaggingConfigurationSourcePollIntervalSeconds == DEFAULT_FEATURE_FLAGGING_CONFIGURATION_SOURCE_POLL_INTERVAL_SECONDS
config.featureFlaggingConfigurationSourceRequestTimeoutSeconds == DEFAULT_FEATURE_FLAGGING_CONFIGURATION_SOURCE_REQUEST_TIMEOUT_SECONDS
}
}
32 changes: 32 additions & 0 deletions metadata/supported-configurations.json
Original file line number Diff line number Diff line change
Expand Up @@ -1497,6 +1497,38 @@
"aliases": []
}
],
"DD_FEATURE_FLAGS_CONFIGURATION_SOURCE": [
{
"version": "A",
"type": "string",
"default": "agentless",
"aliases": []
}
],
"DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL": [
{
"version": "A",
"type": "string",
"default": null,
"aliases": []
}
],
"DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_POLL_INTERVAL_SECONDS": [
{
"version": "A",
"type": "int",
"default": "30",
"aliases": []
}
],
"DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_REQUEST_TIMEOUT_SECONDS": [
{
"version": "A",
"type": "int",
"default": "2",
"aliases": []
}
],
"DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED": [
{
"version": "A",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public class FeatureFlaggingSystem {

private static final Logger LOGGER = LoggerFactory.getLogger(FeatureFlaggingSystem.class);

private static volatile RemoteConfigService CONFIG_SERVICE;
private static volatile ConfigurationSourceService CONFIG_SERVICE;
private static volatile ExposureWriter EXPOSURE_WRITER;

private FeatureFlaggingSystem() {}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.datadog.featureflag;

import static datadog.trace.api.config.FeatureFlaggingConfig.FEATURE_FLAGS_CONFIGURATION_SOURCE;
import static datadog.trace.api.config.RemoteConfigConfig.REMOTE_CONFIGURATION_ENABLED;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
Expand All @@ -23,6 +24,8 @@
class FeatureFlaggingSystemTest {

@Test
@WithConfig(key = FEATURE_FLAGS_CONFIGURATION_SOURCE, value = "remote_config")
@WithConfig(key = REMOTE_CONFIGURATION_ENABLED, value = "true")
void testFeatureFlagSystemInitialization() {
ConfigurationPoller poller = mock(ConfigurationPoller.class);
DDAgentFeaturesDiscovery discovery = mock(DDAgentFeaturesDiscovery.class);
Expand All @@ -48,6 +51,7 @@ void testFeatureFlagSystemInitialization() {
}

@Test
@WithConfig(key = FEATURE_FLAGS_CONFIGURATION_SOURCE, value = "remote_config")
@WithConfig(key = REMOTE_CONFIGURATION_ENABLED, value = "false")
void testThatRemoteConfigIsRequired() {
SharedCommunicationObjects sharedCommunicationObjects = mock(SharedCommunicationObjects.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ dependencies {
api(libs.moshi)
api(libs.jctools)
api(project(":communication"))
implementation(project(":internal-api"))
Comment thread
leoromanovsky marked this conversation as resolved.
api(project(":products:feature-flagging:feature-flagging-bootstrap"))
api(project(":utils:queue-utils"))

Expand Down
Loading