From 62dce926c0475a074cc38e125c45f8e548765bb3 Mon Sep 17 00:00:00 2001 From: Joris Bayer Date: Mon, 19 Sep 2022 14:07:46 +0200 Subject: [PATCH 01/10] wip --- relay-general/src/store/normalize.rs | 51 +++++++++++++++++++++++++--- relay-server/src/actors/processor.rs | 1 + relay-server/src/actors/project.rs | 7 +++- 3 files changed, 54 insertions(+), 5 deletions(-) diff --git a/relay-general/src/store/normalize.rs b/relay-general/src/store/normalize.rs index 67902698dd..125b57f95a 100644 --- a/relay-general/src/store/normalize.rs +++ b/relay-general/src/store/normalize.rs @@ -1,4 +1,5 @@ use std::collections::hash_map::DefaultHasher; +use std::collections::BTreeSet; use std::hash::{Hash, Hasher}; use std::mem; use std::sync::Arc; @@ -34,6 +35,26 @@ mod stacktrace; mod user_agent; +/// Uniquely identifies a measurement by its name and unit. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MeasurementKey<'a> { + name: Cow<'a>, + #[serde(deserialize_with = "MetricUnit::from_str")] + unit: MetricUnit, +} + +/// Configuration for measurements normalization. +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +#[serde(default, rename_all = "camelCase")] +pub struct MeasurementsConfig<'a> { + /// A list of measurements that are built-in and are not subject to custom measurement limits. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + known_measurements: BTreeSet>, + + /// The maximum number of measurements allowed per event that are not known measurements. + max_custom_measurements: usize, +} + /// Validate fields that go into a `sentry.models.BoundedIntegerField`. fn validate_bounded_integer_field(value: u64) -> ProcessingResult { if value < 2_147_483_647 { @@ -221,12 +242,33 @@ fn normalize_breakdowns(event: &mut Event, breakdowns_config: Option<&Breakdowns } } -/// Ensure measurements interface is only present for transaction events -fn normalize_measurements(event: &mut Event) { +/// Enforce the limit on custom (user defined) measurements. +fn normalize_custom_measurements( + measurements: &mut Measurements, + measurements_config: &MeasurementsConfig, +) { + measurements.retain(|name, measurement| { + let unit = match measurement.value().and_then(|m| m.unit.value()) { + Some(unit) => unit, + None => return false, + }; + let key = MeasurementKey { + name, + unit: unit.clone(), + }; + + measurements_config.known_measurements.contains(key) + }); +} + +/// Ensure measurements interface is only present for transaction events. +fn normalize_measurements(event: &mut Event, measurements_config: &MeasurementsConfig) { if event.ty.value() != Some(&EventType::Transaction) { // Only transaction events may have a measurements interface event.measurements = Annotated::empty(); } else if let Some(measurements) = event.measurements.value_mut() { + normalize_custom_measurements(measurements, measurements_config); + let duration_millis = match (event.start_timestamp.0, event.timestamp.0) { (Some(start), Some(end)) => relay_common::chrono_to_positive_millis(end - start), _ => 0.0, @@ -540,6 +582,7 @@ pub struct LightNormalizationConfig<'a> { pub received_at: Option>, pub max_secs_in_past: Option, pub max_secs_in_future: Option, + pub measurements_config: Option<&'a MeasurementsConfig>, pub breakdowns_config: Option<&'a BreakdownsConfig>, pub normalize_user_agent: Option, } @@ -591,7 +634,7 @@ pub fn light_normalize_event( light_normalize_stacktraces(event)?; normalize_exceptions(event)?; // Browser extension filters look at the stacktrace normalize_user_agent(event, config.normalize_user_agent); // Legacy browsers filter - normalize_measurements(event); // Measurements are part of the metric extraction + normalize_measurements(event, config.measurements_config); // Measurements are part of the metric extraction normalize_breakdowns(event, config.breakdowns_config); // Breakdowns are part of the metric extraction too Ok(()) @@ -1870,7 +1913,7 @@ mod tests { let mut event = Annotated::::from_json(json).unwrap().0.unwrap(); - normalize_measurements(&mut event); + normalize_measurements(&mut event, Default::default()); insta::assert_ron_snapshot!(SerializableAnnotated(&Annotated::new(event)), {}, @r###" { diff --git a/relay-server/src/actors/processor.rs b/relay-server/src/actors/processor.rs index 3191cf6b0f..f04cda6a91 100644 --- a/relay-server/src/actors/processor.rs +++ b/relay-server/src/actors/processor.rs @@ -1940,6 +1940,7 @@ impl EnvelopeProcessorService { received_at: Some(state.envelope_context.received_at()), max_secs_in_past: Some(self.config.max_secs_in_past()), max_secs_in_future: Some(self.config.max_secs_in_future()), + measurements_config: state.project_state.config.measurements.as_ref(), breakdowns_config: state.project_state.config.breakdowns_v2.as_ref(), normalize_user_agent: Some(true), }; diff --git a/relay-server/src/actors/project.rs b/relay-server/src/actors/project.rs index 8844d4cc63..0f0d7703b4 100644 --- a/relay-server/src/actors/project.rs +++ b/relay-server/src/actors/project.rs @@ -1,4 +1,6 @@ +use std::borrow::Cow; use std::collections::{BTreeSet, VecDeque}; +use std::str::FromStr; use std::sync::Arc; use std::time::{Duration, Instant}; @@ -11,7 +13,7 @@ use smallvec::SmallVec; use url::Url; use relay_auth::PublicKey; -use relay_common::{ProjectId, ProjectKey}; +use relay_common::{MetricUnit, ProjectId, ProjectKey}; use relay_config::Config; use relay_filter::{matches_any_origin, FiltersConfig}; use relay_general::pii::{DataScrubbingConfig, PiiConfig}; @@ -108,6 +110,9 @@ pub struct ProjectConfig { /// Configuration for sampling traces, if not present there will be no sampling. #[serde(skip_serializing_if = "Option::is_none")] pub dynamic_sampling: Option, + /// Configuration for measurements. + #[serde(skip_serializing_if = "Option::is_none")] + pub measurements: Option, /// Configuration for operation breakdown. Will be emitted only if present. #[serde(skip_serializing_if = "Option::is_none")] pub breakdowns_v2: Option, From dc61d752d94321cb5b5d43c117f0d47546361d43 Mon Sep 17 00:00:00 2001 From: Joris Bayer Date: Mon, 19 Sep 2022 14:23:59 +0200 Subject: [PATCH 02/10] feat: Limit custom measurements --- relay-general/src/store/normalize.rs | 55 +++++++++++++++------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/relay-general/src/store/normalize.rs b/relay-general/src/store/normalize.rs index 125b57f95a..00d3bfeb04 100644 --- a/relay-general/src/store/normalize.rs +++ b/relay-general/src/store/normalize.rs @@ -8,9 +8,11 @@ use chrono::{DateTime, Duration, Utc}; use itertools::Itertools; use once_cell::sync::OnceCell; use regex::Regex; -use relay_common::{DurationUnit, FractionUnit, MetricUnit}; +use serde::{Deserialize, Serialize}; use smallvec::SmallVec; +use relay_common::{DurationUnit, FractionUnit, MetricUnit}; + use super::{schema, transactions, BreakdownsConfig}; use crate::processor::{MaxChars, ProcessValue, ProcessingState, Processor}; use crate::protocol::{ @@ -35,21 +37,13 @@ mod stacktrace; mod user_agent; -/// Uniquely identifies a measurement by its name and unit. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct MeasurementKey<'a> { - name: Cow<'a>, - #[serde(deserialize_with = "MetricUnit::from_str")] - unit: MetricUnit, -} - /// Configuration for measurements normalization. #[derive(Debug, Default, Clone, Serialize, Deserialize)] #[serde(default, rename_all = "camelCase")] -pub struct MeasurementsConfig<'a> { +pub struct MeasurementsConfig { /// A list of measurements that are built-in and are not subject to custom measurement limits. - #[serde(default, skip_serializing_if = "Vec::is_empty")] - known_measurements: BTreeSet>, + #[serde(default, skip_serializing_if = "BTreeSet::::is_empty")] + known_measurements: BTreeSet, /// The maximum number of measurements allowed per event that are not known measurements. max_custom_measurements: usize, @@ -243,31 +237,40 @@ fn normalize_breakdowns(event: &mut Event, breakdowns_config: Option<&Breakdowns } /// Enforce the limit on custom (user defined) measurements. -fn normalize_custom_measurements( +/// +/// Note that [`Measurements`] is a BTreeMap, which means its keys are sorted. +/// This ensures that for two events with the same measurement keys, the same set of custom +/// measurements is retained. +/// +fn filter_custom_measurements( measurements: &mut Measurements, measurements_config: &MeasurementsConfig, ) { - measurements.retain(|name, measurement| { - let unit = match measurement.value().and_then(|m| m.unit.value()) { - Some(unit) => unit, - None => return false, - }; - let key = MeasurementKey { - name, - unit: unit.clone(), - }; + let mut custom_measurements_count = 0; + measurements.retain(|name, _| { + // Check if this is a builtin measurement: + if measurements_config.known_measurements.contains(name) { + return true; + } + // For custom measurements, check the budget: + if custom_measurements_count < measurements_config.max_custom_measurements { + custom_measurements_count += 1; + return true; + } - measurements_config.known_measurements.contains(key) + false }); } /// Ensure measurements interface is only present for transaction events. -fn normalize_measurements(event: &mut Event, measurements_config: &MeasurementsConfig) { +fn normalize_measurements(event: &mut Event, measurements_config: Option<&MeasurementsConfig>) { if event.ty.value() != Some(&EventType::Transaction) { // Only transaction events may have a measurements interface event.measurements = Annotated::empty(); } else if let Some(measurements) = event.measurements.value_mut() { - normalize_custom_measurements(measurements, measurements_config); + if let Some(measurements_config) = measurements_config { + filter_custom_measurements(measurements, measurements_config); + } let duration_millis = match (event.start_timestamp.0, event.timestamp.0) { (Some(start), Some(end)) => relay_common::chrono_to_positive_millis(end - start), @@ -1913,7 +1916,7 @@ mod tests { let mut event = Annotated::::from_json(json).unwrap().0.unwrap(); - normalize_measurements(&mut event, Default::default()); + normalize_measurements(&mut event, None); insta::assert_ron_snapshot!(SerializableAnnotated(&Annotated::new(event)), {}, @r###" { From 8800cce4ac064a9b2568a665e9fca95f2e0f2a59 Mon Sep 17 00:00:00 2001 From: Joris Bayer Date: Mon, 19 Sep 2022 14:50:12 +0200 Subject: [PATCH 03/10] test: limit custom measurements --- relay-general/src/store/normalize.rs | 52 +++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/relay-general/src/store/normalize.rs b/relay-general/src/store/normalize.rs index 00d3bfeb04..ec8151c28f 100644 --- a/relay-general/src/store/normalize.rs +++ b/relay-general/src/store/normalize.rs @@ -892,6 +892,7 @@ impl<'a> Processor for NormalizeProcessor<'a> { #[cfg(test)] mod tests { use chrono::TimeZone; + use serde_json::json; use similar_asserts::assert_eq; use crate::processor::process_value; @@ -1717,7 +1718,7 @@ mod tests { let mut processor = NormalizeProcessor::new( Arc::new(StoreConfig { - grouping_config: Some(serde_json::json!({ + grouping_config: Some(json!({ "id": "legacy:1234-12-12".to_string(), })), ..Default::default() @@ -1954,6 +1955,55 @@ mod tests { "###); } + #[test] + fn test_filter_custom_measurements() { + let json = r#" + { + "type": "transaction", + "timestamp": "2021-04-26T08:00:05+0100", + "start_timestamp": "2021-04-26T08:00:00+0100", + "measurements": { + "my_custom_measurement_1": {"value": 123}, + "frames_slow": {"value": 1}, + "my_custom_measurement_3": {"value": 456}, + "my_custom_measurement_2": {"value": 789} + } + } + "#; + let mut event = Annotated::::from_json(json).unwrap().0.unwrap(); + + let config: MeasurementsConfig = serde_json::from_value(json!({ + "knownMeasurements": [ + "frames_slow" + ], + "maxCustomMeasurements": 2, + "stray_key": "zzz" + })) + .unwrap(); + + normalize_measurements(&mut event, Some(&config)); + + // Only two custom measurements are retained, in alphabetic order (1 and 2) + insta::assert_ron_snapshot!(SerializableAnnotated(&Annotated::new(event)), {}, @r###" + { + "type": "transaction", + "timestamp": 1619420405.0, + "start_timestamp": 1619420400.0, + "measurements": { + "frames_slow": { + "value": 1.0, + }, + "my_custom_measurement_1": { + "value": 123.0, + }, + "my_custom_measurement_2": { + "value": 789.0, + }, + }, + } + "###); + } + #[test] fn test_light_normalization_is_idempotent() { // get an event, light normalize it. the result of that must be the same as light normalizing it once more From bef558d2a39dbae9f2a5fcb0efbc30a6f2783497 Mon Sep 17 00:00:00 2001 From: Joris Bayer Date: Mon, 19 Sep 2022 14:59:50 +0200 Subject: [PATCH 04/10] fix --- relay-general/src/store/mod.rs | 2 +- relay-server/src/actors/project.rs | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/relay-general/src/store/mod.rs b/relay-general/src/store/mod.rs index 5ad027f8ad..220e685088 100644 --- a/relay-general/src/store/mod.rs +++ b/relay-general/src/store/mod.rs @@ -27,7 +27,7 @@ pub use normalize::breakdowns::{ }; pub use normalize::{ compute_measurements, is_valid_platform, light_normalize_event, normalize_dist, - LightNormalizationConfig, + LightNormalizationConfig, MeasurementsConfig, }; pub use transactions::{ get_measurement, get_transaction_op, is_high_cardinality_sdk, validate_timestamps, diff --git a/relay-server/src/actors/project.rs b/relay-server/src/actors/project.rs index 0f0d7703b4..7111ae0d8a 100644 --- a/relay-server/src/actors/project.rs +++ b/relay-server/src/actors/project.rs @@ -1,6 +1,4 @@ -use std::borrow::Cow; use std::collections::{BTreeSet, VecDeque}; -use std::str::FromStr; use std::sync::Arc; use std::time::{Duration, Instant}; @@ -13,11 +11,11 @@ use smallvec::SmallVec; use url::Url; use relay_auth::PublicKey; -use relay_common::{MetricUnit, ProjectId, ProjectKey}; +use relay_common::{ProjectId, ProjectKey}; use relay_config::Config; use relay_filter::{matches_any_origin, FiltersConfig}; use relay_general::pii::{DataScrubbingConfig, PiiConfig}; -use relay_general::store::BreakdownsConfig; +use relay_general::store::{BreakdownsConfig, MeasurementsConfig}; use relay_general::types::SpanAttribute; use relay_metrics::{self, Aggregator, Bucket, Metric}; use relay_quotas::{Quota, RateLimits, Scoping}; @@ -144,6 +142,7 @@ impl Default for ProjectConfig { event_retention: None, quotas: Vec::new(), dynamic_sampling: None, + measurements: None, breakdowns_v2: None, session_metrics: SessionMetricsConfig::default(), transaction_metrics: None, @@ -176,6 +175,9 @@ pub struct LimitedProjectConfig { pub metric_conditional_tagging: Vec, #[serde(skip_serializing_if = "BTreeSet::is_empty")] pub span_attributes: BTreeSet, + /// Configuration for measurements. + #[serde(skip_serializing_if = "Option::is_none")] + pub measurements: Option, #[serde(skip_serializing_if = "Option::is_none")] pub breakdowns_v2: Option, #[serde(skip_serializing_if = "BTreeSet::is_empty")] From 98539fece02a0447ec165635557abd234dd96d6d Mon Sep 17 00:00:00 2001 From: Joris Bayer Date: Mon, 19 Sep 2022 15:38:26 +0200 Subject: [PATCH 05/10] doc: Changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15d66dd9a9..e7bdb9c8e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +**Features**: + +- Limit the number of custom measurements per event. ([#1483](https://github.com/getsentry/relay/pull/1483))) + **Internal**: - Generate a new profile ID when splitting a profile for multiple transactions. ([#1473](https://github.com/getsentry/relay/pull/1473)) From 0024d9e152f60da63e0769bbef45b87919821488 Mon Sep 17 00:00:00 2001 From: Joris Bayer Date: Tue, 20 Sep 2022 15:58:30 +0200 Subject: [PATCH 06/10] Apply suggestions from code review Co-authored-by: Iker Barriocanal <32816711+iker-barriocanal@users.noreply.github.com> --- relay-general/src/store/normalize.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/relay-general/src/store/normalize.rs b/relay-general/src/store/normalize.rs index ec8151c28f..5ea5de0706 100644 --- a/relay-general/src/store/normalize.rs +++ b/relay-general/src/store/normalize.rs @@ -241,7 +241,6 @@ fn normalize_breakdowns(event: &mut Event, breakdowns_config: Option<&Breakdowns /// Note that [`Measurements`] is a BTreeMap, which means its keys are sorted. /// This ensures that for two events with the same measurement keys, the same set of custom /// measurements is retained. -/// fn filter_custom_measurements( measurements: &mut Measurements, measurements_config: &MeasurementsConfig, From 71e8054559a1ecaec22e302681972603f8d9dc20 Mon Sep 17 00:00:00 2001 From: Joris Bayer Date: Tue, 20 Sep 2022 16:01:00 +0200 Subject: [PATCH 07/10] known_measurements -> builtin_measurements --- relay-general/src/store/normalize.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/relay-general/src/store/normalize.rs b/relay-general/src/store/normalize.rs index 5ea5de0706..5fa940c6fd 100644 --- a/relay-general/src/store/normalize.rs +++ b/relay-general/src/store/normalize.rs @@ -43,7 +43,7 @@ mod user_agent; pub struct MeasurementsConfig { /// A list of measurements that are built-in and are not subject to custom measurement limits. #[serde(default, skip_serializing_if = "BTreeSet::::is_empty")] - known_measurements: BTreeSet, + builtin_measurements: BTreeSet, /// The maximum number of measurements allowed per event that are not known measurements. max_custom_measurements: usize, @@ -248,7 +248,7 @@ fn filter_custom_measurements( let mut custom_measurements_count = 0; measurements.retain(|name, _| { // Check if this is a builtin measurement: - if measurements_config.known_measurements.contains(name) { + if measurements_config.builtin_measurements.contains(name) { return true; } // For custom measurements, check the budget: @@ -1972,7 +1972,7 @@ mod tests { let mut event = Annotated::::from_json(json).unwrap().0.unwrap(); let config: MeasurementsConfig = serde_json::from_value(json!({ - "knownMeasurements": [ + "builtinMeasurements": [ "frames_slow" ], "maxCustomMeasurements": 2, From 2b291c5ce59722dbf2016bde03c971642c4219ba Mon Sep 17 00:00:00 2001 From: Joris Bayer Date: Tue, 20 Sep 2022 16:25:29 +0200 Subject: [PATCH 08/10] ref: Stricter builtin measurement rules --- relay-general/src/store/normalize.rs | 34 ++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/relay-general/src/store/normalize.rs b/relay-general/src/store/normalize.rs index 5fa940c6fd..4a7453c6a3 100644 --- a/relay-general/src/store/normalize.rs +++ b/relay-general/src/store/normalize.rs @@ -1,5 +1,4 @@ use std::collections::hash_map::DefaultHasher; -use std::collections::BTreeSet; use std::hash::{Hash, Hasher}; use std::mem; use std::sync::Arc; @@ -37,13 +36,21 @@ mod stacktrace; mod user_agent; +/// Defines a builtin measurement. +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +#[serde(default, rename_all = "camelCase")] +pub struct BuiltinMeasurementKey { + name: String, + unit: MetricUnit, +} + /// Configuration for measurements normalization. #[derive(Debug, Default, Clone, Serialize, Deserialize)] #[serde(default, rename_all = "camelCase")] pub struct MeasurementsConfig { /// A list of measurements that are built-in and are not subject to custom measurement limits. - #[serde(default, skip_serializing_if = "BTreeSet::::is_empty")] - builtin_measurements: BTreeSet, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + builtin_measurements: Vec, /// The maximum number of measurements allowed per event that are not known measurements. max_custom_measurements: usize, @@ -246,11 +253,22 @@ fn filter_custom_measurements( measurements_config: &MeasurementsConfig, ) { let mut custom_measurements_count = 0; - measurements.retain(|name, _| { + measurements.retain(|name, value| { + let unit = match value.value().and_then(|m| m.unit.value()) { + Some(unit) => unit, + None => return false, + }; + // Check if this is a builtin measurement: - if measurements_config.builtin_measurements.contains(name) { - return true; + for builtin_measurement in &measurements_config.builtin_measurements { + if &builtin_measurement.name == name { + // If the unit matches a built-in measurement, we allow it. + // If the name matches but the unit is wrong, we do not even accept it as a custom measurement, + // and just drop it instead. + return &builtin_measurement.unit == unit; + } } + // For custom measurements, check the budget: if custom_measurements_count < measurements_config.max_custom_measurements { custom_measurements_count += 1; @@ -1963,6 +1981,7 @@ mod tests { "start_timestamp": "2021-04-26T08:00:00+0100", "measurements": { "my_custom_measurement_1": {"value": 123}, + "frames_frozen": {"value": 666, "unit": "invalid_unit"}, "frames_slow": {"value": 1}, "my_custom_measurement_3": {"value": 456}, "my_custom_measurement_2": {"value": 789} @@ -1973,7 +1992,8 @@ mod tests { let config: MeasurementsConfig = serde_json::from_value(json!({ "builtinMeasurements": [ - "frames_slow" + {"name": "frames_frozen", "unit": "none"}, + {"name": "frames_slow", "unit": "none"} ], "maxCustomMeasurements": 2, "stray_key": "zzz" From 77da15d7f818d1cec835c3fd8f7cd25bbea02d84 Mon Sep 17 00:00:00 2001 From: Joris Bayer Date: Tue, 20 Sep 2022 16:31:09 +0200 Subject: [PATCH 09/10] doc: Update function name and doc comment --- relay-general/src/store/normalize.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/relay-general/src/store/normalize.rs b/relay-general/src/store/normalize.rs index 4a7453c6a3..6831bb7636 100644 --- a/relay-general/src/store/normalize.rs +++ b/relay-general/src/store/normalize.rs @@ -243,12 +243,15 @@ fn normalize_breakdowns(event: &mut Event, breakdowns_config: Option<&Breakdowns } } -/// Enforce the limit on custom (user defined) measurements. +/// Remove measurements that do not conform to the given config. +/// +/// Built-in measurements are accepted if their unit is correct, dropped otherwise. +/// Custom measurements are accepted up to a limit. /// /// Note that [`Measurements`] is a BTreeMap, which means its keys are sorted. /// This ensures that for two events with the same measurement keys, the same set of custom /// measurements is retained. -fn filter_custom_measurements( +fn remove_invalid_measurements( measurements: &mut Measurements, measurements_config: &MeasurementsConfig, ) { @@ -286,7 +289,7 @@ fn normalize_measurements(event: &mut Event, measurements_config: Option<&Measur event.measurements = Annotated::empty(); } else if let Some(measurements) = event.measurements.value_mut() { if let Some(measurements_config) = measurements_config { - filter_custom_measurements(measurements, measurements_config); + remove_invalid_measurements(measurements, measurements_config); } let duration_millis = match (event.start_timestamp.0, event.timestamp.0) { From 8c4ac94ee5096eb4c4e098f672c42335073c6f0c Mon Sep 17 00:00:00 2001 From: Joris Bayer Date: Tue, 20 Sep 2022 16:48:27 +0200 Subject: [PATCH 10/10] fix: Fall back to unit None --- relay-general/src/store/normalize.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/relay-general/src/store/normalize.rs b/relay-general/src/store/normalize.rs index 6831bb7636..897a557a7f 100644 --- a/relay-general/src/store/normalize.rs +++ b/relay-general/src/store/normalize.rs @@ -257,10 +257,12 @@ fn remove_invalid_measurements( ) { let mut custom_measurements_count = 0; measurements.retain(|name, value| { - let unit = match value.value().and_then(|m| m.unit.value()) { - Some(unit) => unit, + let measurement = match value.value() { + Some(m) => m, None => return false, }; + // TODO(jjbayer): Should we actually normalize the unit into the event? + let unit = measurement.unit.value().unwrap_or(&MetricUnit::None); // Check if this is a builtin measurement: for builtin_measurement in &measurements_config.builtin_measurements {