From 376c39b5802a43bc4d56bafa37560fb49d5217f5 Mon Sep 17 00:00:00 2001 From: Bruce Bujon Date: Fri, 10 Nov 2023 08:33:13 +0100 Subject: [PATCH] Add span operation naming convention (#6104) * feat: Add span operation naming convention * feat: Update OTel smoke tests --- .../OpenTelemetryInstrumentation.java | 2 + .../OpenTelemetryContextInstrumentation.java | 2 + ...elemetryContextStorageInstrumentation.java | 2 + .../trace/OtelConventions.java | 230 ++++++++++++++++++ .../opentelemetry14/trace/OtelSpan.java | 5 +- .../trace/OtelSpanBuilder.java | 19 +- .../opentelemetry14/trace/OtelTracer.java | 6 +- .../OpenTelemetry14ConventionsTest.groovy | 169 +++++++++++++ .../test/groovy/OpenTelemetry14Test.groovy | 46 ++-- .../context/ContextTest.groovy | 13 +- .../propagation/AbstractPropagatorTest.groovy | 5 +- .../app/actions/AbstractAction.java | 8 +- .../app/filters/AbstractFilter.java | 15 +- .../smoketest/Play28OTelSmokeTest.groovy | 20 +- 14 files changed, 477 insertions(+), 65 deletions(-) create mode 100644 dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/trace/OtelConventions.java create mode 100644 dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/test/groovy/OpenTelemetry14ConventionsTest.groovy diff --git a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/OpenTelemetryInstrumentation.java b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/OpenTelemetryInstrumentation.java index 35f0425cb90..02b3bd9b12d 100644 --- a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/OpenTelemetryInstrumentation.java +++ b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/OpenTelemetryInstrumentation.java @@ -64,6 +64,8 @@ public String[] helperClassNames() { packageName + ".context.propagation.AgentTextMapPropagator", packageName + ".context.propagation.OtelContextPropagators", packageName + ".trace.OtelExtractedContext", + packageName + ".trace.OtelConventions", + packageName + ".trace.OtelConventions$1", packageName + ".trace.OtelSpan", packageName + ".trace.OtelSpan$1", packageName + ".trace.OtelSpan$NoopSpan", diff --git a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/context/OpenTelemetryContextInstrumentation.java b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/context/OpenTelemetryContextInstrumentation.java index df0676a5d6d..c8e5a6c193b 100644 --- a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/context/OpenTelemetryContextInstrumentation.java +++ b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/context/OpenTelemetryContextInstrumentation.java @@ -56,6 +56,8 @@ public String[] helperClassNames() { packageName + ".OtelContext", packageName + ".OtelScope", ROOT_PACKAGE_NAME + ".trace.OtelExtractedContext", + ROOT_PACKAGE_NAME + ".trace.OtelConventions", + ROOT_PACKAGE_NAME + ".trace.OtelConventions$1", ROOT_PACKAGE_NAME + ".trace.OtelSpan", ROOT_PACKAGE_NAME + ".trace.OtelSpan$1", ROOT_PACKAGE_NAME + ".trace.OtelSpan$NoopSpan", diff --git a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/context/OpenTelemetryContextStorageInstrumentation.java b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/context/OpenTelemetryContextStorageInstrumentation.java index b7130cd55a3..a5e0537e074 100644 --- a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/context/OpenTelemetryContextStorageInstrumentation.java +++ b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/context/OpenTelemetryContextStorageInstrumentation.java @@ -62,6 +62,8 @@ public String[] helperClassNames() { packageName + ".OtelContext", packageName + ".OtelScope", ROOT_PACKAGE_NAME + ".trace.OtelExtractedContext", + ROOT_PACKAGE_NAME + ".trace.OtelConventions", + ROOT_PACKAGE_NAME + ".trace.OtelConventions$1", ROOT_PACKAGE_NAME + ".trace.OtelSpan", ROOT_PACKAGE_NAME + ".trace.OtelSpan$1", ROOT_PACKAGE_NAME + ".trace.OtelSpan$NoopSpan", diff --git a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/trace/OtelConventions.java b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/trace/OtelConventions.java new file mode 100644 index 00000000000..03dba691ee9 --- /dev/null +++ b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/trace/OtelConventions.java @@ -0,0 +1,230 @@ +package datadog.trace.instrumentation.opentelemetry14.trace; + +import static datadog.trace.api.DDTags.ANALYTICS_SAMPLE_RATE; +import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND_CLIENT; +import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND_CONSUMER; +import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND_PRODUCER; +import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND_SERVER; +import static io.opentelemetry.api.trace.SpanKind.CLIENT; +import static io.opentelemetry.api.trace.SpanKind.CONSUMER; +import static io.opentelemetry.api.trace.SpanKind.INTERNAL; +import static io.opentelemetry.api.trace.SpanKind.PRODUCER; +import static io.opentelemetry.api.trace.SpanKind.SERVER; +import static java.util.Locale.ROOT; + +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import io.opentelemetry.api.trace.SpanKind; +import javax.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class OtelConventions { + /** The Datadog span default operation name. */ + public static final String DEFAULT_OPERATION_NAME = "otel_unknown"; + + private static final Logger LOGGER = LoggerFactory.getLogger(OtelConventions.class); + private static final String OPERATION_NAME_SPECIFIC_ATTRIBUTE = "operation.name"; + private static final String SERVICE_NAME_SPECIFIC_ATTRIBUTE = "service.name"; + private static final String RESOURCE_NAME_SPECIFIC_ATTRIBUTE = "resource.name"; + private static final String SPAN_TYPE_SPECIFIC_ATTRIBUTES = "span.type"; + private static final String ANALYTICS_EVENT_SPECIFIC_ATTRIBUTES = "analytics.event"; + private static final String SPAN_KIND_INTERNAL = "internal"; + + private OtelConventions() {} + + /** + * Convert OpenTelemetry {@link SpanKind} to Datadog span type. + * + * @param spanKind The OpenTelemetry span kind to convert. + * @return The related Datadog span type. + */ + public static String toSpanType(SpanKind spanKind) { + switch (spanKind) { + case CLIENT: + return SPAN_KIND_CLIENT; + case SERVER: + return SPAN_KIND_SERVER; + case PRODUCER: + return SPAN_KIND_PRODUCER; + case CONSUMER: + return SPAN_KIND_CONSUMER; + case INTERNAL: + return SPAN_KIND_INTERNAL; + default: + return spanKind.toString().toLowerCase(ROOT); + } + } + + /** + * Convert Datadog span type to OpenTelemetry {@link SpanKind}. + * + * @param spanType The span type to convert. + * @return The related OpenTelemetry {@link SpanKind}. + */ + public static SpanKind toSpanKind(String spanType) { + if (spanType == null) { + return INTERNAL; + } + switch (spanType) { + case SPAN_KIND_CLIENT: + return CLIENT; + case SPAN_KIND_SERVER: + return SERVER; + case SPAN_KIND_PRODUCER: + return PRODUCER; + case SPAN_KIND_CONSUMER: + return CONSUMER; + default: + return INTERNAL; + } + } + + public static void applyConventions(AgentSpan span) { + String serviceName = getStringAttribute(span, SERVICE_NAME_SPECIFIC_ATTRIBUTE); + if (serviceName != null) { + span.setServiceName(serviceName); + } + String resourceName = getStringAttribute(span, RESOURCE_NAME_SPECIFIC_ATTRIBUTE); + if (resourceName != null) { + span.setResourceName(resourceName); + } + String spanType = getStringAttribute(span, SPAN_TYPE_SPECIFIC_ATTRIBUTES); + if (spanType != null) { + span.setSpanType(spanType); + } + Boolean analyticsEvent = getBooleanAttribute(span, ANALYTICS_EVENT_SPECIFIC_ATTRIBUTES); + if (analyticsEvent != null) { + span.setMetric(ANALYTICS_SAMPLE_RATE, analyticsEvent ? 1 : 0); + } + applyOperationName(span); + } + + private static void applyOperationName(AgentSpan span) { + String operationName = getStringAttribute(span, OPERATION_NAME_SPECIFIC_ATTRIBUTE); + if (operationName == null) { + operationName = computeOperationName(span); + } + span.setOperationName(operationName.toLowerCase(ROOT)); + } + + private static String computeOperationName(AgentSpan span) { + SpanKind spanKind = toSpanKind(span.getSpanType()); + /* + * HTTP convention: https://opentelemetry.io/docs/specs/otel/trace/semantic_conventions/http/ + */ + String httpRequestMethod = getStringAttribute(span, "http.request.method"); + if (spanKind == SERVER && httpRequestMethod != null) { + return "http.server.request"; + } + if (spanKind == CLIENT && httpRequestMethod != null) { + return "http.client.request"; + } + /* + * Database convention: https://opentelemetry.io/docs/specs/semconv/database/database-spans/ + */ + String dbSystem = getStringAttribute(span, "db.system"); + if (spanKind == CLIENT && dbSystem != null) { + return dbSystem + ".query"; + } + /* + * Messaging: https://opentelemetry.io/docs/specs/semconv/messaging/messaging-spans/ + */ + String messagingSystem = getStringAttribute(span, "messaging.system"); + String messagingOperation = getStringAttribute(span, "messaging.operation"); + if ((spanKind == CONSUMER || spanKind == PRODUCER || spanKind == CLIENT || spanKind == SERVER) + && messagingSystem != null + && messagingOperation != null) { + return messagingSystem + "." + messagingOperation; + } + /* + * AWS: https://opentelemetry.io/docs/specs/semconv/cloud-providers/aws-sdk/ + */ + String rpcSystem = getStringAttribute(span, "rpc.system"); + if (spanKind == CLIENT && "aws-api".equals(rpcSystem)) { + String service = getStringAttribute(span, "rpc.service"); + if (service == null) { + return "aws.client.request"; + } else { + return "aws." + service + ".request"; + } + } + /* + * RPC: https://opentelemetry.io/docs/specs/semconv/rpc/rpc-spans/ + */ + if (spanKind == CLIENT && rpcSystem != null) { + return rpcSystem + ".client.request"; + } + if (spanKind == SERVER && rpcSystem != null) { + return rpcSystem + ".server.request"; + } + /* + * FaaS: + * https://opentelemetry.io/docs/specs/semconv/faas/faas-spans/#incoming-faas-span-attributes + * https://opentelemetry.io/docs/specs/semconv/faas/faas-spans/#outgoing-invocations + */ + String faasInvokedProvider = getStringAttribute(span, "faas.invoked_provider"); + String faasInvokedName = getStringAttribute(span, "faas.invoked_name"); + if (spanKind == CLIENT && faasInvokedProvider != null && faasInvokedName != null) { + return faasInvokedProvider + "." + faasInvokedName + ".invoke"; + } + String faasTrigger = getStringAttribute(span, "faas.trigger"); + if (spanKind == SERVER && faasTrigger != null) { + return faasTrigger + ".invoke"; + } + /* + * GraphQL: https://opentelemetry.io/docs/specs/otel/trace/semantic_conventions/instrumentation/graphql/ + */ + String graphqlOperationType = getStringAttribute(span, "graphql.operation.type"); + if (graphqlOperationType != null) { + return "graphql.server.request"; + } + /* + * Generic server / client: https://opentelemetry.io/docs/specs/semconv/http/http-spans/ + */ + String networkProtocolName = getStringAttribute(span, "network.protocol.name"); + if (spanKind == SERVER) { + return networkProtocolName == null + ? "server.request" + : networkProtocolName + ".server.request"; + } + if (spanKind == CLIENT) { + return networkProtocolName == null + ? "client.request" + : networkProtocolName + ".client.request"; + } + // Fallback if no convention match + if (span.getSpanType() != null) { + return spanKind.name(); + } else { + return DEFAULT_OPERATION_NAME; + } + } + + @Nullable + private static String getStringAttribute(AgentSpan span, String key) { + Object tag = span.getTag(key); + if (tag == null) { + return null; + } else if (!(tag instanceof String)) { + LOGGER.debug("Span attributes {} is not a string", key); + return key; + } + return (String) tag; + } + + @Nullable + private static Boolean getBooleanAttribute(AgentSpan span, String key) { + Object tag = span.getTag(key); + if (tag == null) { + return null; + } + if (tag instanceof Boolean) { + return (Boolean) tag; + } else if (tag instanceof String) { + return Boolean.parseBoolean((String) tag); + } else { + LOGGER.debug("Span attributes {} is not a boolean", key); + return null; + } + } +} diff --git a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/trace/OtelSpan.java b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/trace/OtelSpan.java index 1d4cb72aa16..9845e531558 100644 --- a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/trace/OtelSpan.java +++ b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/trace/OtelSpan.java @@ -1,6 +1,7 @@ package datadog.trace.instrumentation.opentelemetry14.trace; import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; +import static datadog.trace.instrumentation.opentelemetry14.trace.OtelConventions.applyConventions; import static io.opentelemetry.api.trace.StatusCode.ERROR; import static io.opentelemetry.api.trace.StatusCode.OK; import static io.opentelemetry.api.trace.StatusCode.UNSET; @@ -106,7 +107,7 @@ public Span recordException(Throwable exception, Attributes additionalAttributes @Override public Span updateName(String name) { if (this.recording) { - this.delegate.setOperationName(name); + this.delegate.setResourceName(name); } return this; } @@ -114,12 +115,14 @@ public Span updateName(String name) { @Override public void end() { this.recording = false; + applyConventions(this.delegate); this.delegate.finish(); } @Override public void end(long timestamp, TimeUnit unit) { this.recording = false; + applyConventions(this.delegate); this.delegate.finish(unit.toMicros(timestamp)); } diff --git a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/trace/OtelSpanBuilder.java b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/trace/OtelSpanBuilder.java index b39118edcc9..aca54bda20b 100644 --- a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/trace/OtelSpanBuilder.java +++ b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/trace/OtelSpanBuilder.java @@ -1,10 +1,10 @@ package datadog.trace.instrumentation.opentelemetry14.trace; +import static datadog.trace.instrumentation.opentelemetry14.trace.OtelConventions.toSpanType; import static datadog.trace.instrumentation.opentelemetry14.trace.OtelExtractedContext.extract; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; import datadog.trace.bootstrap.instrumentation.api.AgentTracer; -import datadog.trace.bootstrap.instrumentation.api.Tags; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.Span; @@ -13,7 +13,6 @@ import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.context.Context; import java.util.List; -import java.util.Locale; import java.util.concurrent.TimeUnit; import javax.annotation.ParametersAreNonnullByDefault; @@ -110,22 +109,6 @@ public SpanBuilder setSpanKind(SpanKind spanKind) { return this; } - private static String toSpanType(SpanKind spanKind) { - switch (spanKind) { - case CLIENT: - return Tags.SPAN_KIND_CLIENT; - case SERVER: - return Tags.SPAN_KIND_SERVER; - case PRODUCER: - return Tags.SPAN_KIND_PRODUCER; - case CONSUMER: - return Tags.SPAN_KIND_CONSUMER; - default: - case INTERNAL: - return spanKind.toString().toLowerCase(Locale.ROOT); - } - } - @Override public SpanBuilder setStartTimestamp(long startTimestamp, TimeUnit unit) { this.delegate.withStartTimestamp(unit.toMicros(startTimestamp)); diff --git a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/trace/OtelTracer.java b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/trace/OtelTracer.java index 0ce1f69bbd9..9d551550063 100644 --- a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/trace/OtelTracer.java +++ b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/trace/OtelTracer.java @@ -1,5 +1,7 @@ package datadog.trace.instrumentation.opentelemetry14.trace; +import static datadog.trace.instrumentation.opentelemetry14.trace.OtelConventions.DEFAULT_OPERATION_NAME; + import datadog.trace.bootstrap.instrumentation.api.AgentTracer; import io.opentelemetry.api.trace.SpanBuilder; import io.opentelemetry.api.trace.Tracer; @@ -19,7 +21,9 @@ public OtelTracer(String instrumentationScopeName) { @Override public SpanBuilder spanBuilder(String spanName) { AgentTracer.SpanBuilder delegate = - this.tracer.buildSpan(INSTRUMENTATION_NAME, spanName).withResourceName(spanName); + this.tracer + .buildSpan(INSTRUMENTATION_NAME, DEFAULT_OPERATION_NAME) + .withResourceName(spanName); return new OtelSpanBuilder(delegate); } } diff --git a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/test/groovy/OpenTelemetry14ConventionsTest.groovy b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/test/groovy/OpenTelemetry14ConventionsTest.groovy new file mode 100644 index 00000000000..558e09162b4 --- /dev/null +++ b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/test/groovy/OpenTelemetry14ConventionsTest.groovy @@ -0,0 +1,169 @@ +import datadog.trace.agent.test.AgentTestRunner +import datadog.trace.api.DDTags +import io.opentelemetry.api.GlobalOpenTelemetry +import io.opentelemetry.context.Context +import io.opentelemetry.context.ThreadLocalContextStorage +import spock.lang.Subject + +import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND_CLIENT +import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND_SERVER +import static datadog.trace.instrumentation.opentelemetry14.trace.OtelConventions.DEFAULT_OPERATION_NAME +import static datadog.trace.instrumentation.opentelemetry14.trace.OtelConventions.toSpanType +import static io.opentelemetry.api.trace.SpanKind.CLIENT +import static io.opentelemetry.api.trace.SpanKind.CONSUMER +import static io.opentelemetry.api.trace.SpanKind.INTERNAL +import static io.opentelemetry.api.trace.SpanKind.PRODUCER +import static io.opentelemetry.api.trace.SpanKind.SERVER + +class OpenTelemetry14ConventionsTest extends AgentTestRunner { + @Subject + def tracer = GlobalOpenTelemetry.get().tracerProvider.get("conventions") + + @Override + void configurePreAgent() { + super.configurePreAgent() + + injectSysConfig("dd.integration.opentelemetry.experimental.enabled", "true") + } + + def "test span name conventions"() { + when: + def builder = tracer.spanBuilder("some-name") + if (kind != null) { + builder.setSpanKind(kind) + } + attributes.forEach { key, value -> builder.setAttribute(key, value) } + builder.startSpan() + .end() + + then: + assertTraces(1) { + trace(1) { + span { + parent() + if (kind != null) { + spanType toSpanType(kind) + } + operationName "$expectedOperationName" + resourceName "some-name" + } + } + } + + where: + kind | attributes | expectedOperationName + // Fallback behavior + null | [:] | DEFAULT_OPERATION_NAME + // Internal spans + INTERNAL | [:] | "internal" + // Server spans + SERVER | [:] | "server.request" + SERVER | ["http.request.method": "GET"] | "http.server.request" + SERVER | ["http.request.method": "GET"] | "http.server.request" + SERVER | ["network.protocol.name": "amqp"] | "amqp.server.request" + // Client spans + CLIENT | [:] | "client.request" + CLIENT | ["http.request.method": "GET"] | "http.client.request" + CLIENT | ["db.system": "mysql"] | "mysql.query" + CLIENT | ["network.protocol.name": "amqp"] | "amqp.client.request" + CLIENT | ["network.protocol.name": "AMQP"] | "amqp.client.request" + // Messaging spans + PRODUCER | [:] | "producer" + CONSUMER | [:] | "consumer" + CONSUMER | ["messaging.system": "rabbitmq", "messaging.operation": "publish"] | "rabbitmq.publish" + PRODUCER | ["messaging.system": "rabbitmq", "messaging.operation": "publish"] | "rabbitmq.publish" + CLIENT | ["messaging.system": "rabbitmq", "messaging.operation": "publish"] | "rabbitmq.publish" + SERVER | ["messaging.system": "rabbitmq", "messaging.operation": "publish"] | "rabbitmq.publish" + // RPC spans + CLIENT | ["rpc.system": "grpc"] | "grpc.client.request" + SERVER | ["rpc.system": "grpc"] | "grpc.server.request" + CLIENT | ["rpc.system": "aws-api"] | "aws.client.request" + CLIENT | ["rpc.system": "aws-api", "rpc.service": "helloworld"] | "aws.helloworld.request" + SERVER | ["rpc.system": "aws-api"] | "aws-api.server.request" + // FAAS spans + CLIENT | ["faas.invoked_provider": "alibaba_cloud", "faas.invoked_name": "my-function"] | "alibaba_cloud.my-function.invoke" + SERVER | ["faas.trigger": "datasource"] | "datasource.invoke" + // GraphQL spans + INTERNAL | ["graphql.operation.type": "query"] | "graphql.server.request" + null | ["graphql.operation.type": "query"] | "graphql.server.request" + // User override + CLIENT | ["db.system": "mysql", "operation.name": "db.query"] | "db.query" + CLIENT | ["db.system": "mysql", "operation.name": "DB.query"] | "db.query" + } + + def "test span specific tags"() { + when: + tracer.spanBuilder("some-name") + .setAttribute("service.name", "my-service") + .setAttribute("resource.name", "/my-resource") + .setAttribute("span.type", "$type") + .startSpan() + .end() + + + then: + assertTraces(1) { + trace(1) { + span { + parent() + spanType "$type" + operationName "$expectedOperationName" + resourceName "/my-resource" + serviceName "my-service" + } + } + } + + where: + type | expectedOperationName + SPAN_KIND_SERVER | "server.request" + SPAN_KIND_CLIENT | "client.request" + } + + def "test span analytics.event specific tag"() { + when: + tracer.spanBuilder("some-name") + .setAttribute("analytics.event", value) + .startSpan() + .end() + + + then: + assertTraces(1) { + trace(1) { + span { + parent() + operationName "$DEFAULT_OPERATION_NAME" + tags { + defaultTags() + if (value != null) { + "analytics.event" value + "$DDTags.ANALYTICS_SAMPLE_RATE" expectedMetric + } + } + } + } + } + + where: + value | expectedMetric + true | 1 + Boolean.TRUE | 1 + false | 0 + Boolean.FALSE | 0 + null | 0 // Not used + "true" | 1 + "false" | 0 + "TRUE" | 1 + "something-else" | 0 + "" | 0 + } + + @Override + void cleanup() { + // Test for context leak + assert Context.current() == Context.root() + // Safely reset OTel context storage + ThreadLocalContextStorage.THREAD_LOCAL_STORAGE.remove() + } +} diff --git a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/test/groovy/OpenTelemetry14Test.groovy b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/test/groovy/OpenTelemetry14Test.groovy index ca0952048e5..fb529889e59 100644 --- a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/test/groovy/OpenTelemetry14Test.groovy +++ b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/test/groovy/OpenTelemetry14Test.groovy @@ -10,6 +10,9 @@ import spock.lang.Subject import java.security.InvalidParameterException +import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND_SERVER +import static datadog.trace.instrumentation.opentelemetry14.trace.OtelConventions.DEFAULT_OPERATION_NAME +import static io.opentelemetry.api.trace.SpanKind.SERVER import static io.opentelemetry.api.trace.StatusCode.ERROR import static io.opentelemetry.api.trace.StatusCode.OK import static io.opentelemetry.api.trace.StatusCode.UNSET @@ -41,12 +44,12 @@ class OpenTelemetry14Test extends AgentTestRunner { trace(2) { span { parent() - operationName "some-name" + operationName DEFAULT_OPERATION_NAME resourceName "some-name" } span { childOfPrevious() - operationName "other-name" + operationName DEFAULT_OPERATION_NAME resourceName "other-name" } } @@ -69,11 +72,13 @@ class OpenTelemetry14Test extends AgentTestRunner { trace(2) { span { parent() - operationName "some-name" + operationName DEFAULT_OPERATION_NAME + resourceName "some-name" } span { childOfPrevious() - operationName "other-name" + operationName DEFAULT_OPERATION_NAME + resourceName "other-name" } } } @@ -90,7 +95,6 @@ class OpenTelemetry14Test extends AgentTestRunner { TEST_WRITER.waitForTraces(1) def trace = TEST_WRITER.firstTrace() - then: trace.size() == 1 trace[0].spanId != 0 @@ -114,13 +118,15 @@ class OpenTelemetry14Test extends AgentTestRunner { trace(1) { span { parent() - operationName "some-name" + operationName DEFAULT_OPERATION_NAME + resourceName"some-name" } } trace(1) { span { parent() - operationName "other-name" + operationName DEFAULT_OPERATION_NAME + resourceName"other-name" } } } @@ -195,7 +201,7 @@ class OpenTelemetry14Test extends AgentTestRunner { trace(1) { span { parent() - operationName "some-name" + operationName DEFAULT_OPERATION_NAME if (tagSpan) { resourceName "other-resource" } else if (tagBuilder) { @@ -280,7 +286,7 @@ class OpenTelemetry14Test extends AgentTestRunner { SpanKind.CONSUMER | Tags.SPAN_KIND_CONSUMER SpanKind.INTERNAL | "internal" SpanKind.PRODUCER | Tags.SPAN_KIND_PRODUCER - SpanKind.SERVER | Tags.SPAN_KIND_SERVER + SERVER | Tags.SPAN_KIND_SERVER } def "test span error status"() { @@ -297,7 +303,7 @@ class OpenTelemetry14Test extends AgentTestRunner { trace(1) { span { parent() - operationName "some-name" + operationName DEFAULT_OPERATION_NAME resourceName "some-name" errored true @@ -349,7 +355,7 @@ class OpenTelemetry14Test extends AgentTestRunner { trace(1) { span { parent() - operationName "some-name" + operationName DEFAULT_OPERATION_NAME resourceName "some-name" errored false tags { @@ -390,7 +396,7 @@ class OpenTelemetry14Test extends AgentTestRunner { trace(1) { span { parent() - operationName "some-name" + operationName DEFAULT_OPERATION_NAME resourceName "some-name" errored false tags { @@ -405,16 +411,18 @@ class OpenTelemetry14Test extends AgentTestRunner { def "test span name update"() { setup: def builder = tracer.spanBuilder("some-name") - def result = builder.startSpan() + def result = builder.setSpanKind(SERVER).startSpan() expect: - result.delegate.operationName == "some-name" + result.delegate.operationName == DEFAULT_OPERATION_NAME + result.delegate.resourceName == "some-name" when: result.updateName("other-name") then: - result.delegate.operationName == "other-name" + result.delegate.operationName == DEFAULT_OPERATION_NAME + result.delegate.resourceName == "other-name" when: result.end() @@ -424,8 +432,9 @@ class OpenTelemetry14Test extends AgentTestRunner { trace(1) { span { parent() - operationName "other-name" - resourceName "some-name" + spanType SPAN_KIND_SERVER + operationName "server.request" + resourceName "other-name" } } } @@ -449,7 +458,8 @@ class OpenTelemetry14Test extends AgentTestRunner { trace(1) { span { parent() - operationName "some-name" + operationName DEFAULT_OPERATION_NAME + resourceName"some-name" errored true tags { defaultTags() diff --git a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/test/groovy/opentelemetry14/context/ContextTest.groovy b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/test/groovy/opentelemetry14/context/ContextTest.groovy index 3c1805133a8..bd834f9aefb 100644 --- a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/test/groovy/opentelemetry14/context/ContextTest.groovy +++ b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/test/groovy/opentelemetry14/context/ContextTest.groovy @@ -12,6 +12,7 @@ import spock.lang.Subject import static datadog.trace.bootstrap.instrumentation.api.ScopeSource.MANUAL import static datadog.trace.instrumentation.opentelemetry14.context.OtelContext.DATADOG_CONTEXT_ROOT_SPAN_KEY import static datadog.trace.instrumentation.opentelemetry14.context.OtelContext.OTEL_CONTEXT_SPAN_KEY +import static datadog.trace.instrumentation.opentelemetry14.trace.OtelConventions.DEFAULT_OPERATION_NAME class ContextTest extends AgentTestRunner { @Subject @@ -188,7 +189,8 @@ class ContextTest extends AgentTestRunner { def activeSpan = TEST_TRACER.activeSpan() then: - activeSpan.operationName == "some-name" + activeSpan.operationName == DEFAULT_OPERATION_NAME + activeSpan.resourceName == "some-name" DDSpanId.toHexStringPadded(activeSpan.spanId) == otelParentSpan.getSpanContext().spanId when: @@ -205,7 +207,8 @@ class ContextTest extends AgentTestRunner { activeSpan = TEST_TRACER.activeSpan() then: - activeSpan.operationName == "another-name" + activeSpan.operationName == DEFAULT_OPERATION_NAME + activeSpan.resourceName == "another-name" DDSpanId.toHexStringPadded(activeSpan.spanId) == otelGrandChildSpan.getSpanContext().spanId when: @@ -221,7 +224,8 @@ class ContextTest extends AgentTestRunner { trace(3) { span { parent() - operationName "some-name" + operationName DEFAULT_OPERATION_NAME + resourceName "some-name" } span { childOfPrevious() @@ -229,7 +233,8 @@ class ContextTest extends AgentTestRunner { } span { childOfPrevious() - operationName "another-name" + operationName DEFAULT_OPERATION_NAME + resourceName "another-name" } } } diff --git a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/test/groovy/opentelemetry14/context/propagation/AbstractPropagatorTest.groovy b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/test/groovy/opentelemetry14/context/propagation/AbstractPropagatorTest.groovy index 351ea547b24..977f5f9c40a 100644 --- a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/test/groovy/opentelemetry14/context/propagation/AbstractPropagatorTest.groovy +++ b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/test/groovy/opentelemetry14/context/propagation/AbstractPropagatorTest.groovy @@ -13,6 +13,8 @@ import spock.lang.Subject import javax.annotation.Nullable +import static datadog.trace.instrumentation.opentelemetry14.trace.OtelConventions.DEFAULT_OPERATION_NAME + abstract class AbstractPropagatorTest extends AgentTestRunner { @Subject def tracer = GlobalOpenTelemetry.get().tracerProvider.get("propagator" + Math.random()) // TODO FIX LATER @@ -86,7 +88,8 @@ abstract class AbstractPropagatorTest extends AgentTestRunner { assertTraces(1) { trace(1) { span { - operationName "some-name" + operationName DEFAULT_OPERATION_NAME + resourceName "some-name" traceDDId(DD128bTraceId.fromHex(traceId)) parentSpanId(DDSpanId.fromHex(spanId).toLong() as BigInteger) } diff --git a/dd-smoke-tests/play-2.8-otel/app/actions/AbstractAction.java b/dd-smoke-tests/play-2.8-otel/app/actions/AbstractAction.java index 3d56e52cadd..9e27a2421f4 100644 --- a/dd-smoke-tests/play-2.8-otel/app/actions/AbstractAction.java +++ b/dd-smoke-tests/play-2.8-otel/app/actions/AbstractAction.java @@ -9,16 +9,16 @@ public abstract class AbstractAction extends Action.Simple { - private final String operationName; + private final String spanName; - protected AbstractAction(String operationName) { - this.operationName = operationName; + protected AbstractAction(String spanName) { + this.spanName = spanName; } @Override public CompletionStage call(Http.Request req) { Tracer tracer = GlobalOpenTelemetry.getTracer("play-test"); - Span span = tracer.spanBuilder(operationName).startSpan(); + Span span = tracer.spanBuilder(spanName).startSpan(); Scope scope = span.makeCurrent(); try { return delegate.call(req); diff --git a/dd-smoke-tests/play-2.8-otel/app/filters/AbstractFilter.java b/dd-smoke-tests/play-2.8-otel/app/filters/AbstractFilter.java index f9e28b8f6b8..b52223036aa 100644 --- a/dd-smoke-tests/play-2.8-otel/app/filters/AbstractFilter.java +++ b/dd-smoke-tests/play-2.8-otel/app/filters/AbstractFilter.java @@ -12,17 +12,16 @@ public abstract class AbstractFilter extends Filter { private final HttpExecutionContext ec; - private final String operationName; + private final String spanName; private final boolean wrap; - public AbstractFilter(String operationName, Materializer mat, HttpExecutionContext ec) { - this(operationName, false, mat, ec); + public AbstractFilter(String spanName, Materializer mat, HttpExecutionContext ec) { + this(spanName, false, mat, ec); } - public AbstractFilter( - String operationName, boolean wrap, Materializer mat, HttpExecutionContext ec) { + public AbstractFilter(String spanName, boolean wrap, Materializer mat, HttpExecutionContext ec) { super(mat); - this.operationName = operationName; + this.spanName = spanName; this.wrap = wrap; this.ec = ec; } @@ -32,14 +31,14 @@ public CompletionStage apply( Function> nextFilter, Http.RequestHeader requestHeader) { final Tracer tracer = GlobalOpenTelemetry.getTracer("play-test"); - final Span startedSpan = wrap ? tracer.spanBuilder(operationName).startSpan() : null; + final Span startedSpan = wrap ? tracer.spanBuilder(spanName).startSpan() : null; Scope outerScope = wrap ? startedSpan.makeCurrent() : null; try { return nextFilter .apply(requestHeader) .thenApplyAsync( result -> { - Span span = wrap ? startedSpan : tracer.spanBuilder(operationName).startSpan(); + Span span = wrap ? startedSpan : tracer.spanBuilder(spanName).startSpan(); try (Scope innerScope = span.makeCurrent()) { // Yes this does no real work return result; diff --git a/dd-smoke-tests/play-2.8-otel/src/test/groovy/datadog/smoketest/Play28OTelSmokeTest.groovy b/dd-smoke-tests/play-2.8-otel/src/test/groovy/datadog/smoketest/Play28OTelSmokeTest.groovy index b2a09396abe..7ab1e90ae44 100644 --- a/dd-smoke-tests/play-2.8-otel/src/test/groovy/datadog/smoketest/Play28OTelSmokeTest.groovy +++ b/dd-smoke-tests/play-2.8-otel/src/test/groovy/datadog/smoketest/Play28OTelSmokeTest.groovy @@ -52,8 +52,8 @@ abstract class Play28OTelSmokeTest extends AbstractServerSmokeTest { + " -Dhttp.port=${httpPort}" + " -Dhttp.address=127.0.0.1" + " -Dplay.server.provider=${serverProvider()}" - + " -Ddd.writer.type=MultiWriter:TraceStructureWriter:${output.getAbsolutePath()},DDAgentWriter" - + " -Ddd.integration.opentelemetry.experimental.enabled=true" + + " -Ddd.writer.type=MultiWriter:TraceStructureWriter:${output.getAbsolutePath()}:includeresource,DDAgentWriter" + + " -Ddd.integration.opentelemetry-1.enabled=true" + " -Dclient.request.base=${clientServer.address}/hello/") return processBuilder } @@ -77,14 +77,14 @@ abstract class Play28OTelSmokeTest extends AbstractServerSmokeTest { // that is completed is completed before the span is finished, the order of those filters and the request processing // is undefined. boolean isOk = true - def allowed = /|^\[${serverProviderName()}.request - |(\[filter1(\[filter\d])(\[filter\d])(\[filter\d])])? - |\[play.request\[action1\[action2\[do-get\[play-ws.request]]]]] - |(\[filter1(\[filter\d])(\[filter\d])(\[filter\d])])? + def allowed = /|^\[${serverProviderName()}.request:GET \/welcome[sj] + |(\[otel_unknown:filter1(\[otel_unknown:filter\d])(\[otel_unknown:filter\d])(\[otel_unknown:filter\d])])? + |\[play.request:GET \/welcome[sj]\[otel_unknown:action1\[otel_unknown:action2\[otel_unknown:do-get\[play-ws.request:GET \/hello\/\?]]]]] + |(\[otel_unknown:filter1(\[otel_unknown:filter\d])(\[otel_unknown:filter\d])(\[otel_unknown:filter\d])])? |]$/.stripMargin().replaceAll("[\n\r]", "") // Ignore [command_execution], which can be generated by hostname calls from Config/Telemetry on some systems. - traceCounts = traceCounts.findAll { it.key != "[command_execution]" } + traceCounts = traceCounts.findAll { it.key != "[command_execution:hostname]" } traceCounts.entrySet().each { def matcher = (it.key =~ allowed).findAll() @@ -94,9 +94,9 @@ abstract class Play28OTelSmokeTest extends AbstractServerSmokeTest { |traceCounts=${traceCounts}""".stripMargin() def matches = matcher.head().findAll{ it != null } isOk &= matches.size() == 5 - isOk &= matches.contains("[filter2]") - isOk &= matches.contains("[filter3]") - isOk &= matches.contains("[filter4]") + isOk &= matches.contains("[otel_unknown:filter2]") + isOk &= matches.contains("[otel_unknown:filter3]") + isOk &= matches.contains("[otel_unknown:filter4]") assert isOk : """\ |Trace ${it.key} does not match allowed pattern: |pattern=${allowed}