Skip to content

Commit dca0c49

Browse files
cecile75Cecile Terpin
andauthored
Add Otel env var telemetry (#7391)
* first commit * add tests for OtelEnvMetricPeriodicAction * add OtelEnvMetricCollector to build time for GraalVM * use data driven tests --------- Co-authored-by: Cecile Terpin <“[email protected]”>
1 parent ea5731f commit dca0c49

File tree

8 files changed

+439
-1
lines changed

8 files changed

+439
-1
lines changed

dd-java-agent/instrumentation/graal/native-image/src/main/java/datadog/trace/instrumentation/graal/nativeimage/NativeImageGeneratorRunnerInstrumentation.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ public static void onEnter(@Advice.Argument(value = 0, readOnly = false) String[
8181
+ "datadog.trace.api.MethodFilterConfigParser:build_time,"
8282
+ "datadog.trace.api.WithGlobalTracer:build_time,"
8383
+ "datadog.trace.api.PropagationStyle:build_time,"
84+
+ "datadog.trace.api.telemetry.OtelEnvMetricCollector:build_time,"
8485
+ "datadog.trace.api.profiling.ProfilingEnablement:build_time,"
8586
+ "datadog.trace.bootstrap.config.provider.ConfigConverter:build_time,"
8687
+ "datadog.trace.bootstrap.config.provider.ConfigProvider:build_time,"
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package datadog.trace.api.telemetry;
2+
3+
import java.util.ArrayList;
4+
import java.util.Collection;
5+
import java.util.Collections;
6+
import java.util.List;
7+
import java.util.concurrent.ArrayBlockingQueue;
8+
import java.util.concurrent.BlockingQueue;
9+
import org.slf4j.Logger;
10+
import org.slf4j.LoggerFactory;
11+
12+
public class OtelEnvMetricCollector
13+
implements MetricCollector<OtelEnvMetricCollector.OtelEnvMetric> {
14+
private static final Logger log = LoggerFactory.getLogger(OtelEnvMetricCollector.class);
15+
private static final String OTEL_ENV_HIDING_METRIC_NAME = "otel.env.hiding";
16+
private static final String OTEL_ENV_INVALID_METRIC_NAME = "otel.env.invalid";
17+
private static final String OTEL_ENV_UNSUPPORTED_METRIC_NAME = "otel.env.unsupported";
18+
private static final String CONFIG_OTEL_KEY_TAG = "config_opentelemetry:";
19+
private static final String CONFIG_DATADOG_KEY_TAG = "config_datadog:";
20+
private static final String NAMESPACE = "tracers";
21+
private static final OtelEnvMetricCollector INSTANCE = new OtelEnvMetricCollector();
22+
23+
private final BlockingQueue<OtelEnvMetricCollector.OtelEnvMetric> metricsQueue;
24+
25+
private OtelEnvMetricCollector() {
26+
this.metricsQueue = new ArrayBlockingQueue<>(RAW_QUEUE_SIZE);
27+
}
28+
29+
public static OtelEnvMetricCollector getInstance() {
30+
return INSTANCE;
31+
}
32+
33+
public void setHidingOtelEnvVarMetric(String otelName, String ddName) {
34+
setMetricOtelEnvVarMetric(
35+
OTEL_ENV_HIDING_METRIC_NAME,
36+
CONFIG_OTEL_KEY_TAG + otelName,
37+
CONFIG_DATADOG_KEY_TAG + ddName);
38+
}
39+
40+
public void setInvalidOtelEnvVarMetric(String otelName, String ddName) {
41+
setMetricOtelEnvVarMetric(
42+
OTEL_ENV_INVALID_METRIC_NAME,
43+
CONFIG_OTEL_KEY_TAG + otelName,
44+
CONFIG_DATADOG_KEY_TAG + ddName);
45+
}
46+
47+
public void setUnsupportedOtelEnvVarMetric(String otelName) {
48+
setMetricOtelEnvVarMetric(OTEL_ENV_UNSUPPORTED_METRIC_NAME, CONFIG_OTEL_KEY_TAG + otelName);
49+
}
50+
51+
private void setMetricOtelEnvVarMetric(String metricName, final String... tags) {
52+
if (!metricsQueue.offer(
53+
new OtelEnvMetricCollector.OtelEnvMetric(NAMESPACE, true, metricName, "count", 1, tags))) {
54+
log.debug("Unable to add telemetry metric {} for {}", metricName, tags[0]);
55+
}
56+
}
57+
58+
@Override
59+
public void prepareMetrics() {
60+
// Nothing to do here
61+
}
62+
63+
@Override
64+
public Collection<OtelEnvMetricCollector.OtelEnvMetric> drain() {
65+
if (this.metricsQueue.isEmpty()) {
66+
return Collections.emptyList();
67+
}
68+
List<OtelEnvMetricCollector.OtelEnvMetric> drained = new ArrayList<>(this.metricsQueue.size());
69+
this.metricsQueue.drainTo(drained);
70+
return drained;
71+
}
72+
73+
public static class OtelEnvMetric extends MetricCollector.Metric {
74+
public OtelEnvMetric(
75+
String namespace,
76+
boolean common,
77+
String metricName,
78+
String type,
79+
Number value,
80+
final String... tags) {
81+
super(namespace, common, metricName, type, value, tags);
82+
}
83+
}
84+
}

internal-api/src/main/java/datadog/trace/bootstrap/config/provider/OtelEnvironmentConfigSource.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import datadog.trace.api.ConfigOrigin;
1919
import datadog.trace.api.TracePropagationStyle;
20+
import datadog.trace.api.telemetry.OtelEnvMetricCollector;
2021
import datadog.trace.util.Strings;
2122
import java.io.BufferedInputStream;
2223
import java.io.File;
@@ -43,6 +44,8 @@ final class OtelEnvironmentConfigSource extends ConfigProvider.Source {
4344
private final Properties otelConfigFile = loadOtelConfigFile();
4445

4546
private final Properties datadogConfigFile;
47+
private static final OtelEnvMetricCollector otelEnvMetricCollector =
48+
OtelEnvMetricCollector.getInstance();
4649

4750
@Override
4851
protected String get(String key) {
@@ -144,6 +147,8 @@ private String getOtelProperty(String otelSysProp, String ddSysProp) {
144147
Strings.toEnvVar(ddSysProp),
145148
otelEnvVar,
146149
otelEnvVar);
150+
otelEnvMetricCollector.setHidingOtelEnvVarMetric(
151+
Strings.toEnvVarLowerCase(otelSysProp), Strings.toEnvVarLowerCase(ddSysProp));
147152
return null;
148153
}
149154
return otelValue;
@@ -285,6 +290,8 @@ private static String mapPropagationStyle(String propagators) {
285290
buf.append(TracePropagationStyle.valueOfDisplayName(style)).append(',');
286291
} catch (IllegalArgumentException e) {
287292
log.warn("OTEL_PROPAGATORS={} is not supported", style);
293+
otelEnvMetricCollector.setInvalidOtelEnvVarMetric(
294+
"otel_propagators", "dd_trace_propagation_style");
288295
}
289296
}
290297
}
@@ -318,6 +325,8 @@ private String mapSampleRate(String tracesSampler) {
318325
}
319326

320327
log.warn("OTEL_TRACES_SAMPLER={} is not supported", tracesSampler);
328+
otelEnvMetricCollector.setInvalidOtelEnvVarMetric(
329+
"otel_traces_sampler", "dd_trace_sample_rate");
321330
return null;
322331
}
323332

@@ -333,6 +342,8 @@ private String mapDataCollection(String type) {
333342
}
334343

335344
log.warn("OTEL_{}_EXPORTER={} is not supported", type, exporter.toUpperCase(Locale.ROOT));
345+
otelEnvMetricCollector.setUnsupportedOtelEnvVarMetric("otel_" + type + "_exporter");
346+
336347
return null;
337348
}
338349

