Skip to content

Commit 4c1f210

Browse files
authored
ref(spans): Unite span kind types (#5144)
* `SpanKind` was missing a catch-all type so outdated external relays would have dropped it. * `SpanV2Kind` promised a catchall-variant but didn't have one. Unite them into one.
1 parent 0fe2870 commit 4c1f210

File tree

5 files changed

+50
-156
lines changed

5 files changed

+50
-156
lines changed

relay-event-schema/src/protocol/span.rs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1106,23 +1106,35 @@ impl FromValue for Route {
11061106
}
11071107
}
11081108

1109+
/// The kind of a span.
1110+
///
1111+
/// This corresponds to OTEL's kind enum, plus a
1112+
/// catchall variant for forward compatibility.
11091113
#[derive(Clone, Debug, PartialEq, ProcessValue)]
11101114
pub enum SpanKind {
1115+
/// An operation internal to an application.
11111116
Internal,
1117+
/// Server-side processing requested by a client.
11121118
Server,
1119+
/// A request from a client to a server.
11131120
Client,
1121+
/// Scheduling of an operation.
11141122
Producer,
1123+
/// Processing of a scheduled operation.
11151124
Consumer,
1125+
/// Unknown kind, for forward compatibility.
1126+
Unknown(String),
11161127
}
11171128

11181129
impl SpanKind {
1119-
pub fn as_str(&self) -> &'static str {
1130+
pub fn as_str(&self) -> &str {
11201131
match self {
11211132
Self::Internal => "internal",
11221133
Self::Server => "server",
11231134
Self::Client => "client",
11241135
Self::Producer => "producer",
11251136
Self::Consumer => "consumer",
1137+
Self::Unknown(s) => s.as_str(),
11261138
}
11271139
}
11281140
}
@@ -1146,7 +1158,7 @@ impl std::str::FromStr for SpanKind {
11461158
"client" => SpanKind::Client,
11471159
"producer" => SpanKind::Producer,
11481160
"consumer" => SpanKind::Consumer,
1149-
_ => return Err(ParseSpanKindError),
1161+
other => SpanKind::Unknown(other.to_owned()),
11501162
})
11511163
}
11521164
}
@@ -1622,4 +1634,20 @@ mod tests {
16221634
r#"{"links":[{"trace_id":"5c79f60c11214eb38604f4ae0781bfb2","span_id":"ab90fdead5f74052","sampled":true,"attributes":{"sentry.link.type":"previous_trace"}},{"trace_id":"4c79f60c11214eb38604f4ae0781bfb2","span_id":"fa90fdead5f74052","sampled":true,"attributes":{"sentry.link.type":"next_trace"}}]}"#
16231635
);
16241636
}
1637+
1638+
#[test]
1639+
fn test_span_kind() {
1640+
let span = Annotated::<Span>::from_json(
1641+
r#"{
1642+
"kind": "???"
1643+
}"#,
1644+
)
1645+
.unwrap()
1646+
.into_value()
1647+
.unwrap();
1648+
assert_eq!(
1649+
span.kind.value().unwrap(),
1650+
&SpanKind::Unknown("???".to_owned())
1651+
);
1652+
}
16251653
}

relay-event-schema/src/protocol/span_v2.rs

Lines changed: 4 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
use relay_protocol::{Annotated, Array, Empty, Error, FromValue, Getter, IntoValue, Object, Value};
1+
use relay_protocol::{Annotated, Array, Empty, FromValue, Getter, IntoValue, Object, Value};
22

33
use std::fmt;
4-
use std::str::FromStr;
54

65
use serde::Serialize;
76

87
use crate::processor::ProcessValue;
9-
use crate::protocol::{Attributes, OperationType, SpanId, Timestamp, TraceId};
8+
use crate::protocol::{Attributes, OperationType, SpanId, SpanKind, Timestamp, TraceId};
109

