Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(otlp): introduce "kind" to spans #4540

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open

Conversation

mjq
Copy link
Member

@mjq mjq commented Mar 3, 2025

OTLP spans have a property called kind that helps specify the relationship between that span and its parent or child. Sentry users should be able to see the span kind and search/filter on it when it is sent (via OTLP or the OtelSpan envelope item type). During ingestion, write a string representation of the kind to the span.kind attribute.

(A string representation is chosen over the default integer one because it is both self-describing and also makes search straightforward (no need for translations between string and integer in the frontend or EAP. We are relying on Clickhouse's compression to make the additional data usage reasonable.)

OTLP spans have a property called `kind` that helps specify the relationship
between that span and its parent or child. Sentry users should be able to see
the span kind and search/filter on it when it is sent. During ingestion, write
a string representation of the `kind` to the `span.kind` attribute.

(A string representation is chosen over the default integer one because it is
both self-describing and also makes search straightforward (no need for
translations between string and integer in the frontend or EAP. We are relying
on Clickhouse's compression to make the additional data usage reasonable.)

[0] https://github.com/open-telemetry/opentelemetry-proto/blob/d7770822d70c7bd47a6891fc9faacc66fc4af3d3/opentelemetry/proto/trace/v1/trace.proto#L152-L178

Co-authored-by: Ash Anand <[email protected]>
@mjq mjq requested a review from a team as a code owner March 3, 2025 20:36
@Dav1dde Dav1dde requested a review from jan-auer March 4, 2025 10:56
Comment on lines 680 to 681
#[metastructure(field = "span.kind")]
pub span_kind: Annotated<String>,
Copy link
Member

Choose a reason for hiding this comment

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

Please add a small doc string, ideally what values it can have and when it should be filled.

Copy link
Member

Choose a reason for hiding this comment

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

See opentelemetry-proto:

  // Distinguishes between spans generated in a particular context. For example,
  // two spans with the same name may be distinguished using `CLIENT` (caller)
  // and `SERVER` (callee) to identify queueing latency associated with the span.

@@ -201,6 +202,11 @@ pub fn otel_to_sentry_span(otel_span: OtelSpan) -> EventSpan {
description = Some(format!("{http_method} {http_route}"));
}

data.insert(
"span.kind".to_owned(),
Copy link
Member

Choose a reason for hiding this comment

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

In opentelemetry-proto this is a top-level field on the span rather than an attribute. Could we align with their schema and create a field on our Span, too? Note we did the same with is_remote (mapping it out from flags).

Copy link
Member Author

@mjq mjq Mar 4, 2025

Choose a reason for hiding this comment

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

@jan-auer Can do. Just to confirm, I'm assuming that would mean adding kind as a field to both Span and SpanKafkaMessage for transfer to Snuba? (And then EAP can choose to store it however it makes sense, column or attribute?)

Also, in that case the choice of how to save the value is being made by EAP instead - in that case should we transfer it as the original integer value rather than our string representation, maybe? (Leaving it to Snuba to stringify, in storage or via RPC?)

Copy link
Member

@jan-auer jan-auer Mar 6, 2025

Choose a reason for hiding this comment

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

that would mean adding kind as a field to both Span and SpanKafkaMessage for transfer to Snuba?

That's right.

And then EAP can choose to store it however it makes sense, column or attribute?

We can consider aligning with how we want kind to show up in the product & how otel SDKs track it. In my previous comment, I assumed we would then also show it as span.kind rather than span.data.kind. If we want to show it as an attribute, then we should probably keep your current approach. Otherwise, we have a strange drift between the SDK protocol, pipeline, and the product-side data model.

in that case should we transfer it as the original integer value rather than our string representation, maybe?

Definitely possible, though I'd advise against it in this instance. In both cases - string or integer - would need to share an enum between Relay, Sentry (pipeline) and Snuba. However, if we convert to string representation right away, we can now use the same enums in the processing pipeline and in all workers/endpoints that pull data out of EAP. Essentially, the pre- and post-EAP data model would be identical, which I think would be desirable.

@mjq mjq requested a review from jan-auer March 10, 2025 19:31
@mjq
Copy link
Member Author

mjq commented Mar 10, 2025

@jan-auer I've updated the PR to use a new kind field on Span and SpanKafkaMessage. I couldn't find a way to profitably use an explicit enum inside Relay for this (although we might still be able to later in the pipeline or in workers/etc) - but please let me know if you had anything specific in mind.

@mjq mjq changed the title feat(otlp): write span kind to attributes feat(otlp): introduce "kind" to spans Mar 13, 2025
Copy link
Member

@jan-auer jan-auer left a comment

Choose a reason for hiding this comment

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

Thanks, I think using the string representation as a top-level field is the right choice.

//
// See <https://opentelemetry.io/docs/specs/otel/trace/api/#spankind>
#[metastructure(skip_serialization = "empty", trim = false)]
pub kind: Annotated<String>,
Copy link
Member

Choose a reason for hiding this comment

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

If you like, we could also make this an enum with a From<i32> implementation for known kinds like here. Maybe @Dav1dde wants to chime in if there's a preference.

Copy link
Member

Choose a reason for hiding this comment

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

I like that suggestion, more strong types are always good down the road 👍

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for the feedback, I'll make that change 👍

Copy link
Member Author

@mjq mjq Mar 21, 2025

Choose a reason for hiding this comment

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

@jan-auer @Dav1dde I've updated the PR to use an enum.

I used a From<i32> implementation in c86423b. The only thing I didn't like about this implementation was that we lost the match on opentelemetry-proto's SpanKind enum (henceforth OtelSpanKind) - in other words, we would no longer be notified (due to exhaustiveness checking) if new kind values were added upstream in the future.

You can see in commit 2f764bc my changes to instead use From<OtelSpanKind>, effectively first transforming the value from the incoming i32 to OtelSpanKind, then to our own SpanLink type. This gives us an exhaustiveness check on OtelSpanKind matches again, and even though the code to read kind in otel_to_sentry_span became a bit longer, I personally like the explicitness of the second in this particular context.

// From<i32> version

kind: SpanKind::from(kind).into(),

vs

// From<OtelSpanKind> version

kind: OtelSpanKind::try_from(kind)
        .map_or(SpanKind::Unspecified, SpanKind::from)
        .into(),

I can always revert the second commit to get back to the more direct i32 check if you prefer. Curious if you have a preference either way.

@mjq mjq force-pushed the mjq-span-kind-to-attr branch from 7dff9eb to b9b95b7 Compare March 21, 2025 03:54
@mjq mjq force-pushed the mjq-span-kind-to-attr branch from b9b95b7 to 2f764bc Compare March 21, 2025 05:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants