Skip to content

Conversation

mhlidd
Copy link
Contributor

@mhlidd mhlidd commented Oct 14, 2025

What Does This Do

Currently, OTel Spans created from Trace Annotations do not contain the span.kind tag, and spans created from custom instrumentation do not have the type field. This leads to an inconsistent customer experience where spans are missing data on the backend, and also may have implications on the way that spans are "named" following normalization on the Agent. This PR aims to reduce the inconsistent behavior between these two ways of creating OTel spans.

Motivation

Escalation

Additional Notes

Contributor Checklist

Jira ticket: [PROJ-IDENT]

This comment has been minimized.

@mhlidd mhlidd changed the title adding span.kind for Otel trace annotations Normalizing Behavior of OTel Spans Created From Custom Instrumentation and Trace Annotations Oct 14, 2025
@mhlidd mhlidd changed the title Normalizing Behavior of OTel Spans Created From Custom Instrumentation and Trace Annotations Normalizing Behavior of OTel Spans Created From Custom-Instrumentation and Trace Annotations Oct 14, 2025
@mhlidd mhlidd added type: bug Bug report and fix comp: core Tracer core inst: opentelemetry OpenTelemetry instrumentation labels Oct 14, 2025
Comment on lines 103 to 141
private static String convertToSpanType(SpanKind kind) {
if (kind == null) {
return null;
}
switch (kind) {
case SERVER:
return HTTP_SERVER;
case CLIENT:
return HTTP_CLIENT;
case PRODUCER:
return MESSAGE_PRODUCER;
case CONSUMER:
return MESSAGE_CONSUMER;
case INTERNAL:
// checking for SpanKind.INTERNAL, returning DDSpanTypes.INTERNAL
return INTERNAL;
default:
return null;
}
}

private static String convertToSpanKindTag(SpanKind kind) {
if (kind == null) {
return null;
}
switch (kind) {
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 null;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is currently duplicated code between the opentelemetry-shim and opentelemetry-annotation modules. @PerfectSlayer what do you think about introducing a new opentelemetry-utils module in utils for shared helpers? 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that the otel-shim classes are already duplicated at build-time and placed under different packages - i.e. one source, but made available in two places in the final jar. This is to support both OTel drop-in support (which cannot expose the OTel packages on the classpath, so must repackage classes both at build-time and run-time) but also instrument OTel client code in the application.

I would investigate whether the annotations instrumentation can also depend on

  implementation project(':dd-java-agent:agent-otel:otel-shim')

like the main OTel instrumentation under opentelemetry-1.4

Copy link
Contributor

@PerfectSlayer PerfectSlayer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❔ question: ‏My main question for this change is should we change this behavior as it is not part of the spec, or should we evolve the spec and make sure the behavior is consistent across all languages? -- system tests could help here too.

Because I can think of case where customers set span type on purpose and it gets overridden when setting span kind.

public static final String CACHE = "cache";
public static final String SOAP = "soap";

public static final String INTERNAL = "internal";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❔ question: ‏It looks like a duplicate of datadog.trace.bootstrap.instrumentation.api.Tags#SPAN_KIND_INTERNAL

* @param kind The OpenTelemetry span kind to convert.
* @return The {@link DDSpanTypes} value.
*/
public static String convertToSpanType(SpanKind kind) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 suggestion: To keep method naming consistent:‏

Suggested change
public static String convertToSpanType(SpanKind kind) {
public static String toSpanType(SpanKind kind) {

Comment on lines +80 to +82
if (kind == null) {
return null;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 suggestion:null is not supposed to be given as parameter as all calls are guarded.
Similarly, I would not have a default case returning null but internal.

@mhlidd
Copy link
Contributor Author

mhlidd commented Oct 15, 2025

❔ question: ‏My main question for this change is should we change this behavior as it is not part of the spec, or should we evolve the spec and make sure the behavior is consistent across all languages? -- system tests could help here too.

Because I can think of case where customers set span type on purpose and it gets overridden when setting span kind.

I think you are right that this change deviates from the spec. There are 2 concepts that are closely tied together, which makes this situation confusing. Below are the changes I think actually should be done:

  1. Datadog has a type field on the Span, which is semantically equivalent to the kind field of an OTel span (or SpanKind). This has the values depicted in this API. We implement this mostly correctly in the tracer (minus adding in the INTERNAL case in a helper).
  2. Datadog also has a span.kind tag that goes in meta. This maps the same SpanKind values to different tag values.

I looked into the OTel implementation of using a SpanBuilder to create an OTel span, and the behavior of setSpanKind in the Datadog shim is different than the OTel implementation. OTel sets the value passed in to the SpanKind field of the Span, while we currently set the span.kind tag instead. I think it makes sense here to align with OTel by modifying the setSpanKind function to set the type field of the span in our shim. This would also make the behavior identical with using OTel trace annotations in Datadog with code such as @WithSpan(kind=""), as we set the type field of the Span there as well.

What do you think @PerfectSlayer? I can see a world where we should keep the legacy behavior of setSpanKind setting the span.kind field to prevent breaking customer code, but I do think there is a need to align the behavior of the function.

@PerfectSlayer
Copy link
Contributor

What do you think @PerfectSlayer? I can see a world where we should keep the legacy behavior of setSpanKind setting the span.kind field to prevent breaking customer code, but I do think there is a need to align the behavior of the function.

No I think it make sens to change to support both span kind and span type on OTel Tracing API and annotation support.

One thing that might be missing is the implementation of the span.type reserved attributes here. Can you add it to? (to call span.setType() on span.type attribute name).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp: core Tracer core inst: opentelemetry OpenTelemetry instrumentation type: bug Bug report and fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants