Skip to content

Commit

Permalink
feat: Add span operation naming convention
Browse files Browse the repository at this point in the history
  • Loading branch information
PerfectSlayer committed Oct 26, 2023
1 parent 85efb35 commit 3e46c17
Show file tree
Hide file tree
Showing 10 changed files with 418 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,22 @@ public String[] helperClassNames() {
packageName + ".context.propagation.AgentTextMapPropagator",
packageName + ".context.propagation.OtelContextPropagators",
packageName + ".trace.OtelExtractedContext",
packageName + ".trace.OtelConventions",
packageName + ".trace.OtelConventions$1",
packageName + ".trace.OtelConventions$Convention",
packageName + ".trace.OtelConventions$Convention$1",
packageName + ".trace.OtelConventions$Convention$2",
packageName + ".trace.OtelConventions$Convention$3",
packageName + ".trace.OtelConventions$Convention$4",
packageName + ".trace.OtelConventions$Convention$5",
packageName + ".trace.OtelConventions$Convention$6",
packageName + ".trace.OtelConventions$Convention$7",
packageName + ".trace.OtelConventions$Convention$8",
packageName + ".trace.OtelConventions$Convention$9",
packageName + ".trace.OtelConventions$Convention$10",
packageName + ".trace.OtelConventions$Convention$11",
packageName + ".trace.OtelConventions$Convention$12",
packageName + ".trace.OtelConventions$Convention$13",
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,22 @@ 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.OtelConventions$Convention",
ROOT_PACKAGE_NAME + ".trace.OtelConventions$Convention$1",
ROOT_PACKAGE_NAME + ".trace.OtelConventions$Convention$2",
ROOT_PACKAGE_NAME + ".trace.OtelConventions$Convention$3",
ROOT_PACKAGE_NAME + ".trace.OtelConventions$Convention$4",
ROOT_PACKAGE_NAME + ".trace.OtelConventions$Convention$5",
ROOT_PACKAGE_NAME + ".trace.OtelConventions$Convention$6",
ROOT_PACKAGE_NAME + ".trace.OtelConventions$Convention$7",
ROOT_PACKAGE_NAME + ".trace.OtelConventions$Convention$8",
ROOT_PACKAGE_NAME + ".trace.OtelConventions$Convention$9",
ROOT_PACKAGE_NAME + ".trace.OtelConventions$Convention$10",
ROOT_PACKAGE_NAME + ".trace.OtelConventions$Convention$11",
ROOT_PACKAGE_NAME + ".trace.OtelConventions$Convention$12",
ROOT_PACKAGE_NAME + ".trace.OtelConventions$Convention$13",
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,22 @@ 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.OtelConventions$Convention",
ROOT_PACKAGE_NAME + ".trace.OtelConventions$Convention$1",
ROOT_PACKAGE_NAME + ".trace.OtelConventions$Convention$2",
ROOT_PACKAGE_NAME + ".trace.OtelConventions$Convention$3",
ROOT_PACKAGE_NAME + ".trace.OtelConventions$Convention$4",
ROOT_PACKAGE_NAME + ".trace.OtelConventions$Convention$5",
ROOT_PACKAGE_NAME + ".trace.OtelConventions$Convention$6",
ROOT_PACKAGE_NAME + ".trace.OtelConventions$Convention$7",
ROOT_PACKAGE_NAME + ".trace.OtelConventions$Convention$8",
ROOT_PACKAGE_NAME + ".trace.OtelConventions$Convention$9",
ROOT_PACKAGE_NAME + ".trace.OtelConventions$Convention$10",
ROOT_PACKAGE_NAME + ".trace.OtelConventions$Convention$11",
ROOT_PACKAGE_NAME + ".trace.OtelConventions$Convention$12",
ROOT_PACKAGE_NAME + ".trace.OtelConventions$Convention$13",
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,319 @@
package datadog.trace.instrumentation.opentelemetry14.trace;

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 datadog.trace.instrumentation.opentelemetry14.trace.OtelConventions.Convention.AWS_CLIENT;
import static datadog.trace.instrumentation.opentelemetry14.trace.OtelConventions.Convention.DATABASE;
import static datadog.trace.instrumentation.opentelemetry14.trace.OtelConventions.Convention.FAAS_CLIENT;
import static datadog.trace.instrumentation.opentelemetry14.trace.OtelConventions.Convention.FAAS_SERVER;
import static datadog.trace.instrumentation.opentelemetry14.trace.OtelConventions.Convention.GENERIC_CLIENT;
import static datadog.trace.instrumentation.opentelemetry14.trace.OtelConventions.Convention.GENERIC_SERVER;
import static datadog.trace.instrumentation.opentelemetry14.trace.OtelConventions.Convention.GRAPHQL_SERVER;
import static datadog.trace.instrumentation.opentelemetry14.trace.OtelConventions.Convention.HTTP_CLIENT;
import static datadog.trace.instrumentation.opentelemetry14.trace.OtelConventions.Convention.HTTP_SERVER;
import static datadog.trace.instrumentation.opentelemetry14.trace.OtelConventions.Convention.MESSAGE_CONSUMER;
import static datadog.trace.instrumentation.opentelemetry14.trace.OtelConventions.Convention.MESSAGE_PRODUCER;
import static datadog.trace.instrumentation.opentelemetry14.trace.OtelConventions.Convention.RPC_CLIENT;
import static datadog.trace.instrumentation.opentelemetry14.trace.OtelConventions.Convention.RPC_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;

public final class OtelConventions {
/** The Datadog span default operation name. */
public static final String DEFAULT_OPERATION_NAME = "otel_unknown";

/** Span naming conventions, by priority order. */
private static final Convention[] SPAN_NAMING_CONVENTIONS =
new Convention[] {
HTTP_SERVER,
HTTP_CLIENT,
DATABASE,
MESSAGE_CONSUMER,
MESSAGE_PRODUCER,
AWS_CLIENT,
RPC_CLIENT,
RPC_SERVER,
FAAS_SERVER,
FAAS_CLIENT,
GRAPHQL_SERVER,
GENERIC_SERVER,
GENERIC_CLIENT,
};

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;
default:
case INTERNAL:
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) {
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 applyOperationName(AgentSpan span) {
String operationName = computeOperationName(span).toLowerCase(ROOT);
span.setOperationName(operationName);
}

public static String computeOperationName(AgentSpan span) {
for (Convention convention : SPAN_NAMING_CONVENTIONS) {
if (convention.appliesFor(span)) {
return convention.defaultOperationName(span);
}
}
// Fallback if no convention match
if (span.getSpanType() != null) {
return toSpanKind(span.getSpanType()).name();
} else {
return DEFAULT_OPERATION_NAME;
}
}

public enum Convention {
// HTTP conventions: https://opentelemetry.io/docs/specs/otel/trace/semantic_conventions/http/
HTTP_SERVER() {
@Override
public boolean appliesFor(AgentSpan span) {
return isKind(span, SERVER) && hasAttribute(span, "http.request.method");
}

@Override
public String defaultOperationName(AgentSpan span) {
return "http.server.request";
}
},
HTTP_CLIENT() {
@Override
public boolean appliesFor(AgentSpan span) {
return isKind(span, CLIENT) && hasAttribute(span, "http.request.method");
}

@Override
public String defaultOperationName(AgentSpan span) {
return "http.client.request";
}
},

// https://opentelemetry.io/docs/specs/semconv/database/database-spans/
DATABASE() {
@Override
public boolean appliesFor(AgentSpan span) {
return isKind(span, CLIENT) && hasAttribute(span, "db.system");
}

@Override
public String defaultOperationName(AgentSpan span) {
return getAttribute(span, "db.system") + ".query";
}
},

// https://opentelemetry.io/docs/specs/semconv/messaging/messaging-spans/
MESSAGE_PRODUCER() { // TODO Merge both producer and consumer?

@Override
public boolean appliesFor(AgentSpan span) {
return isKind(span, PRODUCER)
&& hasAttribute(span, "messaging.system")
&& hasAttribute(span, "messaging.operation");
}

@Override
public String defaultOperationName(AgentSpan span) {
return getAttribute(span, "messaging.system").toLowerCase(ROOT)
+ "."
+ getAttribute(span, "messaging.operation");
}
},
MESSAGE_CONSUMER() {
@Override
public boolean appliesFor(AgentSpan span) {
return (isKind(span, CLIENT) || isKind(span, CONSUMER))
&& hasAttribute(span, "messaging.system")
&& hasAttribute(span, "messaging.operation");
}

@Override
public String defaultOperationName(AgentSpan span) {
return getAttribute(span, "messaging.system").toLowerCase(ROOT)
+ "."
+ getAttribute(span, "messaging.operation");
}
},

// https://opentelemetry.io/docs/specs/semconv/cloud-providers/aws-sdk/
AWS_CLIENT() {
@Override
public boolean appliesFor(AgentSpan span) {
return isKind(span, CLIENT)
&& hasAttribute(span, "rpc.system")
&& "aws-api".equals(getAttribute(span, "rpc.system"));
}

@Override
public String defaultOperationName(AgentSpan span) {
String service = getOptionalAttribute(span, "rpc.service");
if (service == null) {
return "aws.request";
} else {
return "aws." + service.toLowerCase(ROOT) + ".request";
}
}
},
// https://opentelemetry.io/docs/specs/otel/trace/semantic_conventions/rpc/
RPC_SERVER() {
@Override
public boolean appliesFor(AgentSpan span) {
return isKind(span, SERVER) && hasAttribute(span, "rpc.system");
}

@Override
public String defaultOperationName(AgentSpan span) {
return getAttribute(span, "rpc.system") + ".server.request";
}
},
RPC_CLIENT() {
@Override
public boolean appliesFor(AgentSpan span) {
return isKind(span, CLIENT) && hasAttribute(span, "rpc.system");
}

@Override
public String defaultOperationName(AgentSpan span) {
return getAttribute(span, "rpc.system") + ".client.request";
}
},
// https://opentelemetry.io/docs/specs/semconv/faas/faas-spans/#incoming-faas-span-attributes
FAAS_SERVER() {
@Override
public boolean appliesFor(AgentSpan span) {
return isKind(span, SERVER) && hasAttribute(span, "faas.trigger");
}

@Override
public String defaultOperationName(AgentSpan span) {
return getAttribute(span, "faas.trigger") + ".invoke";
}
},
// https://opentelemetry.io/docs/specs/semconv/faas/faas-spans/#outgoing-invocations
FAAS_CLIENT() {
@Override
public boolean appliesFor(AgentSpan span) {
return isKind(span, CLIENT)
&& hasAttribute(span, "faas.invoked_provider")
&& hasAttribute(span, "faas.invoked_name");
}

@Override
public String defaultOperationName(AgentSpan span) {
return getAttribute(span, "faas.invoked_provider")
+ "."
+ getAttribute(span, "faas.invoked_name")
+ ".invoke";
}
},
// https://opentelemetry.io/docs/specs/otel/trace/semantic_conventions/instrumentation/graphql/
GRAPHQL_SERVER() {
@Override
public boolean appliesFor(AgentSpan span) {
return hasAttribute(span, "graphql.operation.type");
}

@Override
public String defaultOperationName(AgentSpan span) {
return "graphql.server.request";
}
},
GENERIC_SERVER() {
@Override
public boolean appliesFor(AgentSpan span) {
return isKind(span, SERVER);
}

@Override
public String defaultOperationName(AgentSpan span) {
String protocol = getOptionalAttribute(span, "network.protocol.name");
return protocol == null ? "server.request" : protocol + "server.request";
}
},
GENERIC_CLIENT() {
@Override
public boolean appliesFor(AgentSpan span) {
return isKind(span, CLIENT);
}

@Override
public String defaultOperationName(AgentSpan span) {
String protocol = getOptionalAttribute(span, "network.protocol.name");
return protocol == null ? "client.request" : protocol + "client.request";
}
};

public abstract boolean appliesFor(AgentSpan span);

public abstract String defaultOperationName(AgentSpan span);

public static boolean hasAttribute(AgentSpan span, String key) {
Object tag = span.getTag(key);
return tag instanceof String;
}

public static String getAttribute(AgentSpan span, String key) {
Object tag = span.getTag(key);
return (String) tag; // TODO Handle type error, missing value, add logging, etc...
}

public static @Nullable String getOptionalAttribute(AgentSpan span, String key) {
Object tag = span.getTag(key);
return tag instanceof String ? (String) tag : null;
}

public static boolean isKind(AgentSpan span, SpanKind kind) {
return toSpanType(kind).equals(span.getSpanType());
}
}
}
Loading

0 comments on commit 3e46c17

Please sign in to comment.