Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
30 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
1c4a223
fix(feature-flags): document retry policy confinement
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 @@ -528,6 +528,9 @@ public static void shutdown(final boolean sync) {
if (flareEnabled) {
stopFlarePoller();
}
if (featureFlaggingEnabled) {
shutdownFeatureFlagging(AGENT_CLASSLOADER);
}

if (agentlessLogSubmissionEnabled) {
shutdownLogsIntake();
Expand Down Expand Up @@ -1179,6 +1182,20 @@ private static void maybeStartFeatureFlagging(final Class<?> scoClass, final Obj
}
}

static void shutdownFeatureFlagging(final ClassLoader agentClassLoader) {
if (agentClassLoader == null) {
return;
}
try {
final Class<?> ffSysClass =
agentClassLoader.loadClass("com.datadog.featureflag.FeatureFlaggingSystem");
final Method stopMethod = ffSysClass.getMethod("stop");
stopMethod.invoke(null);
} catch (final Throwable e) {
log.warn("Unable to stop Feature Flagging subsystem", e);
}
}

private static void maybeInstallLogsIntake(Class<?> scoClass, Object sco) {
if (agentlessLogSubmissionEnabled || appLogsCollectionEnabled) {
StaticEventLogger.begin("Logs Intake");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package datadog.trace.bootstrap;

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.util.concurrent.atomic.AtomicInteger;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class AgentFeatureFlaggingLifecycleTest {

@BeforeEach
void reset() {
FakeFeatureFlaggingSystem.stopCalls.set(0);
}

@Test
void shutdownInvokesFeatureFlaggingSystemStopThroughAgentClassLoader() {
final ClassLoader classLoader =
new ClassLoader(null) {
@Override
public Class<?> loadClass(final String name) throws ClassNotFoundException {
if ("com.datadog.featureflag.FeatureFlaggingSystem".equals(name)) {
return FakeFeatureFlaggingSystem.class;
}
return super.loadClass(name);
}
};

Agent.shutdownFeatureFlagging(classLoader);

assertEquals(1, FakeFeatureFlaggingSystem.stopCalls.get());
}

@Test
void shutdownIsNoopBeforeAgentClassLoaderExists() {
Agent.shutdownFeatureFlagging(null);

assertEquals(0, FakeFeatureFlaggingSystem.stopCalls.get());
}

public static final class FakeFeatureFlaggingSystem {
private static final AtomicInteger stopCalls = new AtomicInteger();

public static void stop() {
stopCalls.incrementAndGet();
}
}
}
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 @@ -48,6 +48,9 @@ public final class ConfigDefaults {

static final boolean DEFAULT_INJECT_DATADOG_ATTRIBUTE = true;
static final String DEFAULT_SITE = "datadoghq.com";
static final String DEFAULT_FEATURE_FLAGGING_CONFIGURATION_SOURCE = "agentless";
static final int DEFAULT_FEATURE_FLAGGING_CONFIGURATION_SOURCE_POLL_INTERVAL_SECONDS = 30;
static final int DEFAULT_FEATURE_FLAGGING_CONFIGURATION_SOURCE_REQUEST_TIMEOUT_SECONDS = 2;

static final boolean DEFAULT_CODE_ORIGIN_FOR_SPANS_INTERFACE_SUPPORT = false;
static final int DEFAULT_CODE_ORIGIN_MAX_USER_FRAMES = 8;
Expand Down
1 change: 1 addition & 0 deletions internal-api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ dependencies {
api(project(":components:context"))
api(project(":components:environment"))
api(project(":components:json"))
api(project(":products:feature-flagging:feature-flagging-config"))
Comment thread
leoromanovsky marked this conversation as resolved.
Outdated
api(project(":utils:config-utils"))
api(project(":utils:time-utils"))

Expand Down
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 @@ -723,6 +726,10 @@
import static datadog.trace.api.config.TracerConfig.WRITER_BAGGAGE_INJECT;
import static datadog.trace.api.config.TracerConfig.WRITER_LINKS_INJECT;
import static datadog.trace.api.config.TracerConfig.WRITER_TYPE;
import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.FEATURE_FLAGS_CONFIGURATION_SOURCE;
import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL;
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.telemetry.LogCollector.SEND_TELEMETRY;
import static datadog.trace.bootstrap.instrumentation.api.WriterConstants.OTLP_WRITER_TYPE;
import static datadog.trace.util.CollectionUtils.tryMakeImmutableList;
Expand Down Expand Up @@ -1214,6 +1221,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 @@ -2839,6 +2851,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 @@ -3751,6 +3797,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 @@ -4668,6 +4722,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 @@ -6500,6 +6570,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,9 @@ 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.featureflag.config.FeatureFlaggingConfig.FEATURE_FLAGS_CONFIGURATION_SOURCE
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.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 +3483,53 @@ 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 "feature flag configuration source normalizes #value to #expected"() {
setup:
Properties properties = new Properties()
if (value != null) {
properties.setProperty(FEATURE_FLAGS_CONFIGURATION_SOURCE, value)
}

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

then:
config.featureFlaggingConfigurationSource == expected

where:
value | expected
null | "agentless"
"" | "agentless"
" " | "agentless"
" ReMoTe_ConFiG " | "remote_config"
}

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
Loading