Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions dd-java-agent/agent-bootstrap/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ dependencies {
api project(':dd-java-agent:agent-debugger:debugger-bootstrap')
api project(':components:environment')
api project(':components:json')
api project(':products:feature-flagging:feature-flagging-bootstrap')
api project(':products:feature-flagging:feature-flagging-config')
api project(':products:metrics:metrics-agent')
api libs.instrument.java
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.FeatureFlaggingGateway;
import datadog.trace.api.featureflag.config.FeatureFlaggingConfig;
import datadog.trace.api.gateway.RequestContextSlot;
import datadog.trace.api.gateway.SubscriptionService;
Expand Down Expand Up @@ -285,6 +286,7 @@ public static void start(
appLogsCollectionEnabled = isFeatureEnabled(AgentFeature.APP_LOGS_COLLECTION);
llmObsEnabled = isFeatureEnabled(AgentFeature.LLMOBS);
featureFlaggingEnabled = isFeatureFlaggingEnabled();
FeatureFlaggingGateway.setProviderInjectionEnabled(featureFlaggingEnabled);

// setup writers when llmobs is enabled to accomodate apm and llmobs
if (llmObsEnabled) {
Expand Down Expand Up @@ -531,6 +533,7 @@ public static void shutdown(final boolean sync) {
stopFlarePoller();
}
if (featureFlaggingEnabled) {
FeatureFlaggingGateway.setProviderInjectionEnabled(false);
shutdownFeatureFlagging(AGENT_CLASSLOADER);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import datadog.trace.api.InstrumenterConfig;
import datadog.trace.api.Platform;
import datadog.trace.api.ProductActivation;
import datadog.trace.api.featureflag.FeatureFlaggingGateway;
import datadog.trace.api.telemetry.IntegrationsCollector;
import datadog.trace.bootstrap.FieldBackedContextAccessor;
import datadog.trace.bootstrap.instrumentation.java.concurrent.ExcludeFilter;
Expand Down Expand Up @@ -328,6 +329,9 @@ public static Set<InstrumenterModule.TargetSystem> getEnabledSystems() {
if (cfg.isCiVisibilityEnabled()) {
enabledSystems.add(InstrumenterModule.TargetSystem.CIVISIBILITY);
}
if (FeatureFlaggingGateway.isProviderInjectionEnabled()) {
enabledSystems.add(InstrumenterModule.TargetSystem.FEATURE_FLAGS);
}
if (cfg.isUsmEnabled()) {
enabledSystems.add(InstrumenterModule.TargetSystem.USM);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public abstract class InstrumenterModule implements Instrumenter {
* <li>{@link TargetSystem#APPSEC appsec}
* <li>{@link TargetSystem#IAST iast}
* <li>{@link TargetSystem#CIVISIBILITY ci-visibility}
* <li>{@link TargetSystem#FEATURE_FLAGS feature-flags}
* <li>{@link TargetSystem#USM usm}
* <li>{@link TargetSystem#CONTEXT_TRACKING context-tracking}
* <li>{@link TargetSystem#RASP rasp}
Expand All @@ -51,6 +52,7 @@ public enum TargetSystem {
APPSEC,
IAST,
CIVISIBILITY,
FEATURE_FLAGS,
USM,
LLMOBS,
CONTEXT_TRACKING,
Expand Down Expand Up @@ -249,6 +251,18 @@ public final boolean isApplicable(Set<TargetSystem> enabledSystems) {
}
}

/** Parent class for all Feature Flags related instrumentations. */
public abstract static class FeatureFlags extends InstrumenterModule {
public FeatureFlags(String instrumentationName, String... additionalNames) {
super(instrumentationName, additionalNames);
}

@Override
public final boolean isApplicable(Set<TargetSystem> enabledSystems) {
return enabledSystems.contains(TargetSystem.FEATURE_FLAGS);
}
}

/** Parent class for all profiling related instrumentations */
public abstract static class Profiling extends InstrumenterModule {
public Profiling(String instrumentationName, String... additionalNames) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
muzzle {
pass {
group = 'dev.openfeature'
module = 'sdk'
versions = '[1.20.1,1.21)'
}
}

apply from: "$rootDir/gradle/java.gradle"

dependencies {
compileOnly group: 'dev.openfeature', name: 'sdk', version: '1.20.1'

implementation(project(':products:feature-flagging:feature-flagging-api')) {
transitive = false
}

testImplementation project(':products:feature-flagging:feature-flagging-bootstrap')
testImplementation group: 'dev.openfeature', name: 'sdk', version: '1.20.1'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package datadog.trace.instrumentation.openfeature;

import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.isStatic;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.returns;
import static net.bytebuddy.matcher.ElementMatchers.takesNoArguments;

import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.agent.tooling.InstrumenterModule;
import dev.openfeature.sdk.OpenFeatureAPI;
import net.bytebuddy.asm.Advice;

@AutoService(InstrumenterModule.class)
public class OpenFeatureAPIInstrumentation extends InstrumenterModule.FeatureFlags
implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice {

public OpenFeatureAPIInstrumentation() {
super("openfeature");
}

@Override
public String instrumentedType() {
return "dev.openfeature.sdk.OpenFeatureAPI";
}

@Override
public String[] helperClassNames() {
return new String[] {
"datadog.trace.api.openfeature.Evaluator",
"datadog.trace.api.openfeature.DDEvaluator$1",
"datadog.trace.api.openfeature.DDEvaluator$FlattenEntry",
"datadog.trace.api.openfeature.DDEvaluator$NumberComparator",
"datadog.trace.api.openfeature.DDEvaluator",
"datadog.trace.api.openfeature.FlagEvalMetrics",
"datadog.trace.api.openfeature.FlagEvalHook",
"datadog.trace.api.openfeature.SpanEnrichmentGate",
"datadog.trace.api.openfeature.SpanEnrichmentHook",
"datadog.trace.api.openfeature.Provider$InitializationState",
"datadog.trace.api.openfeature.Provider$Options",
"datadog.trace.api.openfeature.Provider",
packageName + ".OpenFeatureProviderInstaller",
};
}

@Override
public void methodAdvice(final MethodTransformer transformer) {
transformer.applyAdvice(
isMethod()
.and(isStatic())
.and(named("getInstance"))
.and(takesNoArguments())
.and(returns(named("dev.openfeature.sdk.OpenFeatureAPI"))),
OpenFeatureAPIInstrumentation.class.getName() + "$GetInstanceAdvice");
}

public static class GetInstanceAdvice {
@Advice.OnMethodExit(suppress = Throwable.class)
public static void installProvider(@Advice.Return final OpenFeatureAPI api) {
OpenFeatureProviderInstaller.install(api);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package datadog.trace.instrumentation.openfeature;

import datadog.trace.api.featureflag.FeatureFlaggingGateway;
import datadog.trace.api.openfeature.Provider;
import dev.openfeature.sdk.FeatureProvider;
import dev.openfeature.sdk.NoOpProvider;
import dev.openfeature.sdk.OpenFeatureAPI;

public final class OpenFeatureProviderInstaller {

private static boolean installationComplete;

private OpenFeatureProviderInstaller() {}

public static synchronized void install(final OpenFeatureAPI api) {
if (installationComplete
|| api == null
|| !FeatureFlaggingGateway.isProviderInjectionEnabled()) {
return;
}

final FeatureProvider currentProvider = api.getProvider();
if (currentProvider == null || currentProvider.getClass() != NoOpProvider.class) {
installationComplete = true;
return;
}

api.setProvider(new Provider());
installationComplete = true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import datadog.trace.agent.test.InstrumentationSpecification
import datadog.trace.api.featureflag.FeatureFlaggingGateway
import datadog.trace.api.featureflag.ufc.v1.ServerConfiguration
import dev.openfeature.sdk.Metadata
import dev.openfeature.sdk.NoOpProvider
import dev.openfeature.sdk.OpenFeatureAPI

import static java.util.Collections.emptyMap

class OpenFeatureProviderInjectionTest extends InstrumentationSpecification {

@Override
protected void configurePreAgent() {
super.configurePreAgent()
injectSysConfig("trace.enabled", "false")
injectSysConfig("trace.openfeature.enabled", "true")
FeatureFlaggingGateway.setProviderInjectionEnabled(true)
}

def cleanup() {
FeatureFlaggingGateway.setProviderInjectionEnabled(false)
FeatureFlaggingGateway.dispatch((ServerConfiguration) null)
OpenFeatureAPI.getInstance().shutdown()
}

def "injects once only after explicit activation"() {
given: "the instrumentation loaded for Feature Flags but provider installation is disabled"
FeatureFlaggingGateway.setProviderInjectionEnabled(false)

when: "OpenFeature loads without explicit Feature Flags activation"
def api = OpenFeatureAPI.getInstance()

then:
TRANSFORMED_CLASSES_NAMES.contains("dev.openfeature.sdk.OpenFeatureAPI")
api.provider.class == NoOpProvider

when: "the Java agent enables provider injection"
FeatureFlaggingGateway.dispatch(new ServerConfiguration(null, null, null, emptyMap()))
FeatureFlaggingGateway.setProviderInjectionEnabled(true)
api = OpenFeatureAPI.getInstance()

then:
api.provider.metadata.name == "datadog-openfeature-provider"

when: "application code selects another provider"
def customerProvider = new CustomerProvider()
api.setProvider(customerProvider)
OpenFeatureAPI.getInstance()

then:
api.provider.is(customerProvider)
}

private static final class CustomerProvider extends NoOpProvider {
@Override
Metadata getMetadata() {
return { "customer-provider" }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3520,9 +3520,9 @@ class ConfigTest extends DDSpecification {

where:
value | expected
null | "agentless"
"" | "agentless"
" " | "agentless"
null | null
"" | null
" " | null
" ReMoTe_ConFiG " | "remote_config"
"not-a-real-source" | "not-a-real-source"
" OFFLINE " | "offline"
Expand Down Expand Up @@ -3550,8 +3550,8 @@ class ConfigTest extends DDSpecification {

where:
providerEnabled | source | legacyProviderEnabled | expectedEnabled | expectedSource
null | null | null | true | "agentless"
true | null | null | true | "agentless"
null | null | null | false | null
true | null | null | false | null
null | null | true | true | "remote_config"
null | null | false | false | null
null | "agentless" | true | true | "agentless"
Expand Down
8 changes: 8 additions & 0 deletions metadata/supported-configurations.json
Original file line number Diff line number Diff line change
Expand Up @@ -8553,6 +8553,14 @@
"aliases": ["DD_TRACE_INTEGRATION_OPENAI_JAVA_ENABLED", "DD_INTEGRATION_OPENAI_JAVA_ENABLED"]
}
],
"DD_TRACE_OPENFEATURE_ENABLED": [
{
"version": "A",
"type": "boolean",
"default": "true",
"aliases": ["DD_TRACE_INTEGRATION_OPENFEATURE_ENABLED", "DD_INTEGRATION_OPENFEATURE_ENABLED"]
}
],
"DD_TRACE_OPENSEARCH_ANALYTICS_ENABLED": [
{
"version": "A",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

import datadog.communication.ddagent.SharedCommunicationObjects;
import datadog.trace.api.Config;
import datadog.trace.api.featureflag.FeatureFlaggingGateway;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -17,7 +16,6 @@ public class FeatureFlaggingSystem {
private static volatile ConfigurationSourceService CONFIG_SERVICE;
private static volatile ExposureWriter EXPOSURE_WRITER;
private static volatile SpanEnrichmentWriter SPAN_ENRICHMENT_WRITER;
private static volatile FeatureFlaggingGateway.ActivationListener ACTIVATION_LISTENER;
private static volatile boolean STARTED;

private FeatureFlaggingSystem() {}
Expand All @@ -40,31 +38,6 @@ public static synchronized void start(final SharedCommunicationObjects sco) {
return;
}

if (CONFIGURATION_SOURCE_AGENTLESS.equals(config.getFeatureFlaggingConfigurationSource())) {
final FeatureFlaggingGateway.ActivationListener activationListener =
() -> activateAgentless(sco, config);
ACTIVATION_LISTENER = activationListener;
FeatureFlaggingGateway.addActivationListener(activationListener);
LOGGER.debug("Feature Flagging system awaiting application provider activation");
return;
}

try {
initializeSystem(sco, config);
} catch (final RuntimeException | Error e) {
STARTED = false;
throw e;
}
}

private static synchronized void activateAgentless(
final SharedCommunicationObjects sco, final Config config) {
final FeatureFlaggingGateway.ActivationListener activationListener = ACTIVATION_LISTENER;
if (!STARTED || activationListener == null) {
return;
}
ACTIVATION_LISTENER = null;
FeatureFlaggingGateway.removeActivationListener(activationListener);
try {
initializeSystem(sco, config);
} catch (final RuntimeException | Error e) {
Expand Down Expand Up @@ -134,18 +107,13 @@ static ConfigurationSourceService createConfigurationSourceService(
justification =
"Agent-internal class; Class object does not escape to app code and lock only guards the subsystem lifecycle.")
public static synchronized void stop() {
final FeatureFlaggingGateway.ActivationListener activationListener = ACTIVATION_LISTENER;
final SpanEnrichmentWriter spanEnrichmentWriter = SPAN_ENRICHMENT_WRITER;
final ExposureWriter exposureWriter = EXPOSURE_WRITER;
final ConfigurationSourceService configService = CONFIG_SERVICE;
STARTED = false;
ACTIVATION_LISTENER = null;
SPAN_ENRICHMENT_WRITER = null;
EXPOSURE_WRITER = null;
CONFIG_SERVICE = null;
if (activationListener != null) {
FeatureFlaggingGateway.removeActivationListener(activationListener);
}
try {
if (spanEnrichmentWriter != null) {
spanEnrichmentWriter.close();
Expand All @@ -165,6 +133,6 @@ public static synchronized void stop() {
}

static boolean isAwaitingApplicationActivation() {
return ACTIVATION_LISTENER != null;
return false;
}
}
Loading
Loading