From fa594f0af52f66db8b015ba70a92bc657508d845 Mon Sep 17 00:00:00 2001 From: David Herberth Date: Fri, 10 Apr 2026 11:24:49 +0200 Subject: [PATCH] fix(metrics): Re-introduce transactions namespace --- relay-base-schema/src/metrics/mri.rs | 14 ++++++++++++-- relay-cogs/src/lib.rs | 3 +++ relay-dynamic-config/src/global.rs | 6 ++++++ relay-metrics/src/cogs.rs | 1 + relay-metrics/src/utils.rs | 10 +++++++++- relay-server/src/services/processor/metrics.rs | 2 ++ relay-server/src/services/store.rs | 1 + tests/integration/test_metrics.py | 10 +++++----- 8 files changed, 39 insertions(+), 8 deletions(-) diff --git a/relay-base-schema/src/metrics/mri.rs b/relay-base-schema/src/metrics/mri.rs index ae08462d85..dac5981dbc 100644 --- a/relay-base-schema/src/metrics/mri.rs +++ b/relay-base-schema/src/metrics/mri.rs @@ -110,6 +110,8 @@ pub enum MetricNamespace { Sessions, /// Metrics extracted from spans. Spans, + /// Metrics extracted from transactions. + Transactions, /// User-defined metrics directly sent by SDKs and applications. Custom, /// An unknown and unsupported metric. @@ -126,8 +128,14 @@ pub enum MetricNamespace { impl MetricNamespace { /// Returns all namespaces/variants of this enum. - pub fn all() -> [Self; 4] { - [Self::Sessions, Self::Spans, Self::Custom, Self::Unsupported] + pub fn all() -> [Self; 5] { + [ + Self::Sessions, + Self::Spans, + Self::Transactions, + Self::Custom, + Self::Unsupported, + ] } /// Returns the string representation for this metric type. @@ -135,6 +143,7 @@ impl MetricNamespace { match self { Self::Sessions => "sessions", Self::Spans => "spans", + Self::Transactions => "transactions", Self::Custom => "custom", Self::Unsupported => "unsupported", } @@ -148,6 +157,7 @@ impl std::str::FromStr for MetricNamespace { match ns { "sessions" => Ok(Self::Sessions), "spans" => Ok(Self::Spans), + "transactions" => Ok(Self::Transactions), "custom" => Ok(Self::Custom), _ => Ok(Self::Unsupported), } diff --git a/relay-cogs/src/lib.rs b/relay-cogs/src/lib.rs index dbd0b85640..8e04c7cf02 100644 --- a/relay-cogs/src/lib.rs +++ b/relay-cogs/src/lib.rs @@ -176,6 +176,8 @@ pub enum AppFeature { /// Metrics in the spans namespace. MetricsSpans, + /// Metrics in the transactions namespace. + MetricsTransactions, /// Metrics in the sessions namespace. MetricsSessions, /// Metrics in the custom namespace. @@ -207,6 +209,7 @@ impl AppFeature { Self::Replays => "replays", Self::UserReports => "user_reports", Self::MetricsSpans => "metrics_spans", + Self::MetricsTransactions => "metrics_transactions", Self::MetricsSessions => "metrics_sessions", Self::MetricsCustom => "metrics_custom", Self::MetricsUnsupported => "metrics_unsupported", diff --git a/relay-dynamic-config/src/global.rs b/relay-dynamic-config/src/global.rs index e1c967b95e..275d6c5b91 100644 --- a/relay-dynamic-config/src/global.rs +++ b/relay-dynamic-config/src/global.rs @@ -207,6 +207,7 @@ pub enum CardinalityLimiterMode { #[serde(default)] pub struct BucketEncodings { spans: BucketEncoding, + transactions: BucketEncoding, profiles: BucketEncoding, custom: BucketEncoding, } @@ -216,6 +217,7 @@ impl BucketEncodings { pub fn for_namespace(&self, namespace: MetricNamespace) -> BucketEncoding { match namespace { MetricNamespace::Spans => self.spans, + MetricNamespace::Transactions => self.transactions, MetricNamespace::Custom => self.custom, // Always force the legacy encoding for sessions, // sessions are not part of the generic metrics platform with different @@ -249,6 +251,7 @@ where let encoding = BucketEncoding::deserialize(de::value::StrDeserializer::new(v))?; Ok(BucketEncodings { spans: encoding, + transactions: encoding, profiles: encoding, custom: encoding, }) @@ -451,6 +454,7 @@ mod tests { o.metric_bucket_set_encodings, BucketEncodings { spans: BucketEncoding::Legacy, + transactions: BucketEncoding::Legacy, profiles: BucketEncoding::Legacy, custom: BucketEncoding::Legacy, } @@ -459,6 +463,7 @@ mod tests { o.metric_bucket_dist_encodings, BucketEncodings { spans: BucketEncoding::Zstd, + transactions: BucketEncoding::Zstd, profiles: BucketEncoding::Zstd, custom: BucketEncoding::Zstd, } @@ -469,6 +474,7 @@ mod tests { fn test_metric_bucket_encodings_de_from_obj() { let original = BucketEncodings { spans: BucketEncoding::Zstd, + transactions: BucketEncoding::Zstd, profiles: BucketEncoding::Base64, custom: BucketEncoding::Zstd, }; diff --git a/relay-metrics/src/cogs.rs b/relay-metrics/src/cogs.rs index c024c51bb1..d272c8d268 100644 --- a/relay-metrics/src/cogs.rs +++ b/relay-metrics/src/cogs.rs @@ -39,6 +39,7 @@ fn to_app_feature(ns: MetricNamespace) -> AppFeature { match ns { MetricNamespace::Sessions => AppFeature::MetricsSessions, MetricNamespace::Spans => AppFeature::MetricsSpans, + MetricNamespace::Transactions => AppFeature::MetricsTransactions, MetricNamespace::Custom => AppFeature::MetricsCustom, MetricNamespace::Unsupported => AppFeature::MetricsUnsupported, } diff --git a/relay-metrics/src/utils.rs b/relay-metrics/src/utils.rs index 37a9b3faff..0e86a40c4e 100644 --- a/relay-metrics/src/utils.rs +++ b/relay-metrics/src/utils.rs @@ -19,6 +19,8 @@ pub struct ByNamespace { pub sessions: T, /// Value for the [`MetricNamespace::Spans`] namespace. pub spans: T, + /// Value for the [`MetricNamespace::Transactions`] namespace. + pub transactions: T, /// Value for the [`MetricNamespace::Custom`] namespace. pub custom: T, /// Value for the [`MetricNamespace::Unsupported`] namespace. @@ -31,6 +33,7 @@ impl ByNamespace { match namespace { MetricNamespace::Sessions => &self.sessions, MetricNamespace::Spans => &self.spans, + MetricNamespace::Transactions => &self.transactions, MetricNamespace::Custom => &self.custom, MetricNamespace::Unsupported => &self.unsupported, } @@ -41,6 +44,7 @@ impl ByNamespace { match namespace { MetricNamespace::Sessions => &mut self.sessions, MetricNamespace::Spans => &mut self.spans, + MetricNamespace::Transactions => &mut self.transactions, MetricNamespace::Custom => &mut self.custom, MetricNamespace::Unsupported => &mut self.unsupported, } @@ -49,12 +53,13 @@ impl ByNamespace { impl IntoIterator for ByNamespace { type Item = (MetricNamespace, T); - type IntoIter = std::array::IntoIter<(MetricNamespace, T), 4>; + type IntoIter = std::array::IntoIter<(MetricNamespace, T), 5>; fn into_iter(self) -> Self::IntoIter { let Self { sessions, spans, + transactions, custom, unsupported, } = self; @@ -62,6 +67,7 @@ impl IntoIterator for ByNamespace { [ (MetricNamespace::Sessions, sessions), (MetricNamespace::Spans, spans), + (MetricNamespace::Transactions, transactions), (MetricNamespace::Custom, custom), (MetricNamespace::Unsupported, unsupported), ] @@ -104,12 +110,14 @@ macro_rules! impl_op { let Self { sessions, spans, + transactions, custom, unsupported, } = self; $op::$opfn(sessions, rhs.sessions); $op::$opfn(spans, rhs.spans); + $op::$opfn(transactions, rhs.transactions); $op::$opfn(custom, rhs.custom); $op::$opfn(unsupported, rhs.unsupported); } diff --git a/relay-server/src/services/processor/metrics.rs b/relay-server/src/services/processor/metrics.rs index 1fc03eb08c..35c06cd0ec 100644 --- a/relay-server/src/services/processor/metrics.rs +++ b/relay-server/src/services/processor/metrics.rs @@ -15,6 +15,7 @@ pub fn is_valid_namespace(bucket: &Bucket) -> bool { match bucket.name.namespace() { MetricNamespace::Sessions => true, MetricNamespace::Spans => true, + MetricNamespace::Transactions => true, MetricNamespace::Custom => true, MetricNamespace::Unsupported => false, } @@ -56,6 +57,7 @@ fn is_metric_namespace_valid(state: &ProjectInfo, namespace: MetricNamespace) -> match namespace { MetricNamespace::Sessions => true, MetricNamespace::Spans => true, + MetricNamespace::Transactions => true, MetricNamespace::Custom => state.has_feature(Feature::CustomMetrics), MetricNamespace::Unsupported => false, } diff --git a/relay-server/src/services/store.rs b/relay-server/src/services/store.rs index dc19e08a96..0158655c95 100644 --- a/relay-server/src/services/store.rs +++ b/relay-server/src/services/store.rs @@ -1738,6 +1738,7 @@ impl Message for KafkaMessage<'_> { KafkaMessage::Metric { message, .. } => match message.name.namespace() { MetricNamespace::Sessions => "metric_sessions", MetricNamespace::Spans => "metric_spans", + MetricNamespace::Transactions => "metric_transactions", MetricNamespace::Custom => "metric_custom", MetricNamespace::Unsupported => "metric_unsupported", }, diff --git a/tests/integration/test_metrics.py b/tests/integration/test_metrics.py index af9b43e97d..815c68c8e8 100644 --- a/tests/integration/test_metrics.py +++ b/tests/integration/test_metrics.py @@ -169,7 +169,7 @@ def test_metrics(mini_sentry, relay): mini_sentry.add_basic_project_config(project_id) timestamp = int(datetime.now(tz=timezone.utc).timestamp()) - metrics_payload = f"spans/foo:42|c|T{timestamp}\nspans/bar:17|c|T{timestamp}" + metrics_payload = f"spans/foo:42|c|T{timestamp}\ntransactions/bar:17|c|T{timestamp}" relay.send_metrics(project_id, metrics_payload) envelope = mini_sentry.get_captured_envelope() @@ -185,15 +185,15 @@ def test_metrics(mini_sentry, relay): { "timestamp": time_after(timestamp), "width": 1, - "name": "c:spans/bar@none", - "value": 17.0, + "name": "c:spans/foo@none", + "value": 42.0, "type": "c", }, { "timestamp": time_after(timestamp), "width": 1, - "name": "c:spans/foo@none", - "value": 42.0, + "name": "c:transactions/bar@none", + "value": 17.0, "type": "c", }, ]