Skip to content

Commit

Permalink
Enable OTEL Env parametric tests for Java. This required adding speci…
Browse files Browse the repository at this point in the history
…al mappings for propagation style names and to set DD_TRACE_OTEL_ENABLED=true in all test cases to run the Java code.

Since the tests require a new GetTraceConfig endpoint, this was implemented as part of this commit. Note that there's an obscene amount of reflection to get the required tracer settings from the internal config object. Any and all feedback is welcome to improve the maintainability of this endpoint.
  • Loading branch information
zacharycmontoya committed Jul 23, 2024
1 parent a7afad9 commit 9d1880c
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 17 deletions.
2 changes: 1 addition & 1 deletion manifests/java.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1054,7 +1054,7 @@ tests/:
TestDynamicConfigV2: v1.31.0
test_otel_api_interoperability.py: missing_feature
test_otel_env_vars.py:
Test_Otel_Env_Vars: missing_feature
Test_Otel_Env_Vars: v1.35.2
test_otel_sdk_interoperability.py: missing_feature
test_span_links.py: missing_feature
test_telemetry.py:
Expand Down
45 changes: 33 additions & 12 deletions tests/parametric/test_otel_env_vars.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,13 @@ def test_dd_env_var_take_precedence(self, test_agent, test_library):
assert "baz:qux" in tags
assert "foo:otel_bar" not in tags
assert "baz:otel_qux" not in tags
assert resp["dd_trace_propagation_style"] == "b3,tracecontext"
assert resp["dd_trace_debug"] == "false"

if context.library != "java":
assert resp["dd_trace_propagation_style"] == "b3,tracecontext"
else:
assert resp["dd_trace_propagation_style"] == "b3multi,tracecontext"

if context.library != "php":
assert resp["dd_runtime_metrics_enabled"]

Expand All @@ -60,6 +64,7 @@ def test_dd_env_var_take_precedence(self, test_agent, test_library):
"OTEL_METRICS_EXPORTER": "none",
"OTEL_RESOURCE_ATTRIBUTES": "foo=bar1,baz=qux1",
"OTEL_PROPAGATORS": "b3,tracecontext",
"DD_TRACE_OTEL_ENABLED": "true",
}
],
)
Expand All @@ -76,6 +81,8 @@ def test_otel_env_vars_set(self, test_agent, test_library):

if context.library in ("dotnet", "php"):
assert resp["dd_trace_propagation_style"] == "b3 single header,tracecontext"
elif context.library == "java":
assert resp["dd_trace_propagation_style"] == "b3single,tracecontext"
else:
assert resp["dd_trace_propagation_style"] == "b3,tracecontext"

Expand All @@ -85,6 +92,7 @@ def test_otel_env_vars_set(self, test_agent, test_library):
@missing_feature(context.library == "python", reason="DD_LOG_LEVEL is not supported in Python")
@missing_feature(context.library == "dotnet", reason="DD_LOG_LEVEL is not supported in .NET")
@missing_feature(context.library == "ruby", reason="DD_LOG_LEVEL is not supported in ruby")
@missing_feature(context.library == "java", reason="OTEL_LOG_LEVEL doesn't update DD_LOG_LEVEL")
@pytest.mark.parametrize("library_env", [{"OTEL_LOG_LEVEL": "error"}])
def test_otel_log_level_env(self, test_agent, test_library):
with test_library as t:
Expand All @@ -96,7 +104,8 @@ def test_otel_log_level_env(self, test_agent, test_library):
"library_env",
[
{
"OTEL_RESOURCE_ATTRIBUTES": "deployment.environment=test1,service.name=test2,service.version=5,foo=bar1,baz=qux1"
"OTEL_RESOURCE_ATTRIBUTES": "deployment.environment=test1,service.name=test2,service.version=5,foo=bar1,baz=qux1",
"DD_TRACE_OTEL_ENABLED": "true",
}
],
)
Expand All @@ -114,20 +123,21 @@ def test_otel_attribute_mapping(self, test_agent, test_library):
@missing_feature(
context.library <= "php@1.1.0", reason="The always_on sampler mapping is properly implemented in v1.2.0"
)
@pytest.mark.parametrize("library_env", [{"OTEL_TRACES_SAMPLER": "always_on",}])
@pytest.mark.parametrize("library_env", [{"OTEL_TRACES_SAMPLER": "always_on", "DD_TRACE_OTEL_ENABLED": "true"}])
def test_otel_traces_always_on(self, test_agent, test_library):
with test_library as t:
resp = t.get_tracer_config()
assert float(resp["dd_trace_sample_rate"]) == 1.0

