Skip to content

Commit

Permalink
Add span operation naming convention (#6104)
Browse files Browse the repository at this point in the history
* feat: Add span operation naming convention
* feat: Update OTel smoke tests
  • Loading branch information
PerfectSlayer committed Nov 10, 2023
1 parent ffaa3d0 commit 376c39b
Show file tree
Hide file tree
Showing 14 changed files with 477 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -106,20 +107,22 @@ 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;
}

@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));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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));
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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);
}
}
Loading

0 comments on commit 376c39b

Please sign in to comment.