internal-api/src/main/java/datadog/trace/util/Strings.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ public static String toEnvVar(String string) {
8686
return string.replace('.', '_').replace('-', '_').toUpperCase();
8787
}
8888

89+
public static String toEnvVarLowerCase(String string) {
90+
return string.replace('.', '_').replace('-', '_').toLowerCase();
91+
}
92+
8993
/** com.foo.Bar -> com/foo/Bar.class */
9094
public static String getResourceName(final String className) {
9195
if (!className.endsWith(".class")) {
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
package datadog.trace.api.telemetry
2+
3+
import datadog.trace.test.util.DDSpecification
4+
5+
// PLEASE READ
6+
// When a metric is generated, it's duplicated in these tests. We call twice setupOteEnvironment() because of the separation of the configuration done in the rebuild function of DDSpecification between datadog/trace/api/InstrumenterConfig.java and internal-api/src/main/java/datadog/trace/api/Config.java.
7+
// We are calling ConfigProvider.createDefault()) twice
8+
// We check otel configuration keys only if opentelemetry is enabled using DD_TRACE_OTEL_ENABLED=true or OTEL_SDK_DISABLED=false.
9+
// Note that if DD_TRACE_OTEL_ENABLED is set, its value will overwrite the one in OTEL_SDK_DISABLED.
10+
11+
12+
class OtelEnvMetricCollectorTest extends DDSpecification {
13+
14+
def "otel disabled - no metric"() {
15+
setup:
16+
injectEnvConfig('DD_SERVICE_NAME', 'DD_TEST_SERVICE', false)
17+
injectEnvConfig('OTEL_SERVICE_NAME', 'OTEL_TEST_SERVICE', false)
18+
injectEnvConfig('DD_TRACE_OTEL_ENABLED', 'false', false)
19+
def collector = OtelEnvMetricCollector.getInstance()
20+
21+
when:
22+
collector.prepareMetrics()
23+
def metrics = collector.drain()
24+
25+
then:
26+
metrics.size() == 0
27+
}
28+
29+
def "otel_sdk_disabled - hiding"() {
30+
setup:
31+
injectEnvConfig('DD_TRACE_OTEL_ENABLED', 'true', false)
32+
injectEnvConfig('OTEL_SDK_DISABLED', 'true', false)
33+
def collector = OtelEnvMetricCollector.getInstance()
34+
35+
when:
36+
collector.prepareMetrics()
37+
def metrics = collector.drain()
38+
39+
then:
40+
metrics.size() == 2
41+
metrics[0] == metrics[1]
42+
def hidingMetric = metrics[0]
43+
hidingMetric.type == 'count'
44+
hidingMetric.value == 1
45+
hidingMetric.namespace == 'tracers'
46+
hidingMetric.metricName == 'otel.env.hiding'
47+
hidingMetric.tags.size() == 2
48+
hidingMetric.tags[0] == 'config_opentelemetry:otel_sdk_disabled'
49+
hidingMetric.tags[1] == 'config_datadog:dd_trace_otel_enabled'
50+
}
51+
52+
def "otel_service_name only - no metric"() {
53+
setup:
54+
injectEnvConfig('OTEL_SERVICE_NAME', 'OTEL_TEST_SERVICE', false)
55+
injectEnvConfig('DD_TRACE_OTEL_ENABLED', 'true', false)
56+
def collector = OtelEnvMetricCollector.getInstance()
57+
58+
when:
59+
collector.prepareMetrics()
60+
def metrics = collector.drain()
61+
62+
then:
63+
metrics.size() == 0
64+
}
65+
66+
def "multiple metrics"() {
67+
setup:
68+
injectEnvConfig('DD_SERVICE_NAME', 'DD_TEST_SERVICE', false)
69+
injectEnvConfig('OTEL_SERVICE_NAME', 'OTEL_TEST_SERVICE', false)
70+
injectEnvConfig('OTEL_PROPAGATORS', 'MyStyle', false)
71+
injectEnvConfig('DD_TRACE_OTEL_ENABLED', 'true', false)
72+
def collector = OtelEnvMetricCollector.getInstance()
73+
74+
when:
75+
collector.prepareMetrics()
76+
def metrics = collector.drain()
77+
78+
then:
79+
metrics.size() == 4
80+
}
81+
82+
83+
84+
def "hiding metric"() {
85+
setup:
86+
injectEnvConfig(otelEnvKey, otelEnvValue, false)
87+
injectEnvConfig(ddEnvKey, ddEnvValue, false)
88+
injectEnvConfig('DD_TRACE_OTEL_ENABLED', 'true', false)
89+
def collector = OtelEnvMetricCollector.getInstance()
90+
91+
when:
92+
collector.prepareMetrics()
93+
def metrics = collector.drain()
94+
95+
then:
96+
metrics.size() == 2
97+
metrics[0] == metrics[1]
98+
def hidingMetric = metrics[0]
99+
hidingMetric.type == metricType
100+
hidingMetric.value == metricValue
101+
hidingMetric.namespace == metricNamespace
102+
hidingMetric.metricName == metricName
103+
hidingMetric.tags.size() == 2
104+
hidingMetric.tags[0] == tagsOtelValue
105+
hidingMetric.tags[1] == tagsDdValue
106+
107+
where:
108+
otelEnvKey | otelEnvValue | ddEnvKey | ddEnvValue || metricType | metricValue | metricNamespace | metricName | tagsOtelValue | tagsDdValue
109+
'DD_SERVICE_NAME' | 'DD_TEST_SERVICE' | 'OTEL_SERVICE_NAME' | 'OTEL_TEST_SERVICE' || 'count' | 1 | 'tracers' | 'otel.env.hiding' |'config_opentelemetry:otel_service_name' | 'config_datadog:dd_service_name'
110+
'OTEL_LOG_LEVEL' | 'debug' | 'DD_LOG_LEVEL' | 'INFO' || 'count' | 1 | 'tracers' | 'otel.env.hiding' |'config_opentelemetry:otel_log_level' | 'config_datadog:dd_log_level'
111+
'OTEL_PROPAGATORS' | 'b3' | 'DD_TRACE_PROPAGATION_STYLE' | 'datadog' || 'count' | 1 | 'tracers' | 'otel.env.hiding' |'config_opentelemetry:otel_propagators' | 'config_datadog:dd_trace_propagation_style'
112+
'OTEL_TRACES_SAMPLER' | 'parentbased_always_off' | 'DD_TRACE_SAMPLE_RATE' | '1.0' || 'count' | 1 | 'tracers' | 'otel.env.hiding' |'config_opentelemetry:otel_traces_sampler' | 'config_datadog:dd_trace_sample_rate'
113+
'OTEL_INSTRUMENTATION_HTTP_CLIENT_CAPTURE_REQUEST_HEADERS' | 'My-OtelHeader' | 'DD_TRACE_REQUEST_HEADER_TAGS' | 'My-DDHeader' || 'count' | 1 | 'tracers' | 'otel.env.hiding' |'config_opentelemetry:otel_instrumentation_http_client_capture_request_headers' | 'config_datadog:dd_trace_request_header_tags'
114+
'OTEL_INSTRUMENTATION_HTTP_SERVER_CAPTURE_RESPONSE_HEADERS' | 'My-OtelHeader' | 'DD_TRACE_RESPONSE_HEADER_TAGS' | 'My-DDHeader' || 'count' | 1 | 'tracers' | 'otel.env.hiding' |'config_opentelemetry:otel_instrumentation_http_server_capture_response_headers' | 'config_datadog:dd_trace_response_header_tags'
115+
'OTEL_JAVAAGENT_EXTENSIONS' | '/opt/opentelemetry/extensions' | 'DD_TRACE_EXTENSIONS_PATH' |'/opt/datadog/extensions' || 'count' | 1 | 'tracers' | 'otel.env.hiding' |'config_opentelemetry:otel_javaagent_extensions' | 'config_datadog:dd_trace_extensions_path'
116+
117+
// Although DD env vars take precedence as expected, no warning/telemetry is outputted when there is conflict between envvars in the below tests
118+
// as the code to compare these variables is not implemented in the OtelEnvironmentConfigSource
119+
//'OTEL_LOG_LEVEL' | 'info' | 'DD_TRACE_DEBUG' | 'true' || 'count' | 1 | 'tracers' | 'otel.env.hiding' |'config_opentelemetry:otel_log_level' | 'config_datadog:dd_trace_debug'
120+
//'DD_SERVICE' | 'DD_TEST_SERVICE' | 'OTEL_SERVICE_NAME' | 'OTEL_TEST_SERVICE' || 'count' | 1 | 'tracers' | 'otel.env.hiding' |'config_opentelemetry:otel_service_name' | 'config_datadog:dd_service'
121+
//'OTEL_TRACES_EXPORTER' | 'none' | 'DD_TRACE_ENABLED' | 'true' || 'count' | 1 | 'tracers' | 'otel.env.hiding' |'config_opentelemetry:otel_traces_exporter' | 'config_datadog:dd_trace_enabled'
122+
//'OTEL_METRICS_EXPORTER' | 'none' | 'DD_RUNTIME_METRICS_ENABLED' | 'true' || 'count' | 1 | 'tracers' | 'otel.env.hiding' |'config_opentelemetry:otel_metrics_exporter' | 'config_datadog:dd_runtime_metrics_enabled'
123+
}
124+
125+
//Not included in data driven test as "=" is not supported in the environment variable name of the Environment map
126+
def "otel_resource_attributes - hiding metric"() {
127+
setup:
128+
injectEnvConfig('OTEL_RESOURCE_ATTRIBUTES', 'env=oteltest,version=0.0.1', false)
129+
injectEnvConfig('DD_TAGS', 'env=ddtest,version=0.0.2', false)
130+
injectEnvConfig('DD_TRACE_OTEL_ENABLED', 'true', false)
131+
def collector = OtelEnvMetricCollector.getInstance()
132+
133+
when:
134+
collector.prepareMetrics()
135+
def metrics = collector.drain()
136+
137+
then:
138+
metrics.size() == 2
139+
metrics[0] == metrics[1]
140+
def hidingMetric = metrics[0]
141+
hidingMetric.type == 'count'
142+
hidingMetric.value == 1
143+
hidingMetric.namespace == 'tracers'
144+
hidingMetric.metricName == 'otel.env.hiding'
145+
hidingMetric.tags.size() == 2
146+
hidingMetric.tags[0] == 'config_opentelemetry:otel_resource_attributes'
147+
hidingMetric.tags[1] == 'config_datadog:dd_tags'
148+
}
149+
150+
def "invalid metric"() {
151+
setup:
152+
injectEnvConfig(otelEnvKey, otelEnvValue, false)
153+
injectEnvConfig('DD_TRACE_OTEL_ENABLED', 'true', false)
154+
def collector = OtelEnvMetricCollector.getInstance()
155+
156+
when:
157+
collector.prepareMetrics()
158+
def metrics = collector.drain()
159+
160+
then:
161+
metrics.size() == 2
162+
metrics[0] == metrics[1]
163+
def invalidMetric = metrics[0]
164+
invalidMetric.type == metricType
165+
invalidMetric.value == metricValue
166+
invalidMetric.namespace == metricNamespace
167+
invalidMetric.metricName == metricName
168+
invalidMetric.tags.size() == 2
169+
invalidMetric.tags[0] == tagsOtelValue
170+
invalidMetric.tags[1] == tagsDdValue
171+
172+
where:
173+
otelEnvKey | otelEnvValue || metricType | metricValue | metricNamespace | metricName | tagsOtelValue | tagsDdValue
174+
'OTEL_PROPAGATORS' | 'StyleUnknown' || 'count' | 1 | 'tracers' | 'otel.env.invalid' | 'config_opentelemetry:otel_propagators' | 'config_datadog:dd_trace_propagation_style'
175+
'OTEL_TRACES_SAMPLER' | 'newrate' || 'count' | 1 | 'tracers' | 'otel.env.invalid' | 'config_opentelemetry:otel_traces_sampler' | 'config_datadog:dd_trace_sample_rate'
176+
}
177+
178+
def "unsupported metric"() {
179+
setup:
180+
injectEnvConfig(otelEnvKey, otelEnvValue, false)
181+
injectEnvConfig('DD_TRACE_OTEL_ENABLED', 'true', false)
182+
def collector = OtelEnvMetricCollector.getInstance()
183+
184+
when:
185+
collector.prepareMetrics()
186+
def metrics = collector.drain()
187+
188+
then:
189+
metrics.size() == 2
190+
metrics[0] == metrics[1]
191+
def unsupportedMetric = metrics[0]
192+
unsupportedMetric.type == metricType
193+
unsupportedMetric.value == metricValue
194+
unsupportedMetric.namespace == metricNamespace
195+
unsupportedMetric.metricName == metricName
196+
unsupportedMetric.tags.size() == 1
197+
unsupportedMetric.tags[0] == tagsOtelValue
198+
199+
where:
200+
otelEnvKey | otelEnvValue || metricType | metricValue | metricNamespace | metricName | tagsOtelValue
201+
'OTEL_METRICS_EXPORTER' | 'otlp' || 'count' | 1 | 'tracers' | 'otel.env.unsupported' | 'config_opentelemetry:otel_metrics_exporter'
202+
'OTEL_TRACES_EXPORTER' | 'otlp' || 'count' | 1 | 'tracers' | 'otel.env.unsupported' | 'config_opentelemetry:otel_traces_exporter'
203+
'OTEL_LOGS_EXPORTER' | 'otlp' || 'count' | 1 | 'tracers' | 'otel.env.unsupported' | 'config_opentelemetry:otel_logs_exporter'
204+
}
205+
206+
}

telemetry/src/main/java/datadog/telemetry/TelemetrySystem.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import datadog.telemetry.metric.CiVisibilityMetricPeriodicAction;
1111
import datadog.telemetry.metric.CoreMetricsPeriodicAction;
1212
import datadog.telemetry.metric.IastMetricPeriodicAction;
13+
import datadog.telemetry.metric.OtelEnvMetricPeriodicAction;
1314
import datadog.telemetry.metric.WafMetricPeriodicAction;
1415
import datadog.telemetry.products.ProductChangeAction;
1516
import datadog.trace.api.Config;
@@ -44,10 +45,10 @@ static Thread createTelemetryRunnable(
4445
DependencyService dependencyService,
4546
boolean telemetryMetricsEnabled) {
4647
DEPENDENCY_SERVICE = dependencyService;
47-
4848
List<TelemetryPeriodicAction> actions = new ArrayList<>();
4949
if (telemetryMetricsEnabled) {
5050
actions.add(new CoreMetricsPeriodicAction());
51+
actions.add(new OtelEnvMetricPeriodicAction());
5152
actions.add(new IntegrationPeriodicAction());
5253
actions.add(new WafMetricPeriodicAction());
5354
if (Verbosity.OFF != Config.get().getIastTelemetryVerbosity()) {
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package datadog.telemetry.metric;
2+
3+
import datadog.trace.api.telemetry.MetricCollector;
4+
import datadog.trace.api.telemetry.OtelEnvMetricCollector;
5+
import edu.umd.cs.findbugs.annotations.NonNull;
6+
7+
public class OtelEnvMetricPeriodicAction extends MetricPeriodicAction {
8+
@Override
9+
@NonNull
10+
public MetricCollector collector() {
11+
return OtelEnvMetricCollector.getInstance();
12+
}
13+
}

0 commit comments

Comments
 (0)