1110
/// A version 2 (transactionless) span.
1211
#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
@@ -46,7 +45,7 @@ pub struct SpanV2 {
4645
///
4746
/// See <https://opentelemetry.io/docs/specs/otel/trace/api/#spankind>
4847
#[metastructure(skip_serialization = "empty", trim = false)]
49-
pub kind: Annotated<SpanV2Kind>,
48+
pub kind: Annotated<SpanKind>,
5049

5150
/// Timestamp when the span started.
5251
#[metastructure(required = true)]
@@ -177,116 +176,6 @@ impl IntoValue for SpanV2Status {
177176
}
178177
}
179178

180-
/// The kind of a V2 span.
181-
///
182-
/// This corresponds to OTEL's kind enum, plus a
183-
/// catchall variant for forward compatibility.
184-
#[derive(Clone, Debug, PartialEq, ProcessValue)]
185-
pub enum SpanV2Kind {
186-
/// An operation internal to an application.
187-
Internal,
188-
/// Server-side processing requested by a client.
189-
Server,
190-
/// A request from a client to a server.
191-
Client,
192-
/// Scheduling of an operation.
193-
Producer,
194-
/// Processing of a scheduled operation.
195-
Consumer,
196-
}
197-
198-
impl SpanV2Kind {
199-
pub fn as_str(&self) -> &'static str {
200-
match self {
201-
Self::Internal => "internal",
202-
Self::Server => "server",
203-
Self::Client => "client",
204-
Self::Producer => "producer",
205-
Self::Consumer => "consumer",
206-
}
207-
}
208-
}
209-
210-
impl Empty for SpanV2Kind {
211-
fn is_empty(&self) -> bool {
212-
false
213-
}
214-
}
215-
216-
impl Default for SpanV2Kind {
217-
fn default() -> Self {
218-
Self::Internal
219-
}
220-
}
221-
222-
impl fmt::Display for SpanV2Kind {
223-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
224-
write!(f, "{}", self.as_str())
225-
}
226-
}
227-
228-
#[derive(Debug, Clone, Copy)]
229-
pub struct ParseSpanV2KindError;
230-
231-
impl fmt::Display for ParseSpanV2KindError {
232-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233-
write!(f, "invalid span kind")
234-
}
235-
}
236-
237-
impl FromStr for SpanV2Kind {
238-
type Err = ParseSpanV2KindError;
239-
240-
fn from_str(s: &str) -> Result<Self, Self::Err> {
241-
let kind = match s {
242-
"internal" => Self::Internal,
243-
"server" => Self::Server,
244-
"client" => Self::Client,
245-
"producer" => Self::Producer,
246-
"consumer" => Self::Consumer,
247-
_ => return Err(ParseSpanV2KindError),
248-
};
249-
Ok(kind)
250-
}
251-
}
252-
253-
impl FromValue for SpanV2Kind {
254-
fn from_value(Annotated(value, meta): Annotated<Value>) -> Annotated<Self>
255-
where
256-
Self: Sized,
257-
{
258-
match &value {
259-
Some(Value::String(s)) => match s.parse() {
260-
Ok(kind) => Annotated(Some(kind), meta),
261-
Err(_) => Annotated::from_error(Error::expected("a span kind"), value),
262-
},
263-
Some(_) => Annotated::from_error(Error::expected("a span kind"), value),
264-
None => Annotated::empty(),
265-
}
266-
}
267-
}
268-
269-
impl IntoValue for SpanV2Kind {
270-
fn into_value(self) -> Value
271-
where
272-
Self: Sized,
273-
{
274-
Value::String(self.to_string())
275-
}
276-
277-
fn serialize_payload<S>(
278-
&self,
279-
s: S,
280-
_behavior: relay_protocol::SkipSerialization,
281-
) -> Result<S::Ok, S::Error>
282-
where
283-
Self: Sized,
284-
S: serde::Serializer,
285-
{
286-
s.serialize_str(self.as_str())
287-
}
288-
}
289-
290179
/// A link from a span to another span.
291180
#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
292181
#[metastructure(trim = false)]
@@ -428,7 +317,7 @@ mod tests {
428317
span_id: Annotated::new("438f40bd3b4a41ee".parse().unwrap()),
429318
parent_span_id: Annotated::empty(),
430319
status: Annotated::new(SpanV2Status::Ok),
431-
kind: Annotated::new(SpanV2Kind::Server),
320+
kind: Annotated::new(SpanKind::Server),
432321
is_remote: Annotated::new(true),
433322
links: Annotated::new(links),
434323
attributes: Annotated::new(attributes),

relay-spans/src/otel_to_sentry_v2.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use chrono::{TimeZone, Utc};
22
use opentelemetry_proto::tonic::trace::v1::span::Link as OtelLink;
33
use opentelemetry_proto::tonic::trace::v1::span::SpanKind as OtelSpanKind;
4-
use relay_event_schema::protocol::{Attributes, SpanV2Kind};
4+
use relay_event_schema::protocol::{Attributes, SpanKind};
55
use relay_otel::otel_value_to_attribute;
66
use relay_protocol::ErrorKind;
77

@@ -116,14 +116,14 @@ fn otel_flags_is_remote(value: u32) -> Option<bool> {
116116
}
117117
}
118118

