From 8dcab8e8811755a4f0797ece67a9b87c7ccc9cdc Mon Sep 17 00:00:00 2001 From: David Herberth Date: Wed, 19 Nov 2025 10:57:02 +0100 Subject: [PATCH] feat(eap): Add support for native scalar arrays --- relay-event-normalization/src/eap/mod.rs | 10 +++++++++ relay-event-schema/src/protocol/attributes.rs | 21 +++++++++++++++++++ relay-otel/src/lib.rs | 1 + relay-server/src/processing/logs/store.rs | 20 ++++++++++++++++-- 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/relay-event-normalization/src/eap/mod.rs b/relay-event-normalization/src/eap/mod.rs index 49e96d7c974..a8572bc07c5 100644 --- a/relay-event-normalization/src/eap/mod.rs +++ b/relay-event-normalization/src/eap/mod.rs @@ -43,6 +43,16 @@ pub fn normalize_attribute_types(attributes: &mut Annotated) { (Annotated(Some(Double), _), Annotated(Some(Value::U64(_)), _)) => (), (Annotated(Some(Double), _), Annotated(Some(Value::F64(_)), _)) => (), (Annotated(Some(String), _), Annotated(Some(Value::String(_)), _)) => (), + (Annotated(Some(Array), _), Annotated(Some(Value::Array(arr)), _)) => { + // TODO: implement this check + let is_supported_array = arr.iter().all(|v| v.value().is_some()); + + if !is_supported_array { + let original = attribute.value_mut().take(); + attribute.meta_mut().add_error(ErrorKind::InvalidData); + attribute.meta_mut().set_original_value(original); + } + } // Note: currently the mapping to Kafka requires that invalid or unknown combinations // of types and values are removed from the mapping. // diff --git a/relay-event-schema/src/protocol/attributes.rs b/relay-event-schema/src/protocol/attributes.rs index a277f315b94..c2a90a2c390 100644 --- a/relay-event-schema/src/protocol/attributes.rs +++ b/relay-event-schema/src/protocol/attributes.rs @@ -120,10 +120,29 @@ pub fn attribute_pii_from_conventions(state: &ProcessingState) -> Pii { #[derive(Debug, Clone, PartialEq, Eq)] pub enum AttributeType { + /// A boolean type. + /// + /// The respective value must be of type [`Value::Bool`]. Boolean, + /// A integer type. + /// + /// The respective value must be of type [`Value::I64`] or [`Value::U64`]. Integer, + /// A floating point/double type. + /// + /// The respective value must be of type [`Value::F64`]. Double, + /// A string type. + /// + /// The respective value must be of type [`Value::String`]. String, + /// A string type. + /// + /// The respective value must be of type [`Value::Array`]. + Array, + /// An unknown type. + /// + /// Kept for forward compatibility. Unknown(String), } @@ -136,6 +155,7 @@ impl AttributeType { Self::Integer => "integer", Self::Double => "double", Self::String => "string", + Self::Array => "array", Self::Unknown(value) => value, } } @@ -158,6 +178,7 @@ impl From for AttributeType { "integer" => Self::Integer, "double" => Self::Double, "string" => Self::String, + "array" => Self::Array, _ => Self::Unknown(value), } } diff --git a/relay-otel/src/lib.rs b/relay-otel/src/lib.rs index 3fd2644b016..79000c4221a 100644 --- a/relay-otel/src/lib.rs +++ b/relay-otel/src/lib.rs @@ -58,6 +58,7 @@ pub fn otel_value_to_attribute(otel_value: OtelValue) -> Option { }) .collect(); + // TODO: convert to array here to support arrays for otlp let json = serde_json::to_string(&safe_values).ok()?; (AttributeType::String, Value::String(json)) } diff --git a/relay-server/src/processing/logs/store.rs b/relay-server/src/processing/logs/store.rs index edd75413055..61354a8c1d3 100644 --- a/relay-server/src/processing/logs/store.rs +++ b/relay-server/src/processing/logs/store.rs @@ -5,7 +5,7 @@ use prost_types::Timestamp; use relay_event_schema::protocol::{Attributes, OurLog, OurLogLevel, SpanId}; use relay_protocol::{Annotated, IntoValue, Value}; use relay_quotas::Scoping; -use sentry_protos::snuba::v1::{AnyValue, TraceItem, TraceItemType, any_value}; +use sentry_protos::snuba::v1::{AnyValue, ArrayValue, TraceItem, TraceItemType, any_value}; use uuid::Uuid; use crate::envelope::WithHeader; @@ -140,15 +140,31 @@ fn attributes( continue; }; + // TODO: share this code with all EAP items let Some(value) = (match value { Value::Bool(v) => Some(any_value::Value::BoolValue(v)), Value::I64(v) => Some(any_value::Value::IntValue(v)), Value::U64(v) => i64::try_from(v).ok().map(any_value::Value::IntValue), Value::F64(v) => Some(any_value::Value::DoubleValue(v)), Value::String(v) => Some(any_value::Value::StringValue(v)), + Value::Array(v) => Some(any_value::Value::ArrayValue(ArrayValue { + values: v + .into_iter() + .filter_map(|v| v.into_value()) + .map(|v| match v { + Value::Bool(v) => any_value::Value::BoolValue(v), + Value::I64(v) => any_value::Value::IntValue(v), + Value::U64(v) => any_value::Value::IntValue(v as i64), + Value::F64(v) => any_value::Value::DoubleValue(v), + Value::String(v) => any_value::Value::StringValue(v), + Value::Array(_) | Value::Object(_) => todo!(), + }) + .map(|v| AnyValue { value: Some(v) }) + .collect(), + })), // These cases do not happen, as they are not valid attributes // and they should have been filtered out before already. - Value::Array(_) | Value::Object(_) => { + Value::Object(_) => { debug_assert!(false, "unsupported log value"); None }