From d12c96edaf56ca051abd5df10466420460dd9ca9 Mon Sep 17 00:00:00 2001 From: Riccardo Busetti Date: Mon, 13 May 2024 08:29:11 +0200 Subject: [PATCH] feat(spans): Add event. getters for Span (#3577) --- CHANGELOG.md | 1 + relay-event-schema/src/protocol/span.rs | 107 ++++++++++++++++-------- 2 files changed, 75 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf31464010..30de7b5352 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ - Emit negative outcomes for denied metrics. ([#3508](https://github.com/getsentry/relay/pull/3508)) - Increase size limits for internal batch endpoints. ([#3562](https://github.com/getsentry/relay/pull/3562)) - Emit negative outcomes when metrics are rejected because of a disabled namespace. ([#3544](https://github.com/getsentry/relay/pull/3544)) +- Add support for `event.` in the `Span` `Getter` implementation. ([#3577](https://github.com/getsentry/relay/pull/3577)) ## 24.4.2 diff --git a/relay-event-schema/src/protocol/span.rs b/relay-event-schema/src/protocol/span.rs index 0d05faaa72..e95d9eebb6 100644 --- a/relay-event-schema/src/protocol/span.rs +++ b/relay-event-schema/src/protocol/span.rs @@ -111,41 +111,56 @@ pub struct Span { impl Getter for Span { fn get_value(&self, path: &str) -> Option> { - Some(match path.strip_prefix("span.")? { - "exclusive_time" => self.exclusive_time.value()?.into(), - "description" => self.description.as_str()?.into(), - "op" => self.op.as_str()?.into(), - "span_id" => self.span_id.as_str()?.into(), - "parent_span_id" => self.parent_span_id.as_str()?.into(), - "trace_id" => self.trace_id.as_str()?.into(), - "status" => self.status.as_str()?.into(), - "origin" => self.origin.as_str()?.into(), - "duration" => { - let start_timestamp = *self.start_timestamp.value()?; - let timestamp = *self.timestamp.value()?; - relay_common::time::chrono_to_positive_millis(timestamp - start_timestamp).into() - } - "was_transaction" => self.was_transaction.value().unwrap_or(&false).into(), - path => { - if let Some(key) = path.strip_prefix("tags.") { - self.tags.value()?.get(key)?.as_str()?.into() - } else if let Some(key) = path.strip_prefix("data.") { - self.data.value()?.get_value(key)? - } else if let Some(key) = path.strip_prefix("sentry_tags.") { - self.sentry_tags.value()?.get(key)?.as_str()?.into() - } else if let Some(rest) = path.strip_prefix("measurements.") { - let name = rest.strip_suffix(".value")?; - self.measurements - .value()? - .get(name)? - .value()? - .value - .value()? + let span_prefix = path.strip_prefix("span."); + if let Some(span_prefix) = span_prefix { + return Some(match span_prefix { + "exclusive_time" => self.exclusive_time.value()?.into(), + "description" => self.description.as_str()?.into(), + "op" => self.op.as_str()?.into(), + "span_id" => self.span_id.as_str()?.into(), + "parent_span_id" => self.parent_span_id.as_str()?.into(), + "trace_id" => self.trace_id.as_str()?.into(), + "status" => self.status.as_str()?.into(), + "origin" => self.origin.as_str()?.into(), + "duration" => { + let start_timestamp = *self.start_timestamp.value()?; + let timestamp = *self.timestamp.value()?; + relay_common::time::chrono_to_positive_millis(timestamp - start_timestamp) .into() - } else { - return None; } - } + "was_transaction" => self.was_transaction.value().unwrap_or(&false).into(), + path => { + if let Some(key) = path.strip_prefix("tags.") { + self.tags.value()?.get(key)?.as_str()?.into() + } else if let Some(key) = path.strip_prefix("data.") { + self.data.value()?.get_value(key)? + } else if let Some(key) = path.strip_prefix("sentry_tags.") { + self.sentry_tags.value()?.get(key)?.as_str()?.into() + } else if let Some(rest) = path.strip_prefix("measurements.") { + let name = rest.strip_suffix(".value")?; + self.measurements + .value()? + .get(name)? + .value()? + .value + .value()? + .into() + } else { + return None; + } + } + }); + } + + // For backward compatibility with event-based rules, we try to support `event.` fields also + // for a span. + let event_prefix = path.strip_prefix("event.")?; + Some(match event_prefix { + "release" => self.data.value()?.release.as_str()?.into(), + "environment" => self.data.value()?.environment.as_str()?.into(), + "transaction" => self.data.value()?.segment_name.as_str()?.into(), + // TODO: we might want to add additional fields once they are added to the span. + _ => return None, }) } } @@ -513,6 +528,32 @@ mod tests { assert!(!RuleCondition::eq("span.was_transaction", false).matches(&span)); } + #[test] + fn test_span_fields_as_event() { + let span = Annotated::::from_json( + r#"{ + "data": { + "release": "1.0", + "environment": "prod", + "sentry.segment.name": "/api/endpoint" + } + }"#, + ) + .unwrap() + .into_value() + .unwrap(); + + assert_eq!(span.get_value("event.release"), Some(Val::String("1.0"))); + assert_eq!( + span.get_value("event.environment"), + Some(Val::String("prod")) + ); + assert_eq!( + span.get_value("event.transaction"), + Some(Val::String("/api/endpoint")) + ); + } + #[test] fn test_span_duration() { let span = Annotated::::from_json(