119-
fn otel_to_sentry_kind(kind: i32) -> Annotated<SpanV2Kind> {
119+
fn otel_to_sentry_kind(kind: i32) -> Annotated<SpanKind> {
120120
match kind {
121121
kind if kind == OtelSpanKind::Unspecified as i32 => Annotated::empty(),
122-
kind if kind == OtelSpanKind::Internal as i32 => Annotated::new(SpanV2Kind::Internal),
123-
kind if kind == OtelSpanKind::Server as i32 => Annotated::new(SpanV2Kind::Server),
124-
kind if kind == OtelSpanKind::Client as i32 => Annotated::new(SpanV2Kind::Client),
125-
kind if kind == OtelSpanKind::Producer as i32 => Annotated::new(SpanV2Kind::Producer),
126-
kind if kind == OtelSpanKind::Consumer as i32 => Annotated::new(SpanV2Kind::Consumer),
122+
kind if kind == OtelSpanKind::Internal as i32 => Annotated::new(SpanKind::Internal),
123+
kind if kind == OtelSpanKind::Server as i32 => Annotated::new(SpanKind::Server),
124+
kind if kind == OtelSpanKind::Client as i32 => Annotated::new(SpanKind::Client),
125+
kind if kind == OtelSpanKind::Producer as i32 => Annotated::new(SpanKind::Producer),
126+
kind if kind == OtelSpanKind::Consumer as i32 => Annotated::new(SpanKind::Consumer),
127127
_ => Annotated::from_error(ErrorKind::InvalidData, Some(Value::I64(kind as i64))),
128128
}
129129
}

relay-spans/src/v1_to_v2.rs

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ use std::borrow::Cow;
22

33
use relay_event_schema::protocol::{
44
Attribute, AttributeType, AttributeValue, Attributes, JsonLenientString, Span as SpanV1,
5-
SpanData, SpanKind as SpanV1Kind, SpanLink, SpanStatus as SpanV1Status, SpanV2, SpanV2Kind,
6-
SpanV2Link, SpanV2Status,
5+
SpanData, SpanLink, SpanStatus as SpanV1Status, SpanV2, SpanV2Link, SpanV2Status,
76
};
87
use relay_protocol::{Annotated, Empty, Error, IntoValue, Meta, Value};
98

@@ -112,7 +111,7 @@ pub fn span_v1_to_span_v2(span_v1: SpanV1) -> SpanV2 {
112111
.into(),
113112
status: Annotated::map_value(status, span_v1_status_to_span_v2_status),
114113
is_remote,
115-
kind: Annotated::map_value(kind, span_v1_kind_to_span_v2_kind),
114+
kind,
116115
start_timestamp,
117116
end_timestamp: timestamp,
118117
links: links.map_value(span_v1_links_to_span_v2_links),
@@ -128,17 +127,6 @@ fn span_v1_status_to_span_v2_status(status: SpanV1Status) -> SpanV2Status {
128127
}
129128
}
130129

131-
fn span_v1_kind_to_span_v2_kind(kind: SpanV1Kind) -> SpanV2Kind {
132-
match kind {
133-
SpanV1Kind::Internal => SpanV2Kind::Internal,
134-
SpanV1Kind::Server => SpanV2Kind::Server,
135-
SpanV1Kind::Client => SpanV2Kind::Client,
136-
SpanV1Kind::Producer => SpanV2Kind::Producer,
137-
SpanV1Kind::Consumer => SpanV2Kind::Consumer,
138-
// TODO: implement catchall type so outdated customer relays can still forward the field.
139-
}
140-
}
141-
142130
fn span_v1_links_to_span_v2_links(links: Vec<Annotated<SpanLink>>) -> Vec<Annotated<SpanV2Link>> {
143131
links
144132
.into_iter()

relay-spans/src/v2_to_v1.rs

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use relay_event_schema::protocol::{SpanKind, SpanV2Link};
55
use crate::status_codes;
66
use relay_event_schema::protocol::{
77
Attributes, EventId, Span as SpanV1, SpanData, SpanId, SpanLink, SpanStatus, SpanV2,
8-
SpanV2Kind, SpanV2Status,
8+
SpanV2Status,
99
};
1010
use relay_protocol::{Annotated, FromValue, Object, Value};
1111
use url::Url;
@@ -151,7 +151,7 @@ pub fn span_v2_to_span_v1(span_v2: SpanV2) -> SpanV1 {
151151
timestamp: end_timestamp,
152152
trace_id,
153153
platform,
154-
kind: kind.map_value(span_v2_kind_to_span_v1_kind),
154+
kind,
155155
links,
156156
..Default::default()
157157
}
@@ -186,17 +186,6 @@ fn span_v2_status_to_span_v1_status(
186186
})
187187
.or_else(|| Annotated::new(SpanStatus::Unknown))
188188
}
189-
190-
fn span_v2_kind_to_span_v1_kind(kind: SpanV2Kind) -> SpanKind {
191-
match kind {
192-
SpanV2Kind::Internal => SpanKind::Internal,
193-
SpanV2Kind::Server => SpanKind::Server,
194-
SpanV2Kind::Client => SpanKind::Client,
195-
SpanV2Kind::Producer => SpanKind::Producer,
196-
SpanV2Kind::Consumer => SpanKind::Consumer,
197-
}
198-
}
199-
200189
fn span_v2_link_to_span_v1_link(link: SpanV2Link) -> SpanLink {
201190
let SpanV2Link {
202191
trace_id,
@@ -242,8 +231,8 @@ fn derive_op_for_v2_span(span: &SpanV2) -> String {
242231

243232
if attributes.contains_key("http.request.method") || attributes.contains_key("http.method") {
244233
return match span.kind.value() {
245-
Some(SpanV2Kind::Client) => String::from("http.client"),
246-
Some(SpanV2Kind::Server) => String::from("http.server"),
234+
Some(SpanKind::Client) => String::from("http.client"),
235+
Some(SpanKind::Server) => String::from("http.server"),
247236
_ => {
248237
if attributes.contains_key("sentry.http.prefetch") {
249238
String::from("http.prefetch")
@@ -304,7 +293,7 @@ fn derive_description_for_v2_span(span: &SpanV2) -> Option<String> {
304293
description
305294
}
306295

307-
fn derive_http_description(attributes: &Attributes, kind: &Option<&SpanV2Kind>) -> Option<String> {
296+
fn derive_http_description(attributes: &Attributes, kind: &Option<&SpanKind>) -> Option<String> {
308297
// Get HTTP method
309298
let http_method = attributes
310299
.get_value("http.request.method")
@@ -315,8 +304,8 @@ fn derive_http_description(attributes: &Attributes, kind: &Option<&SpanV2Kind>)
315304

316305
// Get URL path information
317306
let url_path = match kind {
318-
Some(SpanV2Kind::Server) => get_server_url_path(attributes),
319-
Some(SpanV2Kind::Client) => get_client_url_path(attributes),
307+
Some(SpanKind::Server) => get_server_url_path(attributes),
308+
Some(SpanKind::Client) => get_client_url_path(attributes),
320309
_ => None,
321310
};
322311

0 commit comments

Comments
 (0)