Skip to content

Commit 9435c54

Browse files
committed
Use own SdkMeterProvider with OTLP HTTP exporter for eval metrics
Replace GlobalOpenTelemetry.getMeterProvider() with a dedicated SdkMeterProvider + OtlpHttpMetricExporter that sends metrics directly to the DD Agent's OTLP endpoint (default :4318/v1/metrics). This avoids the agent's OTel class shading issue where the agent relocates io.opentelemetry.api.* to datadog.trace.bootstrap.otel.api.*, making GlobalOpenTelemetry calls from the dd-openfeature jar hit the unshaded no-op provider instead of the agent's shim. Requires opentelemetry-sdk-metrics and opentelemetry-exporter-otlp on the application classpath. Falls back to no-op if absent. System tests: 11/17 pass. 6 failures are pre-existing DDEvaluator gaps (reason mapping, parse errors, type mismatch strictness).
1 parent fe4c153 commit 9435c54

File tree

2 files changed

+46
-4
lines changed

2 files changed

+46
-4
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,13 @@ dependencies {
4545

4646
compileOnly(project(":products:feature-flagging:feature-flagging-bootstrap"))
4747
compileOnly("io.opentelemetry:opentelemetry-api:1.47.0")
48+
compileOnly("io.opentelemetry:opentelemetry-sdk-metrics:1.47.0")
49+
compileOnly("io.opentelemetry:opentelemetry-exporter-otlp:1.47.0")
4850

4951
testImplementation(project(":products:feature-flagging:feature-flagging-bootstrap"))
5052
testImplementation("io.opentelemetry:opentelemetry-api:1.47.0")
53+
testImplementation("io.opentelemetry:opentelemetry-sdk-metrics:1.47.0")
54+
testImplementation("io.opentelemetry:opentelemetry-exporter-otlp:1.47.0")
5155
testImplementation(libs.bundles.junit5)
5256
testImplementation(libs.bundles.mockito)
5357
testImplementation(libs.moshi)

products/feature-flagging/feature-flagging-api/src/main/java/datadog/trace/api/openfeature/FlagEvalMetrics.java

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,27 @@
11
package datadog.trace.api.openfeature;
22

33
import dev.openfeature.sdk.ErrorCode;
4-
import io.opentelemetry.api.GlobalOpenTelemetry;
54
import io.opentelemetry.api.common.AttributeKey;
65
import io.opentelemetry.api.common.Attributes;
76
import io.opentelemetry.api.common.AttributesBuilder;
87
import io.opentelemetry.api.metrics.LongCounter;
98
import io.opentelemetry.api.metrics.Meter;
9+
import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter;
10+
import io.opentelemetry.sdk.metrics.SdkMeterProvider;
11+
import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader;
12+
import java.io.Closeable;
13+
import java.time.Duration;
1014

11-
class FlagEvalMetrics {
15+
class FlagEvalMetrics implements Closeable {
1216

1317
private static final String METER_NAME = "ddtrace.openfeature";
1418
private static final String METRIC_NAME = "feature_flag.evaluations";
1519
private static final String METRIC_UNIT = "{evaluation}";
1620
private static final String METRIC_DESC = "Number of feature flag evaluations";
21+
private static final Duration EXPORT_INTERVAL = Duration.ofSeconds(10);
22+
23+
private static final String DEFAULT_ENDPOINT = "http://localhost:4318/v1/metrics";
24+
private static final String ENDPOINT_ENV = "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT";
1725

1826
private static final AttributeKey<String> ATTR_FLAG_KEY =
1927
AttributeKey.stringKey("feature_flag.key");
@@ -26,25 +34,41 @@ class FlagEvalMetrics {
2634
AttributeKey.stringKey("feature_flag.result.allocation_key");
2735

2836
private volatile LongCounter counter;
37+
private volatile SdkMeterProvider meterProvider;
2938

3039
FlagEvalMetrics() {
3140
try {
32-
Meter meter = GlobalOpenTelemetry.getMeterProvider().meterBuilder(METER_NAME).build();
41+
String endpoint = System.getenv(ENDPOINT_ENV);
42+
if (endpoint == null || endpoint.isEmpty()) {
43+
endpoint = DEFAULT_ENDPOINT;
44+
}
45+
46+
OtlpHttpMetricExporter exporter =
47+
OtlpHttpMetricExporter.builder().setEndpoint(endpoint).build();
48+
49+
PeriodicMetricReader reader =
50+
PeriodicMetricReader.builder(exporter).setInterval(EXPORT_INTERVAL).build();
51+
52+
meterProvider = SdkMeterProvider.builder().registerMetricReader(reader).build();
53+
54+
Meter meter = meterProvider.meterBuilder(METER_NAME).build();
3355
counter =
3456
meter
3557
.counterBuilder(METRIC_NAME)
3658
.setUnit(METRIC_UNIT)
3759
.setDescription(METRIC_DESC)
3860
.build();
3961
} catch (NoClassDefFoundError | Exception e) {
40-
// OTel API not on classpath or initialization failed — counter stays null (no-op)
62+
// OTel SDK not on classpath or initialization failed — counter stays null (no-op)
4163
counter = null;
64+
meterProvider = null;
4265
}
4366
}
4467

4568
/** Package-private constructor for testing with a mock counter. */
4669
FlagEvalMetrics(LongCounter counter) {
4770
this.counter = counter;
71+
this.meterProvider = null;
4872
}
4973

5074
void record(
@@ -74,7 +98,21 @@ void record(
7498
}
7599
}
76100

101+
@Override
102+
public void close() {
103+
shutdown();
104+
}
105+
77106
void shutdown() {
78107
counter = null;
108+
SdkMeterProvider mp = meterProvider;
109+
if (mp != null) {
110+
meterProvider = null;
111+
try {
112+
mp.close();
113+
} catch (Exception e) {
114+
// Ignore shutdown errors
115+
}
116+
}
79117
}
80118
}

0 commit comments

Comments
 (0)