@pytest.mark.parametrize("library_env", [{"OTEL_TRACES_SAMPLER": "always_off",}])
@pytest.mark.parametrize("library_env", [{"OTEL_TRACES_SAMPLER": "always_off", "DD_TRACE_OTEL_ENABLED": "true"}])
def test_otel_traces_always_off(self, test_agent, test_library):
with test_library as t:
resp = t.get_tracer_config()
assert float(resp["dd_trace_sample_rate"]) == 0.0

@pytest.mark.parametrize(
"library_env", [{"OTEL_TRACES_SAMPLER": "traceidratio", "OTEL_TRACES_SAMPLER_ARG": "0.1"}],
"library_env",
[{"OTEL_TRACES_SAMPLER": "traceidratio", "OTEL_TRACES_SAMPLER_ARG": "0.1", "DD_TRACE_OTEL_ENABLED": "true"}],
)
def test_otel_traces_traceidratio(self, test_agent, test_library):
with test_library as t:
Expand All @@ -137,30 +147,39 @@ def test_otel_traces_traceidratio(self, test_agent, test_library):
@missing_feature(
context.library <= "php@1.1.0", reason="The always_on sampler mapping is properly implemented in v1.2.0"
)
@pytest.mark.parametrize("library_env", [{"OTEL_TRACES_SAMPLER": "parentbased_always_on",}])
@pytest.mark.parametrize(
"library_env", [{"OTEL_TRACES_SAMPLER": "parentbased_always_on", "DD_TRACE_OTEL_ENABLED": "true"}]
)
def test_otel_traces_parentbased_on(self, test_agent, test_library):
with test_library as t:
resp = t.get_tracer_config()
assert float(resp["dd_trace_sample_rate"]) == 1.0

@pytest.mark.parametrize(
"library_env", [{"OTEL_TRACES_SAMPLER": "parentbased_always_off",}],
"library_env", [{"OTEL_TRACES_SAMPLER": "parentbased_always_off", "DD_TRACE_OTEL_ENABLED": "true"}],
)
def test_otel_traces_parentbased_off(self, test_agent, test_library):
with test_library as t:
resp = t.get_tracer_config()
assert float(resp["dd_trace_sample_rate"]) == 0.0

@pytest.mark.parametrize(
"library_env", [{"OTEL_TRACES_SAMPLER": "parentbased_traceidratio", "OTEL_TRACES_SAMPLER_ARG": "0.1"}],
"library_env",
[
{
"OTEL_TRACES_SAMPLER": "parentbased_traceidratio",
"OTEL_TRACES_SAMPLER_ARG": "0.1",
"DD_TRACE_OTEL_ENABLED": "true",
}
],
)
def test_otel_traces_parentbased_ratio(self, test_agent, test_library):
with test_library as t:
resp = t.get_tracer_config()
assert float(resp["dd_trace_sample_rate"]) == 0.1

@pytest.mark.parametrize(
"library_env", [{"OTEL_TRACES_EXPORTER": "none"}],
"library_env", [{"OTEL_TRACES_EXPORTER": "none", "DD_TRACE_OTEL_ENABLED": "true"}],
)
def test_otel_traces_exporter_none(self, test_agent, test_library):
with test_library as t:
Expand All @@ -171,7 +190,7 @@ def test_otel_traces_exporter_none(self, test_agent, test_library):
context.library == "php",
reason="PHP uses DD_TRACE_DEBUG to set DD_TRACE_LOG_LEVEL=debug, so it does not do this mapping in the reverse direction",
)
@pytest.mark.parametrize("library_env", [{"OTEL_LOG_LEVEL": "debug"}])
@pytest.mark.parametrize("library_env", [{"OTEL_LOG_LEVEL": "debug", "DD_TRACE_OTEL_ENABLED": "true"}])
def test_otel_log_level_to_debug_mapping(self, test_agent, test_library):
with test_library as t:
resp = t.get_tracer_config()
Expand Down Expand Up @@ -202,7 +221,7 @@ def test_otel_sdk_disabled_set(self, test_agent, test_library):
@missing_feature(
True, reason="dd_trace_sample_ignore_parent requires an RFC, this feature is not implemented in any language"
)
@pytest.mark.parametrize("library_env", [{"OTEL_TRACES_SAMPLER": "always_on"}])
@pytest.mark.parametrize("library_env", [{"OTEL_TRACES_SAMPLER": "always_on", "DD_TRACE_OTEL_ENABLED": "true"}])
def test_dd_trace_sample_ignore_parent_true(self, test_agent, test_library):
with test_library as t:
resp = t.get_tracer_config()
Expand All @@ -211,7 +230,9 @@ def test_dd_trace_sample_ignore_parent_true(self, test_agent, test_library):
@missing_feature(
True, reason="dd_trace_sample_ignore_parent requires an RFC, this feature is not implemented in any language"
)
@pytest.mark.parametrize("library_env", [{"OTEL_TRACES_SAMPLER": "parentbased_always_off"}])
@pytest.mark.parametrize(
"library_env", [{"OTEL_TRACES_SAMPLER": "parentbased_always_off", "DD_TRACE_OTEL_ENABLED": "true"}]
)
def test_dd_trace_sample_ignore_parent_false(self, test_agent, test_library):
with test_library as t:
resp = t.get_tracer_config()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

