diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f628fe663b..1b679b05e34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,10 +22,9 @@ - No longer writes Spans as trace items. ([#5152](https://github.com/getsentry/relay/pull/5152)) - Produce spans to `ingest-spans` by default. ([#5163](https://github.com/getsentry/relay/pull/5163)) -- Add ability to produce Span V2 Kafka messages. ([#5151](https://github.com/getsentry/relay/pull/5151)) - Add `retentions` to the project configuration and use them for logs. ([#5135](https://github.com/getsentry/relay/pull/5135)) +- Produce Span V2 Kafka messages. ([#5151](https://github.com/getsentry/relay/pull/5151), [#5173](https://github.com/getsentry/relay/pull/5173), [#5199](https://github.com/getsentry/relay/pull/5199)) - Modernize session processing and move to Relay's new processing framework. ([#5201](https://github.com/getsentry/relay/pull/5201)) -- Add ability to produce Span V2 Kafka messages. ([#5151](https://github.com/getsentry/relay/pull/5151), [#5173](https://github.com/getsentry/relay/pull/5173)) ## 25.9.0 diff --git a/Cargo.lock b/Cargo.lock index a07954f5351..be00199603f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4787,9 +4787,9 @@ dependencies = [ [[package]] name = "sentry-kafka-schemas" -version = "2.1.1" +version = "2.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f3a669a7e34855a4ab0bf3698383adf78de4a723e7a845af374be2ae935519" +checksum = "a8757c054b3dd24d457fb4b0f63a42eac257ec25640f2bdc66332f6f5956be3f" dependencies = [ "jsonschema", "prost", diff --git a/Cargo.toml b/Cargo.toml index 123d0b7ca12..e1197b725c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -177,7 +177,7 @@ sentry = { version = "0.41.0", default-features = false, features = [ "transport", ] } sentry-core = "0.41.0" -sentry-kafka-schemas = { version = "2.1.1", default-features = false } +sentry-kafka-schemas = { version = "2.1.6", default-features = false } sentry-release-parser = { version = "1.3.2", default-features = false } sentry-types = "0.41.0" sentry_protos = "0.3.3" diff --git a/relay-dynamic-config/src/global.rs b/relay-dynamic-config/src/global.rs index ada87d094b0..d3465c58266 100644 --- a/relay-dynamic-config/src/global.rs +++ b/relay-dynamic-config/src/global.rs @@ -186,14 +186,6 @@ pub struct Options { )] pub replay_relay_snuba_publish_disabled_sample_rate: f32, - /// Fraction of spans that are produced as backward-compatible Span V2 kafka messages. - #[serde( - rename = "relay.kafka.span-v2.sample-rate", - deserialize_with = "default_on_error", - skip_serializing_if = "is_default" - )] - pub span_kafka_v2_sample_rate: f32, - /// All other unknown options. #[serde(flatten)] other: HashMap, diff --git a/relay-server/src/managed/counted.rs b/relay-server/src/managed/counted.rs index 974a294fa26..ed4f7d23465 100644 --- a/relay-server/src/managed/counted.rs +++ b/relay-server/src/managed/counted.rs @@ -1,5 +1,5 @@ use relay_event_schema::protocol::{ - OurLog, SessionAggregateItem, SessionAggregates, SessionUpdate, Span, SpanV2, + CompatSpan, OurLog, SessionAggregateItem, SessionAggregates, SessionUpdate, Span, SpanV2, }; use relay_protocol::Annotated; use relay_quotas::DataCategory; @@ -105,6 +105,12 @@ impl Counted for Annotated { } } +impl Counted for Annotated { + fn quantities(&self) -> Quantities { + smallvec::smallvec![(DataCategory::Span, 1), (DataCategory::SpanIndexed, 1)] + } +} + impl Counted for ExtractedMetrics { fn quantities(&self) -> Quantities { // We only consider project metrics, sampling project metrics should never carry outcomes, diff --git a/relay-server/src/processing/spans/mod.rs b/relay-server/src/processing/spans/mod.rs index 2ad0ce65616..8dbb23fd4ad 100644 --- a/relay-server/src/processing/spans/mod.rs +++ b/relay-server/src/processing/spans/mod.rs @@ -2,6 +2,8 @@ use std::sync::Arc; use relay_event_normalization::GeoIpLookup; use relay_event_schema::processor::ProcessingAction; +#[cfg(feature = "processing")] +use relay_event_schema::protocol::CompatSpan; use relay_event_schema::protocol::SpanV2; use relay_quotas::{DataCategory, RateLimits}; @@ -197,14 +199,24 @@ impl Forward for SpanOutput { let envelope = spans.map(|spans, records| { let mut items = Items::with_capacity(spans.spans.len()); for span in spans.spans { - let mut span = span.value.map_value(relay_spans::span_v2_to_span_v1); + use relay_protocol::Annotated; + + let mut span = match span.value.map_value(CompatSpan::try_from) { + Annotated(Some(Result::Err(error)), _) => { + // TODO: Use records.internal_error(error, span) + relay_log::error!( + error = &error as &dyn std::error::Error, + "Failed to create CompatSpan" + ); + continue; + } + Annotated(Some(Result::Ok(compat_span)), meta) => { + Annotated(Some(compat_span), meta) + } + Annotated(None, meta) => Annotated(None, meta), + }; if let Some(span) = span.value_mut() { - inject_server_sample_rate(span, spans.server_sample_rate); - - // TODO: this needs to be done in a normalization step, which is yet to be - // implemented. - span.received = - relay_event_schema::protocol::Timestamp(chrono::Utc::now()).into(); + inject_server_sample_rate(&mut span.span_v2, spans.server_sample_rate); } let mut item = Item::new(ItemType::Span); @@ -215,7 +227,10 @@ impl Forward for SpanOutput { continue; } }; - item.set_payload(ContentType::Json, payload); + item.set_payload(ContentType::CompatSpan, payload); + if let Some(trace_id) = span.value().and_then(|s| s.span_v2.trace_id.value()) { + item.set_routing_hint(*trace_id.as_ref()); + } items.push(item); } @@ -238,24 +253,14 @@ impl Forward for SpanOutput { /// Ideally we forward a proper data structure to the store instead, then we don't /// have to inject the sample rate into a measurement. #[cfg(feature = "processing")] -fn inject_server_sample_rate( - span: &mut relay_event_schema::protocol::Span, - server_sample_rate: Option, -) { +fn inject_server_sample_rate(span: &mut SpanV2, server_sample_rate: Option) { let Some(server_sample_rate) = server_sample_rate.and_then(relay_protocol::FiniteF64::new) else { return; }; - let measurements = span.measurements.get_or_insert_with(Default::default); - measurements.0.insert( - "server_sample_rate".to_owned(), - relay_event_schema::protocol::Measurement { - value: server_sample_rate.into(), - unit: None.into(), - } - .into(), - ); + let attributes = span.attributes.get_or_insert_with(Default::default); + attributes.insert("sentry.server_sample_rate", server_sample_rate.to_f64()); } /// Spans in their serialized state, as transported in an envelope. diff --git a/relay-server/src/services/processor/span/processing.rs b/relay-server/src/services/processor/span/processing.rs index 1d0df923a9e..b0852805b89 100644 --- a/relay-server/src/services/processor/span/processing.rs +++ b/relay-server/src/services/processor/span/processing.rs @@ -91,7 +91,6 @@ pub async fn process( geo_lookup, ); - let org_id = managed_envelope.scoping().organization_id.value(); let client_ip = managed_envelope.envelope().meta().client_addr(); let filter_settings = &project_info.config.filter_settings; let sampling_decision = sampling_result.decision(); @@ -230,8 +229,7 @@ pub async fn process( } }; - let Ok(mut new_item) = create_span_item(annotated_span, &config, global_config, org_id) - else { + let Ok(mut new_item) = create_span_item(annotated_span, &config) else { return ItemAction::Drop(Outcome::Invalid(DiscardReason::Internal)); }; @@ -254,14 +252,9 @@ pub async fn process( } } -fn create_span_item( - span: Annotated, - config: &Config, - global_config: &GlobalConfig, - org_id: u64, -) -> Result { +fn create_span_item(span: Annotated, config: &Config) -> Result { let mut new_item = Item::new(ItemType::Span); - if produce_compat_spans(config, global_config, org_id) { + if cfg!(feature = "processing") && config.processing_enabled() { let span_v2 = span.map_value(relay_spans::span_v1_to_span_v2); let compat_span = match span_v2.map_value(CompatSpan::try_from) { Annotated(Some(Result::Err(err)), _) => { @@ -297,15 +290,6 @@ fn create_span_item( Ok(new_item) } -/// Whether or not to convert spans into backward-compatible V2 spans. -/// -/// This only makes sense when we forward the envelope to Kafka. -fn produce_compat_spans(config: &Config, global_config: &GlobalConfig, org_id: u64) -> bool { - cfg!(feature = "processing") - && config.processing_enabled() - && utils::is_rolled_out(org_id, global_config.options.span_kafka_v2_sample_rate).is_keep() -} - fn add_sample_rate(measurements: &mut Annotated, name: &str, value: Option) { let value = match value { Some(value) if value > 0.0 => value, @@ -352,8 +336,6 @@ pub fn extract_from_event( .dsc() .and_then(|ctx| ctx.sample_rate); - let org_id = managed_envelope.scoping().organization_id.value(); - let mut add_span = |mut span: Span| { add_sample_rate( &mut span.measurements, @@ -387,7 +369,7 @@ pub fn extract_from_event( } }; - let Ok(mut item) = create_span_item(span, &config, global_config, org_id) else { + let Ok(mut item) = create_span_item(span, &config) else { managed_envelope.track_outcome( Outcome::Invalid(DiscardReason::InvalidSpan), relay_quotas::DataCategory::SpanIndexed, diff --git a/relay-server/src/services/store.rs b/relay-server/src/services/store.rs index 66e4fb5eb4f..ae365d82d9b 100644 --- a/relay-server/src/services/store.rs +++ b/relay-server/src/services/store.rs @@ -12,9 +12,7 @@ use futures::FutureExt; use futures::future::BoxFuture; use prost::Message as _; use sentry_protos::snuba::v1::{TraceItem, TraceItemType}; -use serde::ser::SerializeMap; use serde::{Deserialize, Serialize}; -use serde_json::Deserializer; use serde_json::value::RawValue; use uuid::Uuid; @@ -23,7 +21,7 @@ use relay_base_schema::organization::OrganizationId; use relay_base_schema::project::ProjectId; use relay_common::time::UnixTimestamp; use relay_config::Config; -use relay_event_schema::protocol::{EventId, VALID_PLATFORMS, datetime_to_timestamp}; +use relay_event_schema::protocol::{EventId, datetime_to_timestamp}; use relay_kafka::{ClientError, KafkaClient, KafkaTopic, Message, SerializationOutput}; use relay_metrics::{ Bucket, BucketView, BucketViewValue, BucketsView, ByNamespace, GaugeValue, MetricName, @@ -353,16 +351,20 @@ impl StoreService { let client = envelope.meta().client(); self.produce_check_in(scoping.project_id, received_at, client, retention, item)? } - ItemType::Span if content_type == Some(&ContentType::Json) => self.produce_span( - scoping, - received_at, - event_id, - retention, - downsampled_retention, - item, - )?, + ItemType::Span if content_type == Some(&ContentType::Json) => { + relay_log::error!("Store producer received legacy span"); + self.outcome_aggregator.send(TrackOutcome { + category: DataCategory::SpanIndexed, + event_id: None, + outcome: Outcome::Invalid(DiscardReason::Internal), + quantity: 1, + remote_addr: None, + scoping, + timestamp: received_at, + }); + } ItemType::Span if content_type == Some(&ContentType::CompatSpan) => self - .produce_span_v2( + .produce_span( scoping, received_at, event_id, @@ -649,21 +651,6 @@ impl StoreService { metric_encoding = metric.value.encoding().unwrap_or(""), ); } - KafkaMessage::Span { message: span, .. } => { - let is_segment = span.is_segment; - let has_parent = span.parent_span_id.is_some(); - let platform = VALID_PLATFORMS.iter().find(|p| *p == &span.platform); - - metric!( - counter(RelayCounters::ProcessingMessageProduced) += 1, - event_type = message.variant(), - topic = topic_name, - platform = platform.unwrap_or(&""), - is_segment = bool_to_str(is_segment), - has_parent = bool_to_str(has_parent), - topic = topic_name, - ); - } KafkaMessage::ReplayRecordingNotChunked(replay) => { let has_video = replay.replay_video.is_some(); @@ -1024,81 +1011,6 @@ impl StoreService { retention_days: u16, downsampled_retention_days: u16, item: &Item, - ) -> Result<(), StoreError> { - relay_log::trace!("Producing span"); - - let payload = item.payload(); - let d = &mut Deserializer::from_slice(&payload); - let mut span: SpanKafkaMessage = match serde_path_to_error::deserialize(d) { - Ok(span) => span, - Err(error) => { - relay_log::error!( - error = &error as &dyn std::error::Error, - "failed to parse span" - ); - self.outcome_aggregator.send(TrackOutcome { - category: DataCategory::SpanIndexed, - event_id: None, - outcome: Outcome::Invalid(DiscardReason::InvalidSpan), - quantity: 1, - remote_addr: None, - scoping, - timestamp: received_at, - }); - return Ok(()); - } - }; - - // Discard measurements with empty `value`s. - if let Some(measurements) = &mut span.measurements { - measurements.retain(|_, v| v.as_ref().and_then(|v| v.value).is_some()); - } - - span.backfill_data(); - span.duration_ms = - ((span.end_timestamp_precise - span.start_timestamp_precise) * 1e3) as u32; - span.event_id = event_id; - span.organization_id = scoping.organization_id.value(); - span.project_id = scoping.project_id.value(); - span.retention_days = retention_days; - span.downsampled_retention_days = downsampled_retention_days; - span.start_timestamp_ms = (span.start_timestamp_precise * 1e3) as u64; - span.key_id = scoping.key_id; - - self.produce( - KafkaTopic::Spans, - KafkaMessage::Span { - headers: BTreeMap::from([( - "project_id".to_owned(), - scoping.project_id.to_string(), - )]), - message: span, - }, - )?; - - // XXX: Temporarily produce span outcomes also for JSON spans. Keep in sync with either EAP - // or the segments consumer, depending on which will produce outcomes later. - self.outcome_aggregator.send(TrackOutcome { - category: DataCategory::SpanIndexed, - event_id: None, - outcome: Outcome::Accepted, - quantity: 1, - remote_addr: None, - scoping, - timestamp: received_at, - }); - - Ok(()) - } - - fn produce_span_v2( - &self, - scoping: Scoping, - received_at: DateTime, - event_id: Option, - retention_days: u16, - downsampled_retention_days: u16, - item: &Item, ) -> Result<(), StoreError> { debug_assert_eq!(item.ty(), &ItemType::Span); debug_assert_eq!(item.content_type(), Some(&ContentType::CompatSpan)); @@ -1111,7 +1023,7 @@ impl StoreService { } = scoping; let payload = item.payload(); - let message = SpanV2KafkaMessage { + let message = SpanKafkaMessage { meta: SpanMeta { organization_id, project_id, @@ -1128,7 +1040,7 @@ impl StoreService { relay_statsd::metric!(counter(RelayCounters::SpanV2Produced) += 1); self.produce( KafkaTopic::Spans, - KafkaMessage::SpanV2 { + KafkaMessage::Span { routing_key: item.routing_hint(), headers: BTreeMap::from([( "project_id".to_owned(), @@ -1264,27 +1176,6 @@ where .serialize(serializer) } -fn serialize_btreemap_skip_nulls( - map: &Option>>, - serializer: S, -) -> Result -where - K: Serialize, - S: serde::Serializer, - T: serde::Serialize, -{ - let Some(map) = map else { - return serializer.serialize_none(); - }; - let mut m = serializer.serialize_map(Some(map.len()))?; - for (key, value) in map.iter() { - if let Some(value) = value { - m.serialize_entry(key, value)?; - } - } - m.end() -} - /// Container payload for event messages. #[derive(Debug, Serialize)] struct EventKafkaMessage { @@ -1469,115 +1360,8 @@ struct CheckInKafkaMessage { retention_days: u16, } -#[derive(Debug, Deserialize, Serialize, Clone)] -struct SpanLink<'a> { - pub trace_id: &'a str, - pub span_id: &'a str, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub sampled: Option, - #[serde(borrow)] - pub attributes: Option<&'a RawValue>, -} - -#[derive(Debug, Deserialize, Serialize, Clone)] -struct SpanMeasurement<'a> { - #[serde(skip_serializing_if = "Option::is_none", borrow)] - value: Option<&'a RawValue>, -} - -#[derive(Debug, Deserialize, Serialize, Clone)] -struct SpanKafkaMessage<'a> { - #[serde(skip_serializing_if = "Option::is_none", borrow)] - description: Option>, - #[serde(default)] - duration_ms: u32, - /// The ID of the transaction event associated to this span, if any. - #[serde(default, skip_serializing_if = "Option::is_none")] - event_id: Option, - #[serde(rename(deserialize = "exclusive_time"))] - exclusive_time_ms: f64, - #[serde(default)] - is_segment: bool, - #[serde(default)] - is_remote: bool, - - #[serde(skip_serializing_if = "none_or_empty_map", borrow)] - data: Option, Option<&'a RawValue>>>, - #[serde(default, skip_serializing_if = "Option::is_none")] - kind: Option>, - #[serde(default, skip_serializing_if = "none_or_empty_vec")] - links: Option>>, - #[serde(borrow, default, skip_serializing_if = "Option::is_none")] - measurements: Option, Option>>>, - #[serde(default)] - organization_id: u64, - #[serde(borrow, default, skip_serializing_if = "Option::is_none")] - origin: Option>, - #[serde(default, skip_serializing_if = "Option::is_none")] - parent_span_id: Option>, - #[serde(default, skip_serializing_if = "Option::is_none")] - profile_id: Option>, - /// The numeric ID of the project. - #[serde(default)] - project_id: u64, - /// Time at which the event was received by Relay. Not to be confused with `start_timestamp_ms`. - received: f64, - /// Number of days until these data should be deleted. - #[serde(default)] - retention_days: u16, - /// Number of days until the downsampled version of this data should be deleted. - #[serde(default)] - downsampled_retention_days: u16, - #[serde(default, skip_serializing_if = "Option::is_none")] - segment_id: Option>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - serialize_with = "serialize_btreemap_skip_nulls" - )] - #[serde(borrow)] - sentry_tags: Option, Option<&'a RawValue>>>, - span_id: Cow<'a, str>, - #[serde(skip_serializing_if = "none_or_empty_map", borrow)] - tags: Option, Option<&'a RawValue>>>, - trace_id: EventId, - - #[serde(default)] - start_timestamp_ms: u64, - #[serde(rename(deserialize = "start_timestamp"))] - start_timestamp_precise: f64, - #[serde(rename(deserialize = "timestamp"))] - end_timestamp_precise: f64, - - #[serde(borrow, default, skip_serializing)] - platform: Cow<'a, str>, // We only use this for logging for now - - #[serde(default, skip_serializing_if = "Option::is_none")] - client_sample_rate: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - server_sample_rate: Option, - - #[serde( - default, - rename = "_meta", - skip_serializing_if = "none_or_empty_object" - )] - meta: Option<&'a RawValue>, - - #[serde( - default, - rename = "_performance_issues_spans", - skip_serializing_if = "Option::is_none" - )] - performance_issues_spans: Option, - - // Required for the buffer to emit outcomes scoped to the DSN. - #[serde(skip_serializing_if = "Option::is_none")] - key_id: Option, -} - #[derive(Debug, Serialize)] -struct SpanV2KafkaMessage<'a> { +struct SpanKafkaMessage<'a> { #[serde(flatten)] meta: SpanMeta, #[serde(flatten)] @@ -1601,109 +1385,6 @@ struct SpanMeta { downsampled_retention_days: u16, } -impl SpanKafkaMessage<'_> { - /// Backfills `data` based on `sentry_tags`, `tags`, and `measurements`. - /// - /// * Every item in `sentry_tags` is copied to `data`, with the key prefixed with `sentry.`. - /// The only exception is the `description` tag, which is copied as `sentry.normalized_description`. - /// - /// * Every item in `tags` is copied to `data` verbatim, with the exception of `description`, which - /// is copied as `sentry.normalized_description`. - /// - /// * The value of every item in `measurements` is copied to `data` with the same key, with the exceptions - /// of `client_sample_rate` and `server_sample_rate`. Those measurements are instead written to the top-level - /// fields of the same names. - /// - /// In no case are existing keys overwritten. Thus, from highest to lowest, the order of precedence is - /// * existing values in `data` - /// * `measurements` - /// * `tags` - /// * `sentry_tags` - fn backfill_data(&mut self) { - let data = self.data.get_or_insert_default(); - - if let Some(measurements) = &self.measurements { - for (key, value) in measurements { - let Some(value) = value.as_ref().and_then(|v| v.value) else { - continue; - }; - - match key.as_ref() { - "client_sample_rate" => { - data.entry(Cow::Borrowed("sentry.client_sample_rate")) - .or_insert(Some(value)); - - if let Ok(client_sample_rate) = Deserialize::deserialize(value) { - self.client_sample_rate = Some(client_sample_rate); - } - } - "server_sample_rate" => { - data.entry(Cow::Borrowed("sentry.server_sample_rate")) - .or_insert(Some(value)); - - if let Ok(server_sample_rate) = Deserialize::deserialize(value) { - self.server_sample_rate = Some(server_sample_rate); - } - } - _ => { - data.entry(key.clone()).or_insert(Some(value)); - } - } - } - } - - if let Some(tags) = &self.tags { - for (key, value) in tags { - let Some(value) = value else { - continue; - }; - - let key = if *key == "description" { - Cow::Borrowed("sentry.normalized_description") - } else { - key.clone() - }; - - data.entry(key).or_insert(Some(value)); - } - } - - if let Some(sentry_tags) = &self.sentry_tags { - for (key, value) in sentry_tags { - let Some(value) = value else { - continue; - }; - - let key = if *key == "description" { - Cow::Borrowed("sentry.normalized_description") - } else { - Cow::Owned(format!("sentry.{key}")) - }; - - data.entry(key).or_insert(Some(value)); - } - } - } -} - -fn none_or_empty_object(value: &Option<&RawValue>) -> bool { - match value { - None => true, - Some(raw) => raw.get() == "{}", - } -} - -fn none_or_empty_vec(value: &Option>) -> bool { - match &value { - Some(vec) => vec.is_empty(), - None => true, - } -} - -fn none_or_empty_map(value: &Option>) -> bool { - value.as_ref().is_none_or(BTreeMap::is_empty) -} - #[derive(Clone, Debug, Serialize)] struct ProfileChunkKafkaMessage { organization_id: OrganizationId, @@ -1736,18 +1417,12 @@ enum KafkaMessage<'a> { message: TraceItem, }, Span { - #[serde(skip)] - headers: BTreeMap, - #[serde(flatten)] - message: SpanKafkaMessage<'a>, - }, - SpanV2 { #[serde(skip)] routing_key: Option, #[serde(skip)] headers: BTreeMap, #[serde(flatten)] - message: SpanV2KafkaMessage<'a>, + message: SpanKafkaMessage<'a>, }, Attachment(AttachmentKafkaMessage), @@ -1773,7 +1448,7 @@ impl Message for KafkaMessage<'_> { MetricNamespace::Unsupported => "metric_unsupported", }, KafkaMessage::CheckIn(_) => "check_in", - KafkaMessage::Span { .. } | KafkaMessage::SpanV2 { .. } => "span", + KafkaMessage::Span { .. } => "span", KafkaMessage::Item { item_type, .. } => item_type.as_str_name(), KafkaMessage::Attachment(_) => "attachment", @@ -1792,8 +1467,7 @@ impl Message for KafkaMessage<'_> { match self { Self::Event(message) => Some(message.event_id.0), Self::UserReport(message) => Some(message.event_id.0), - Self::Span { message, .. } => Some(message.trace_id.0), - Self::SpanV2 { routing_key, .. } => *routing_key, + Self::Span { routing_key, .. } => *routing_key, // Monitor check-ins use the hinted UUID passed through from the Envelope. // @@ -1820,7 +1494,6 @@ impl Message for KafkaMessage<'_> { match &self { KafkaMessage::Metric { headers, .. } | KafkaMessage::Span { headers, .. } - | KafkaMessage::SpanV2 { headers, .. } | KafkaMessage::Item { headers, .. } | KafkaMessage::Profile(ProfileKafkaMessage { headers, .. }) => Some(headers), @@ -1840,7 +1513,6 @@ impl Message for KafkaMessage<'_> { KafkaMessage::Metric { message, .. } => serialize_as_json(message), KafkaMessage::ReplayEvent(message) => serialize_as_json(message), KafkaMessage::Span { message, .. } => serialize_as_json(message), - KafkaMessage::SpanV2 { message, .. } => serialize_as_json(message), KafkaMessage::Item { message, .. } => { let mut payload = Vec::new(); match message.encode(&mut payload) { @@ -1914,134 +1586,4 @@ mod tests { assert!(matches!(res, Err(ClientError::InvalidTopicName))); } } - - #[test] - fn backfill() { - let json = r#"{ - "description": "/api/0/relays/projectconfigs/", - "duration_ms": 152, - "exclusive_time": 0.228, - "is_segment": true, - "data": { - "sentry.environment": "development", - "sentry.release": "backend@24.7.0.dev0+c45b49caed1e5fcbf70097ab3f434b487c359b6b", - "thread.name": "uWSGIWorker1Core0", - "thread.id": "8522009600", - "sentry.segment.name": "/api/0/relays/projectconfigs/", - "sentry.sdk.name": "sentry.python.django", - "sentry.sdk.version": "2.7.0", - "my.float.field": 101.2, - "my.int.field": 2000, - "my.neg.field": -100, - "my.neg.float.field": -101.2, - "my.true.bool.field": true, - "my.false.bool.field": false, - "my.dict.field": { - "id": 42, - "name": "test" - }, - "my.u64.field": 9447000002305251000, - "my.array.field": [1, 2, ["nested", "array"]] - }, - "measurements": { - "num_of_spans": {"value": 50.0}, - "client_sample_rate": {"value": 0.1}, - "server_sample_rate": {"value": 0.2} - }, - "profile_id": "56c7d1401ea14ad7b4ac86de46baebae", - "organization_id": 1, - "origin": "auto.http.django", - "project_id": 1, - "received": 1721319572.877828, - "retention_days": 90, - "segment_id": "8873a98879faf06d", - "sentry_tags": { - "description": "normalized_description", - "category": "http", - "environment": "development", - "op": "http.server", - "platform": "python", - "release": "backend@24.7.0.dev0+c45b49caed1e5fcbf70097ab3f434b487c359b6b", - "sdk.name": "sentry.python.django", - "sdk.version": "2.7.0", - "status": "ok", - "status_code": "200", - "thread.id": "8522009600", - "thread.name": "uWSGIWorker1Core0", - "trace.status": "ok", - "transaction": "/api/0/relays/projectconfigs/", - "transaction.method": "POST", - "transaction.op": "http.server", - "user": "ip:127.0.0.1" - }, - "span_id": "8873a98879faf06d", - "tags": { - "http.status_code": "200", - "relay_endpoint_version": "3", - "relay_id": "88888888-4444-4444-8444-cccccccccccc", - "relay_no_cache": "False", - "relay_protocol_version": "3", - "relay_use_post_or_schedule": "True", - "relay_use_post_or_schedule_rejected": "version", - "server_name": "D23CXQ4GK2.local", - "spans_over_limit": "False" - }, - "trace_id": "d099bf9ad5a143cf8f83a98081d0ed3b", - "start_timestamp_ms": 1721319572616, - "start_timestamp": 1721319572.616648, - "timestamp": 1721319572.768806 - }"#; - let mut span: SpanKafkaMessage = serde_json::from_str(json).unwrap(); - span.backfill_data(); - - assert_eq!( - serde_json::to_string_pretty(&span.data).unwrap(), - r#"{ - "http.status_code": "200", - "my.array.field": [1, 2, ["nested", "array"]], - "my.dict.field": { - "id": 42, - "name": "test" - }, - "my.false.bool.field": false, - "my.float.field": 101.2, - "my.int.field": 2000, - "my.neg.field": -100, - "my.neg.float.field": -101.2, - "my.true.bool.field": true, - "my.u64.field": 9447000002305251000, - "num_of_spans": 50.0, - "relay_endpoint_version": "3", - "relay_id": "88888888-4444-4444-8444-cccccccccccc", - "relay_no_cache": "False", - "relay_protocol_version": "3", - "relay_use_post_or_schedule": "True", - "relay_use_post_or_schedule_rejected": "version", - "sentry.category": "http", - "sentry.client_sample_rate": 0.1, - "sentry.environment": "development", - "sentry.normalized_description": "normalized_description", - "sentry.op": "http.server", - "sentry.platform": "python", - "sentry.release": "backend@24.7.0.dev0+c45b49caed1e5fcbf70097ab3f434b487c359b6b", - "sentry.sdk.name": "sentry.python.django", - "sentry.sdk.version": "2.7.0", - "sentry.segment.name": "/api/0/relays/projectconfigs/", - "sentry.server_sample_rate": 0.2, - "sentry.status": "ok", - "sentry.status_code": "200", - "sentry.thread.id": "8522009600", - "sentry.thread.name": "uWSGIWorker1Core0", - "sentry.trace.status": "ok", - "sentry.transaction": "/api/0/relays/projectconfigs/", - "sentry.transaction.method": "POST", - "sentry.transaction.op": "http.server", - "sentry.user": "ip:127.0.0.1", - "server_name": "D23CXQ4GK2.local", - "spans_over_limit": "False", - "thread.id": "8522009600", - "thread.name": "uWSGIWorker1Core0" -}"# - ); - } } diff --git a/relay-spans/src/v1_to_v2.rs b/relay-spans/src/v1_to_v2.rs index 65e08c3c1e6..93c43061e6d 100644 --- a/relay-spans/src/v1_to_v2.rs +++ b/relay-spans/src/v1_to_v2.rs @@ -107,8 +107,9 @@ pub fn span_v1_to_span_v2(span_v1: SpanV1) -> SpanV2 { .get_value("sentry.name") .and_then(|v| Some(v.as_str()?.to_owned())) .into(), - status: Annotated::map_value(status, span_v1_status_to_span_v2_status), - is_remote, + status: Annotated::map_value(status, span_v1_status_to_span_v2_status) + .or_else(|| SpanV2Status::Ok.into()), + is_remote: is_remote.or_else(|| false.into()), kind, start_timestamp, end_timestamp: timestamp, diff --git a/tests/integration/test_spans.py b/tests/integration/test_spans.py index 8d297806bc5..ec57e42b48c 100644 --- a/tests/integration/test_spans.py +++ b/tests/integration/test_spans.py @@ -33,7 +33,6 @@ @pytest.mark.parametrize("performance_issues_spans", [False, True]) @pytest.mark.parametrize("discard_transaction", [False, True]) -@pytest.mark.parametrize("produce_compat_spans", [False, True]) def test_span_extraction( mini_sentry, relay_with_processing, @@ -41,14 +40,9 @@ def test_span_extraction( transactions_consumer, events_consumer, metrics_consumer, - produce_compat_spans, discard_transaction, performance_issues_spans, ): - mini_sentry.global_config["options"] = { - "relay.kafka.span-v2.sample-rate": float(produce_compat_spans) - } - spans_consumer = spans_consumer() transactions_consumer = transactions_consumer() events_consumer = events_consumer() @@ -191,10 +185,7 @@ def test_span_extraction( "end_timestamp_precise": start.timestamp() + duration.total_seconds(), "trace_id": "ff62a8b040f340bda5d830223def1d81", } - if produce_compat_spans: - assert_contains(child_span, expected_child_span) - else: - assert child_span == expected_child_span + assert_contains(child_span, expected_child_span) start_timestamp = datetime.fromisoformat(event["start_timestamp"]).replace( tzinfo=timezone.utc @@ -265,10 +256,7 @@ def test_span_extraction( "trace_id": "a0fa8803753e40fd8124b21eeb2986b5", } - if produce_compat_spans: - assert_contains(transaction_span, expected_transaction_span) - else: - assert transaction_span == expected_transaction_span + assert_contains(transaction_span, expected_transaction_span) spans_consumer.assert_empty() @@ -687,17 +675,12 @@ def make_otel_span(start, end): } -@pytest.mark.parametrize("produce_compat_spans", [False, True]) def test_span_ingestion( mini_sentry, relay_with_processing, spans_consumer, metrics_consumer, - produce_compat_spans, ): - mini_sentry.global_config["options"] = { - "relay.kafka.span-v2.sample-rate": float(produce_compat_spans) - } spans_consumer = spans_consumer() metrics_consumer = metrics_consumer() @@ -1163,11 +1146,8 @@ def test_span_ingestion( }, ] - if produce_compat_spans: - for span, expected_span in zip(spans, expected_spans): - assert_contains(span, expected_span) - else: - assert spans == expected_spans + for span, expected_span in zip(spans, expected_spans): + assert_contains(span, expected_span) spans_consumer.assert_empty() @@ -1530,56 +1510,7 @@ def test_span_reject_invalid_timestamps( spans = spans_consumer.get_spans(timeout=10.0, n=1) assert len(spans) == 1 - assert spans[0]["sentry_tags"]["op"] == "default" - - -def test_span_filter_empty_measurements( - mini_sentry, - relay_with_processing, - spans_consumer, -): - spans_consumer = spans_consumer() - - relay = relay_with_processing() - project_id = 42 - project_config = mini_sentry.add_full_project_config(project_id) - project_config["config"]["features"] = [ - "organizations:standalone-span-ingestion", - ] - - start = datetime.now(UTC) - end = start + timedelta(seconds=1) - - envelope = Envelope() - envelope.add_item( - Item( - type="span", - payload=PayloadRef( - bytes=json.dumps( - { - "description": "https://example.com/p/blah.js", - "op": "resource.script", - "span_id": "b0429c44b67a3eb1", - "segment_id": "b0429c44b67a3eb1", - "start_timestamp": start.timestamp(), - "timestamp": end.timestamp() + 1, - "exclusive_time": 345.0, - "trace_id": "ff62a8b040f340bda5d830223def1d81", - "measurements": { - "score.total": {"unit": "ratio", "value": 0.12121616}, - "missing": {"unit": "ratio", "value": None}, - "other_missing": {"unit": "ratio"}, - }, - }, - ).encode() - ), - ) - ) - relay.send_envelope(project_id, envelope) - - spans = spans_consumer.get_spans(timeout=10.0, n=1) - assert len(spans) == 1 - assert spans[0]["measurements"] == {"score.total": {"value": 0.12121616}} + assert spans[0]["name"] == "span with valid timestamps" def test_span_ingestion_with_performance_scores( @@ -1699,155 +1630,45 @@ def test_span_ingestion_with_performance_scores( # endpoint might overtake envelope spans.sort(key=lambda msg: msg["span_id"]) - assert spans == [ + expected_scores = [ { - "data": { - "browser.name": "Python Requests", - "client.address": "127.0.0.1", - "user_agent.original": "python-requests/2.32.4", - # Backfilled from `sentry_tags`: - "sentry.browser.name": "Python Requests", - "sentry.name": "ui.interaction.click", - "sentry.op": "ui.interaction.click", - # Backfilled from `measurements`: - "score.fcp": 0.14999972769539766, - "score.fid": 0.14999999985, - "score.lcp": 0.29986141375718806, - "score.ratio.cls": 0.0, - "score.ratio.fcp": 0.9999981846359844, - "score.ratio.fid": 0.4999999995, - "score.ratio.lcp": 0.9995380458572936, - "score.ratio.ttfb": 0.0, - "score.total": 0.5998611413025857, - "score.ttfb": 0.0, - "score.weight.cls": 0.25, - "score.weight.fcp": 0.15, - "score.weight.fid": 0.3, - "score.weight.lcp": 0.3, - "score.weight.ttfb": 0.0, - "cls": 100.0, - "fcp": 200.0, - "fid": 300.0, - "lcp": 400.0, - "ttfb": 500.0, - "score.cls": 0.0, - }, - "downsampled_retention_days": 90, - "duration_ms": 1500, - "exclusive_time_ms": 345.0, - "is_segment": False, - "is_remote": False, - "organization_id": 1, - "project_id": 42, - "key_id": 123, - "retention_days": 90, - "sentry_tags": { - "browser.name": "Python Requests", - "name": "ui.interaction.click", - "op": "ui.interaction.click", - }, - "span_id": "bd429c44b67a3eb1", - "start_timestamp_ms": int(start.timestamp() * 1e3), - "start_timestamp_precise": start.timestamp(), - "end_timestamp_precise": end.timestamp() + 1, - "trace_id": "ff62a8b040f340bda5d830223def1d81", - "measurements": { - "score.fcp": {"value": 0.14999972769539766}, - "score.fid": {"value": 0.14999999985}, - "score.lcp": {"value": 0.29986141375718806}, - "score.ratio.cls": {"value": 0.0}, - "score.ratio.fcp": {"value": 0.9999981846359844}, - "score.ratio.fid": {"value": 0.4999999995}, - "score.ratio.lcp": {"value": 0.9995380458572936}, - "score.ratio.ttfb": {"value": 0.0}, - "score.total": {"value": 0.5998611413025857}, - "score.ttfb": {"value": 0.0}, - "score.weight.cls": {"value": 0.25}, - "score.weight.fcp": {"value": 0.15}, - "score.weight.fid": {"value": 0.3}, - "score.weight.lcp": {"value": 0.3}, - "score.weight.ttfb": {"value": 0.0}, - "cls": {"value": 100.0}, - "fcp": {"value": 200.0}, - "fid": {"value": 300.0}, - "lcp": {"value": 400.0}, - "ttfb": {"value": 500.0}, - "score.cls": {"value": 0.0}, - }, + "score.fcp": 0.14999972769539766, + "score.fid": 0.14999999985, + "score.lcp": 0.29986141375718806, + "score.ratio.cls": 0.0, + "score.ratio.fcp": 0.9999981846359844, + "score.ratio.fid": 0.4999999995, + "score.ratio.lcp": 0.9995380458572936, + "score.ratio.ttfb": 0.0, + "score.total": 0.5998611413025857, + "score.ttfb": 0.0, + "score.weight.cls": 0.25, + "score.weight.fcp": 0.15, + "score.weight.fid": 0.3, + "score.weight.lcp": 0.3, + "score.weight.ttfb": 0.0, + "cls": 100.0, + "fcp": 200.0, + "fid": 300.0, + "lcp": 400.0, + "ttfb": 500.0, + "score.cls": 0.0, }, { - "_meta": { - "data": { - "sentry.segment.name": { - "": { - "rem": [ - [ - "int", - "s", - 34, - 37, - ], - ["**/interaction/*/**", "s"], - ], - "val": "/page/with/click/interaction/jane/123", - } - } - } - }, - "data": { - "browser.name": "Python Requests", - "client.address": "127.0.0.1", - "sentry.replay.id": "8477286c8e5148b386b71ade38374d58", - "sentry.segment.name": "/page/with/click/interaction/*/*", - "user": "[email]", - "user_agent.original": "python-requests/2.32.4", - # Backfilled from `sentry_tags`: - "sentry.browser.name": "Python Requests", - "sentry.name": "ui.interaction.click", - "sentry.op": "ui.interaction.click", - "sentry.transaction": "/page/with/click/interaction/*/*", - "sentry.replay_id": "8477286c8e5148b386b71ade38374d58", - "sentry.user": "[email]", - # Backfilled from `measurements`: - "inp": 100.0, - "score.inp": 0.9948129113413748, - "score.ratio.inp": 0.9948129113413748, - "score.total": 0.9948129113413748, - "score.weight.inp": 1.0, - }, - "downsampled_retention_days": 90, - "duration_ms": 1500, - "exclusive_time_ms": 345.0, - "is_segment": False, - "is_remote": False, - "profile_id": "3d9428087fda4ba0936788b70a7587d0", - "organization_id": 1, - "project_id": 42, - "key_id": 123, - "retention_days": 90, - "sentry_tags": { - "browser.name": "Python Requests", - "name": "ui.interaction.click", - "op": "ui.interaction.click", - "transaction": "/page/with/click/interaction/*/*", - "replay_id": "8477286c8e5148b386b71ade38374d58", - "user": "[email]", - }, - "span_id": "cd429c44b67a3eb1", - "start_timestamp_ms": int(start.timestamp() * 1e3), - "start_timestamp_precise": start.timestamp(), - "end_timestamp_precise": end.timestamp() + 1, - "trace_id": "ff62a8b040f340bda5d830223def1d81", - "measurements": { - "inp": {"value": 100.0}, - "score.inp": {"value": 0.9948129113413748}, - "score.ratio.inp": {"value": 0.9948129113413748}, - "score.total": {"value": 0.9948129113413748}, - "score.weight.inp": {"value": 1.0}, - }, + "inp": 100.0, + "score.inp": 0.9948129113413748, + "score.ratio.inp": 0.9948129113413748, + "score.total": 0.9948129113413748, + "score.weight.inp": 1.0, }, ] + assert len(spans) == len(expected_scores) + for span, scores in zip(spans, expected_scores): + for key, score in scores.items(): + assert span["data"][key] == score + assert span["attributes"][key]["value"] == score + def test_rate_limit_indexed_consistent( mini_sentry, relay_with_processing, spans_consumer, outcomes_consumer @@ -2165,63 +1986,6 @@ def summarize_outcomes(): assert usage_metrics() == (1, 2) -@pytest.mark.parametrize( - "tags, expected_tags", - [ - ( - { - "some": "tag", - "other": "value", - }, - { - "some": "tag", - "other": "value", - }, - ), - ( - { - "some": 1, - "other": True, - }, - { - "some": "1", - "other": "True", - }, - ), - ], -) -def test_span_extraction_with_tags( - mini_sentry, - relay_with_processing, - spans_consumer, - tags, - expected_tags, -): - spans_consumer = spans_consumer() - - relay = relay_with_processing() - project_id = 42 - project_config = mini_sentry.add_full_project_config(project_id) - project_config["config"]["features"] = [ - "organizations:indexed-spans-extraction", - ] - - event = make_transaction( - { - "event_id": "e022a2da91e9495d944c291fe065972d", - "tags": tags, - } - ) - - relay.send_event(project_id, event) - - transaction_span = spans_consumer.get_span() - - assert transaction_span["tags"] == expected_tags - - spans_consumer.assert_empty() - - def test_span_filtering_with_generic_inbound_filter( mini_sentry, relay_with_processing, spans_consumer, outcomes_consumer ): @@ -2442,161 +2206,45 @@ def test_scrubs_ip_addresses( child_span = spans_consumer.get_span() - del child_span["received"] + assert ( + child_span["_meta"]["attributes"]["sentry.user.email"] + == child_span["_meta"]["data"]["sentry.user.email"] + == {"": {"len": 15, "rem": [["@email", "s", 0, 7]]}} + ) - expected = { - "_meta": { - "sentry_tags": { - "user.email": {"": {"len": 15, "rem": [["@email", "s", 0, 7]]}}, - "user.ip": { - "": { - "len": 9, - "rem": [["@ip:replace", "s", 0, 4], ["@anything:remove", "x"]], - } - }, - } - }, - "data": { - # Backfilled from `sentry_tags` - "sentry.category": "http", - "sentry.normalized_description": "GET *", - "sentry.group": "37e3d9fab1ae9162", - "sentry.name": "http", - "sentry.op": "http", - "sentry.platform": "other", - "sentry.sdk.name": "raven-node", - "sentry.sdk.version": "2.6.3", - "sentry.status": "ok", - "sentry.trace.status": "unknown", - "sentry.transaction": "hi", - "sentry.transaction.op": "hi", - "sentry.user": "id:unique_id", - "sentry.user.email": "[email]", - "sentry.user.id": "unique_id", - "sentry.user.ip": "127.0.0.1", - "sentry.user.username": "my_user", - # Backfilled from `tags` - "extra_info": "added by user", - }, - "description": "GET /api/0/organizations/?member=1", - "downsampled_retention_days": 90, - "duration_ms": int(duration.total_seconds() * 1e3), - "event_id": "cbf6960622e14a45abc1f03b2055b186", - "exclusive_time_ms": 500.0, - "is_segment": False, - "is_remote": False, - "organization_id": 1, - "origin": "manual", - "parent_span_id": "968cff94913ebb07", - "project_id": 42, - "key_id": 123, - "retention_days": 90, - "segment_id": "968cff94913ebb07", - "sentry_tags": { - "category": "http", - "description": "GET *", - "group": "37e3d9fab1ae9162", - "name": "http", - "op": "http", - "platform": "other", - "sdk.name": "raven-node", - "sdk.version": "2.6.3", - "status": "ok", - "trace.status": "unknown", - "transaction": "hi", - "transaction.op": "hi", - "user": "id:unique_id", - "user.email": "[email]", - "user.id": "unique_id", - "user.ip": "127.0.0.1", - "user.username": "my_user", - }, - "tags": { - "extra_info": "added by user", - }, - "span_id": "bbbbbbbbbbbbbbbb", - "start_timestamp_ms": int(start.timestamp() * 1e3), - "start_timestamp_precise": start.timestamp(), - "end_timestamp_precise": start.timestamp() + duration.total_seconds(), - "trace_id": "ff62a8b040f340bda5d830223def1d81", - } if scrub_ip_addresses: - del expected["sentry_tags"]["user.ip"] - del expected["data"]["sentry.user.ip"] + assert child_span["attributes"]["sentry.user.ip"] is None + assert child_span["data"]["sentry.user.ip"] is None + assert ( + child_span["_meta"]["attributes"]["sentry.user.ip"] + == child_span["_meta"]["data"]["sentry.user.ip"] + == { + "": { + "len": 9, + "rem": [["@ip:replace", "s", 0, 4], ["@anything:remove", "x"]], + } + } + ) else: - del expected["_meta"]["sentry_tags"]["user.ip"] - assert child_span == expected - - start_timestamp = datetime.fromisoformat(event["start_timestamp"]).replace( - tzinfo=timezone.utc - ) - end_timestamp = datetime.fromisoformat(event["timestamp"]).replace( - tzinfo=timezone.utc - ) - duration = (end_timestamp - start_timestamp).total_seconds() - duration_ms = int(duration * 1e3) - - child_span = spans_consumer.get_span() + assert ( + child_span["attributes"]["sentry.user.ip"]["value"] + == child_span["data"]["sentry.user.ip"] + == "127.0.0.1" + ) + assert "sentry.user.ip" not in child_span["_meta"]["attributes"] + assert "sentry.user.ip" not in child_span["_meta"]["data"] - del child_span["received"] + parent_span = spans_consumer.get_span() - expected = { - "data": { - "sentry.sdk.name": "raven-node", - "sentry.sdk.version": "2.6.3", - "sentry.segment.name": "hi", - # Backfilled from `sentry_tags`: - "sentry.name": "hi", - "sentry.op": "hi", - "sentry.platform": "other", - "sentry.status": "unknown", - "sentry.trace.status": "unknown", - "sentry.transaction": "hi", - "sentry.transaction.op": "hi", - "sentry.user": "id:unique_id", - "sentry.user.email": "[email]", - "sentry.user.id": "unique_id", - "sentry.user.ip": "127.0.0.1", - "sentry.user.username": "my_user", - }, - "description": "hi", - "downsampled_retention_days": 90, - "duration_ms": duration_ms, - "event_id": "cbf6960622e14a45abc1f03b2055b186", - "exclusive_time_ms": 1500.0, - "is_segment": True, - "is_remote": True, - "organization_id": 1, - "project_id": 42, - "key_id": 123, - "retention_days": 90, - "segment_id": "968cff94913ebb07", - "sentry_tags": { - "name": "hi", - "op": "hi", - "platform": "other", - "sdk.name": "raven-node", - "sdk.version": "2.6.3", - "status": "unknown", - "trace.status": "unknown", - "transaction": "hi", - "transaction.op": "hi", - "user": "id:unique_id", - "user.email": "[email]", - "user.id": "unique_id", - "user.ip": "127.0.0.1", - "user.username": "my_user", - }, - "span_id": "968cff94913ebb07", - "start_timestamp_ms": int(start_timestamp.timestamp() * 1e3), - "start_timestamp_precise": start_timestamp.timestamp(), - "end_timestamp_precise": start_timestamp.timestamp() + duration, - "trace_id": "a0fa8803753e40fd8124b21eeb2986b5", - } if scrub_ip_addresses: - del expected["sentry_tags"]["user.ip"] - del expected["data"]["sentry.user.ip"] - assert child_span == expected + assert "sentry.user.ip" not in parent_span["data"] + assert "sentry.user.ip" not in parent_span["attributes"] + else: + assert ( + parent_span["attributes"]["sentry.user.ip"]["value"] + == parent_span["data"]["sentry.user.ip"] + == "127.0.0.1" + ) spans_consumer.assert_empty() diff --git a/tests/integration/test_spansv2.py b/tests/integration/test_spansv2.py index f103ceae0b3..a06fd8be93b 100644 --- a/tests/integration/test_spansv2.py +++ b/tests/integration/test_spansv2.py @@ -63,6 +63,7 @@ def test_spansv2_basic( "span_id": "eee19b7ec3c1b175", "is_remote": False, "name": "some op", + "status": "ok", "attributes": {"foo": {"value": "bar", "type": "string"}}, } ) @@ -74,20 +75,29 @@ def test_spansv2_basic( "span_id": "eee19b7ec3c1b175", "data": { "foo": "bar", - "sentry.name": "some op", "sentry.browser.name": "Python Requests", "sentry.browser.version": "2.32", "sentry.observed_timestamp_nanos": time_within(ts, expect_resolution="ns"), }, - "description": "some op", + "attributes": { + "foo": {"type": "string", "value": "bar"}, + "sentry.browser.name": {"type": "string", "value": "Python Requests"}, + "sentry.browser.version": {"type": "string", "value": "2.32"}, + "sentry.observed_timestamp_nanos": { + "type": "string", + "value": time_within(ts, expect_resolution="ns"), + }, + }, + "name": "some op", "received": time_within(ts), "start_timestamp_ms": time_within(ts, precision="ms", expect_resolution="ms"), + "start_timestamp": time_within(ts), "start_timestamp_precise": time_within(ts), + "end_timestamp": time_within(ts.timestamp() + 0.5), "end_timestamp_precise": time_within(ts.timestamp() + 0.5), "duration_ms": 500, - "exclusive_time_ms": 500.0, "is_remote": False, - "is_segment": False, + "status": "ok", "retention_days": 90, "downsampled_retention_days": 90, "key_id": 123, @@ -240,6 +250,7 @@ def test_spansv2_ds_sampled( "is_remote": False, "name": "some op", "attributes": {"foo": {"value": "bar", "type": "string"}}, + "status": "ok", }, trace_info={ "trace_id": "5b8efff798038103d269b633813fc60c", @@ -249,34 +260,9 @@ def test_spansv2_ds_sampled( relay.send_envelope(project_id, envelope) - assert spans_consumer.get_span() == { - "trace_id": "5b8efff798038103d269b633813fc60c", - "span_id": "eee19b7ec3c1b175", - "description": "some op", - "data": { - "foo": "bar", - "sentry.name": "some op", - "sentry.server_sample_rate": 0.9, - "sentry.browser.name": "Python Requests", - "sentry.browser.version": "2.32", - "sentry.observed_timestamp_nanos": time_within(ts, expect_resolution="ns"), - }, - "measurements": {"server_sample_rate": {"value": 0.9}}, - "server_sample_rate": 0.9, - "received": time_within_delta(ts), - "start_timestamp_ms": time_within(ts, precision="ms", expect_resolution="ms"), - "start_timestamp_precise": time_within(ts), - "end_timestamp_precise": time_within(ts.timestamp() + 0.5), - "duration_ms": 500, - "exclusive_time_ms": 500.0, - "is_remote": False, - "is_segment": False, - "retention_days": 90, - "downsampled_retention_days": 90, - "key_id": 123, - "organization_id": 1, - "project_id": 42, - } + span = spans_consumer.get_span() + assert span["span_id"] == "eee19b7ec3c1b175" + assert span["attributes"]["sentry.server_sample_rate"]["value"] == 0.9 assert metrics_consumer.get_metrics(n=2, with_headers=False) == [ {