Skip to content

Commit 0388db6

Browse files
Initial implementation of the OpenFeature SDK
1 parent 8db72c0 commit 0388db6

File tree

50 files changed

+3750
-2
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+3750
-2
lines changed

.github/CODEOWNERS

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,10 @@ dd-trace-core/src/test/groovy/datadog/trace/llmobs/ @DataDog/ml-observability
139139
/internal-api/src/test/groovy/datadog/trace/api/rum/ @DataDog/rum
140140
/telemetry/src/main/java/datadog/telemetry/rum/ @DataDog/rum
141141
/telemetry/src/test/groovy/datadog/telemetry/rum/ @DataDog/rum
142+
143+
# @DataDog/feature-flagging-and-experimentation-sdk
144+
/dd-java-agent/agent-featureflag/ @DataDog/feature-flagging-and-experimentation-sdk
145+
/dd-smoke-tests/featureflag/ @DataDog/feature-flagging-and-experimentation-sdk
146+
/internal-api/src/main/java/datadog/trace/api/featureflag @DataDog/feature-flagging-and-experimentation-sdk
147+
/internal-api/src/test/groovy/datadog/trace/api/featureflag @DataDog/feature-flagging-and-experimentation-sdk
148+
/products/openfeature/ @DataDog/feature-flagging-and-experimentation-sdk

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

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import datadog.trace.api.config.CrashTrackingConfig;
3333
import datadog.trace.api.config.CwsConfig;
3434
import datadog.trace.api.config.DebuggerConfig;
35+
import datadog.trace.api.config.FeatureFlagConfig;
3536
import datadog.trace.api.config.GeneralConfig;
3637
import datadog.trace.api.config.IastConfig;
3738
import datadog.trace.api.config.JmxFetchConfig;
@@ -125,7 +126,8 @@ private enum AgentFeature {
125126
DATA_JOBS(GeneralConfig.DATA_JOBS_ENABLED, false),
126127
AGENTLESS_LOG_SUBMISSION(GeneralConfig.AGENTLESS_LOG_SUBMISSION_ENABLED, false),
127128
LLMOBS(LlmObsConfig.LLMOBS_ENABLED, false),
128-
LLMOBS_AGENTLESS(LlmObsConfig.LLMOBS_AGENTLESS_ENABLED, false);
129+
LLMOBS_AGENTLESS(LlmObsConfig.LLMOBS_AGENTLESS_ENABLED, false),
130+
FEATURE_FLAG(FeatureFlagConfig.FLAGGING_PROVIDER_ENABLED, false);
129131

130132
private final String configKey;
131133
private final String systemProp;
@@ -184,6 +186,7 @@ public boolean isEnabledByDefault() {
184186
private static boolean codeOriginEnabled = false;
185187
private static boolean distributedDebuggerEnabled = false;
186188
private static boolean agentlessLogSubmissionEnabled = false;
189+
private static boolean featureFlagEnabled = false;
187190

188191
private static void safelySetContextClassLoader(ClassLoader classLoader) {
189192
try {
@@ -268,6 +271,7 @@ public static void start(
268271
codeOriginEnabled = isFeatureEnabled(AgentFeature.CODE_ORIGIN);
269272
agentlessLogSubmissionEnabled = isFeatureEnabled(AgentFeature.AGENTLESS_LOG_SUBMISSION);
270273
llmObsEnabled = isFeatureEnabled(AgentFeature.LLMOBS);
274+
featureFlagEnabled = isFeatureEnabled(AgentFeature.FEATURE_FLAG);
271275

272276
// setup writers when llmobs is enabled to accomodate apm and llmobs
273277
if (llmObsEnabled) {
@@ -662,6 +666,7 @@ public void execute() {
662666
maybeStartDebugger(instrumentation, scoClass, sco);
663667
maybeStartRemoteConfig(scoClass, sco);
664668
maybeStartAiGuard();
669+
maybeStartFeatureFlag(scoClass, sco);
665670

666671
if (telemetryEnabled) {
667672
startTelemetry(instrumentation, scoClass, sco);
@@ -1083,6 +1088,23 @@ private static void maybeStartLLMObs(Instrumentation inst, Class<?> scoClass, Ob
10831088
}
10841089
}
10851090

1091+
private static void maybeStartFeatureFlag(final Class<?> scoClass, final Object sco) {
1092+
if (featureFlagEnabled) {
1093+
StaticEventLogger.begin("Feature Flag");
1094+
1095+
try {
1096+
final Class<?> ffSysClass =
1097+
AGENT_CLASSLOADER.loadClass("com.datadog.featureflag.FeatureFlagSystem");
1098+
final Method ffSysMethod = ffSysClass.getMethod("start", scoClass);
1099+
ffSysMethod.invoke(null, sco);
1100+
} catch (final Throwable e) {
1101+
log.warn("Not starting Feature Flag subsystem", e);
1102+
}
1103+
1104+
StaticEventLogger.end("Feature Flag");
1105+
}
1106+
}
1107+
10861108
private static void maybeInstallLogsIntake(Class<?> scoClass, Object sco) {
10871109
if (agentlessLogSubmissionEnabled) {
10881110
StaticEventLogger.begin("Logs Intake");
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
2+
3+
plugins {
4+
id 'com.gradleup.shadow'
5+
}
6+
7+
apply from: "$rootDir/gradle/java.gradle"
8+
apply from: "$rootDir/gradle/version.gradle"
9+
10+
java {
11+
sourceCompatibility = JavaVersion.VERSION_1_8
12+
targetCompatibility = JavaVersion.VERSION_1_8
13+
}
14+
15+
excludedClassesCoverage += [
16+
// POJOs
17+
'com.datadog.featureflag.ExposureCache.Key',
18+
'com.datadog.featureflag.ExposureCache.Value'
19+
]
20+
21+
dependencies {
22+
api libs.slf4j
23+
implementation libs.moshi
24+
implementation libs.jctools
25+
26+
api project(':dd-trace-api')
27+
compileOnly project(':dd-trace-core')
28+
implementation project(':internal-api')
29+
implementation project(':communication')
30+
31+
testImplementation project(':utils:test-utils')
32+
testImplementation project(':dd-java-agent:testing')
33+
}
34+
35+
tasks.named("shadowJar", ShadowJar) {
36+
dependencies deps.excludeShared
37+
}
38+
39+
tasks.named("jar", Jar) {
40+
archiveClassifier = 'unbundled'
41+
}
42+
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.datadog.featureflag;
2+
3+
import datadog.trace.api.featureflag.exposure.ExposureEvent;
4+
import java.util.Objects;
5+
6+
public interface ExposureCache {
7+
8+
boolean add(ExposureEvent event);
9+
10+
Value get(Key key);
11+
12+
int size();
13+
14+
final class Key {
15+
public final String flag;
16+
public final String subject;
17+
18+
public Key(final ExposureEvent event) {
19+
this.flag = event.flag == null ? null : event.flag.key;
20+
this.subject = event.subject == null ? null : event.subject.id;
21+
}
22+
23+
@Override
24+
public boolean equals(final Object o) {
25+
if (o == null || getClass() != o.getClass()) {
26+
return false;
27+
}
28+
final Key key = (Key) o;
29+
return Objects.equals(flag, key.flag) && Objects.equals(subject, key.subject);
30+
}
31+
32+
@Override
33+
public int hashCode() {
34+
return Objects.hash(flag, subject);
35+
}
36+
}
37+
38+
final class Value {
39+
public final String variant;
40+
public final String allocation;
41+
42+
public Value(final ExposureEvent event) {
43+
this.variant = event.variant == null ? null : event.variant.key;
44+
this.allocation = event.allocation == null ? null : event.allocation.key;
45+
}
46+
47+
@Override
48+
public boolean equals(final Object o) {
49+
if (o == null || getClass() != o.getClass()) {
50+
return false;
51+
}
52+
final Value value = (Value) o;
53+
return Objects.equals(variant, value.variant) && Objects.equals(allocation, value.allocation);
54+
}
55+
56+
@Override
57+
public int hashCode() {
58+
return Objects.hash(variant, allocation);
59+
}
60+
}
61+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.datadog.featureflag;
2+
3+
import datadog.trace.api.featureflag.FeatureFlagGateway;
4+
5+
/**
6+
* Defines an exposure writer responsible for sending exposure events to the EVP proxy.
7+
* Implementations should use a background thread to perform these operations asynchronously.
8+
*/
9+
public interface ExposureWriter extends AutoCloseable, FeatureFlagGateway.ExposureListener {
10+
11+
void init();
12+
13+
void close();
14+
}

0 commit comments

Comments
 (0)