import com.datadoghq.client.APMClientGrpc;
import com.datadoghq.client.ApmTestClient;
import com.datadoghq.client.ApmTestClient.GetTraceConfigArgs;
import com.datadoghq.client.ApmTestClient.GetTraceConfigReturn;
import com.datadoghq.client.ApmTestClient.OtelEndSpanArgs;
import com.datadoghq.client.ApmTestClient.OtelEndSpanReturn;
import com.datadoghq.client.ApmTestClient.OtelFlushSpansArgs;
Expand Down Expand Up @@ -89,7 +91,10 @@ public void injectHeaders(InjectHeadersArgs request, StreamObserver<InjectHeader
public void flushSpans(FlushSpansArgs request, StreamObserver<FlushSpansReturn> responseObserver) {
LOGGER.info("Flushing OT spans: {}", request);
try {
((InternalTracer) this.ddTracer).flush();
// Only flush spans when tracing was enabled
if (this.ddTracer instanceof InternalTracer) {
((InternalTracer) this.ddTracer).flush();
}
this.otClient.clearSpans();
responseObserver.onNext(FlushSpansReturn.newBuilder().build());
responseObserver.onCompleted();
Expand All @@ -103,7 +108,10 @@ public void flushSpans(FlushSpansArgs request, StreamObserver<FlushSpansReturn>
public void flushTraceStats(FlushTraceStatsArgs request, StreamObserver<FlushTraceStatsReturn> responseObserver) {
LOGGER.info("Flushing OT trace stats: {}", request);
try {
((InternalTracer) this.ddTracer).flushMetrics();
// Only flush trace stats when tracing was enabled
if (this.ddTracer instanceof InternalTracer) {
((InternalTracer) this.ddTracer).flushMetrics();
}
responseObserver.onNext(FlushTraceStatsReturn.newBuilder().build());
responseObserver.onCompleted();
} catch (Throwable t) {
Expand All @@ -112,6 +120,11 @@ public void flushTraceStats(FlushTraceStatsArgs request, StreamObserver<FlushTra
}
}

@Override
public void getTraceConfig(GetTraceConfigArgs request, StreamObserver<GetTraceConfigReturn> responseObserver) {
this.otelClient.getTraceConfig(request, responseObserver);
}

@Override
public void otelStartSpan(OtelStartSpanArgs request, StreamObserver<OtelStartSpanReturn> responseObserver) {
this.otelClient.otelStartSpan(request, responseObserver);
Expand Down Expand Up @@ -151,7 +164,10 @@ public void otelSetAttributes(OtelSetAttributesArgs request, StreamObserver<Otel
public void otelFlushSpans(OtelFlushSpansArgs request, StreamObserver<OtelFlushSpansReturn> responseObserver) {
LOGGER.info("Flushing OTel spans: {}", request);
try {
((InternalTracer) this.ddTracer).flush();
// Only flush spans when tracing was enabled
if (this.ddTracer instanceof InternalTracer) {
((InternalTracer) this.ddTracer).flush();
}
this.otelClient.clearSpans();
responseObserver.onNext(OtelFlushSpansReturn.newBuilder().setSuccess(true).build());
responseObserver.onCompleted();
Expand All @@ -165,7 +181,10 @@ public void otelFlushSpans(OtelFlushSpansArgs request, StreamObserver<OtelFlushS
public void otelFlushTraceStats(OtelFlushTraceStatsArgs request, StreamObserver<OtelFlushTraceStatsReturn> responseObserver) {
LOGGER.info("Flushing OTel trace stats: {}", request);
try {
((InternalTracer) this.ddTracer).flushMetrics();
// Only flush trace stats when tracing was enabled
if (this.ddTracer instanceof InternalTracer) {
((InternalTracer) this.ddTracer).flushMetrics();
}
responseObserver.onNext(OtelFlushTraceStatsReturn.newBuilder().build());
responseObserver.onCompleted();
} catch (Throwable t) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import com.datadoghq.client.APMClientGrpc;
import com.datadoghq.client.ApmTestClient;
import com.datadoghq.client.ApmTestClient.DistributedHTTPHeaders;
import com.datadoghq.client.ApmTestClient.GetTraceConfigArgs;
import com.datadoghq.client.ApmTestClient.GetTraceConfigReturn;
import com.datadoghq.client.ApmTestClient.ListVal;
import com.datadoghq.client.ApmTestClient.OtelEndSpanArgs;
import com.datadoghq.client.ApmTestClient.OtelEndSpanReturn;
Expand All @@ -27,6 +29,7 @@
import com.datadoghq.client.ApmTestClient.SpanLink;
import datadog.trace.api.DDSpanId;
import datadog.trace.api.DDTraceId;
import datadog.trace.api.TracePropagationStyle;
import io.grpc.stub.StreamObserver;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.common.Attributes;
Expand All @@ -40,10 +43,14 @@
import io.opentelemetry.context.Context;
import io.opentelemetry.context.propagation.TextMapGetter;
import io.opentelemetry.context.propagation.TextMapPropagator;
import java.lang.reflect.Method;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import java.util.HashMap;
import java.util.Set;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

public class OpenTelemetryClient extends APMClientGrpc.APMClientImplBase {
Expand Down Expand Up @@ -143,6 +150,81 @@ private static SpanKind parseSpanKindNumber(long spanKindNumber) {
}
}

@Override
public void getTraceConfig(GetTraceConfigArgs request, StreamObserver<GetTraceConfigReturn> responseObserver) {
LOGGER.info("Getting tracer config");
try
{
// Use reflection to get the static Config instance
Class configClass = Class.forName("datadog.trace.api.Config");
Method getConfigMethod = configClass.getMethod("get");

Class instrumenterConfigClass = Class.forName("datadog.trace.api.InstrumenterConfig");
Method getInstrumenterConfigMethod = instrumenterConfigClass.getMethod("get");

Object configObject = getConfigMethod.invoke(null);
Object instrumenterConfigObject = getInstrumenterConfigMethod.invoke(null);

Method getServiceName = configClass.getMethod("getServiceName");
Method getEnv = configClass.getMethod("getEnv");
Method getVersion = configClass.getMethod("getVersion");
Method getTraceSampleRate = configClass.getMethod("getTraceSampleRate");
Method isTraceEnabled = configClass.getMethod("isTraceEnabled");
Method isRuntimeMetricsEnabled = configClass.getMethod("isRuntimeMetricsEnabled");
Method getGlobalTags = configClass.getMethod("getGlobalTags");
Method getTracePropagationStylesToInject = configClass.getMethod("getTracePropagationStylesToInject");
Method isDebugEnabled = configClass.getMethod("isDebugEnabled");
Method getLogLevel = configClass.getMethod("getLogLevel");

Method isTraceOtelEnabled = instrumenterConfigClass.getMethod("isTraceOtelEnabled");

Map<String, String> configMap = new HashMap<>();
configMap.put("dd_service", getServiceName.invoke(configObject).toString());
configMap.put("dd_env", getEnv.invoke(configObject).toString());
configMap.put("dd_version", getVersion.invoke(configObject).toString());
configMap.put("dd_log_level", Optional.ofNullable(getLogLevel.invoke(configObject)).map(Object::toString).orElse(null));
configMap.put("dd_trace_enabled", isTraceEnabled.invoke(configObject).toString());
configMap.put("dd_runtime_metrics_enabled", isRuntimeMetricsEnabled.invoke(configObject).toString());
configMap.put("dd_trace_debug", isDebugEnabled.invoke(configObject).toString());
configMap.put("dd_trace_otel_enabled", isTraceOtelEnabled.invoke(instrumenterConfigObject).toString());
// configMap.put("dd_trace_sample_ignore_parent", Config.get());

Object sampleRate = getTraceSampleRate.invoke(configObject);
if (sampleRate instanceof Double) {
configMap.put("dd_trace_sample_rate", String.valueOf((Double)sampleRate));
}

Object globalTags = getGlobalTags.invoke(configObject);
if (globalTags != null) {
String result = ((Map<String, String>)globalTags).entrySet()
.stream()
.map(entry -> entry.getKey() + ":" + entry.getValue())
.collect(Collectors.joining(","));

configMap.put("dd_tags", result);
}

Object propagationStyles = getTracePropagationStylesToInject.invoke(configObject);
if (propagationStyles != null) {
String result = ((Set<TracePropagationStyle>)propagationStyles)
.stream()
.map(style -> style.toString())
.collect(Collectors.joining(","));

configMap.put("dd_trace_propagation_style", result);
}

configMap.values().removeIf(Objects::isNull);
responseObserver.onNext(GetTraceConfigReturn.newBuilder()
.putAllConfig(configMap)
.build());
responseObserver.onCompleted();
} catch (Throwable t) {
LOGGER.error("Uncaught throwable", t);
responseObserver.onError(t);
}
}

@Override
public void otelStartSpan(OtelStartSpanArgs request, StreamObserver<OtelStartSpanReturn> responseObserver) {
LOGGER.info("Starting OTel span: {}", request);
Expand Down

0 comments on commit 9d1880c

Please sign in to comment.