diff --git a/CHANGELOG.md b/CHANGELOG.md index 62cf7e01af0..a814e17c1ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ - Group resource spans by scrubbed domain and filename. ([#2654](https://github.com/getsentry/relay/pull/2654)) - Convert transactions to spans for all organizations. ([#2659](https://github.com/getsentry/relay/pull/2659)) - Filter outliers (>180s) for mobile measurements. ([#2649](https://github.com/getsentry/relay/pull/2649)) -- Allow access to more context fields in dynamic sampling and metric extraction. ([#2607](https://github.com/getsentry/relay/pull/2607), [#2640](https://github.com/getsentry/relay/pull/2640), [#2675](https://github.com/getsentry/relay/pull/2675), [#2707](https://github.com/getsentry/relay/pull/2707)) +- Allow access to more context fields in dynamic sampling and metric extraction. ([#2607](https://github.com/getsentry/relay/pull/2607), [#2640](https://github.com/getsentry/relay/pull/2640), [#2675](https://github.com/getsentry/relay/pull/2675), [#2707](https://github.com/getsentry/relay/pull/2707), [#2715](https://github.com/getsentry/relay/pull/2715)) - Allow advanced scrubbing expressions for datascrubbing safe fields. ([#2670](https://github.com/getsentry/relay/pull/2670)) - Disable graphql scrubbing when datascrubbing is disabled. ([#2689](https://github.com/getsentry/relay/pull/2689)) - Track when a span was received. ([#2688](https://github.com/getsentry/relay/pull/2688)) diff --git a/relay-event-schema/src/protocol/event.rs b/relay-event-schema/src/protocol/event.rs index 91352504870..3ff95bb2487 100644 --- a/relay-event-schema/src/protocol/event.rs +++ b/relay-event-schema/src/protocol/event.rs @@ -14,9 +14,9 @@ use crate::processor::ProcessValue; use crate::protocol::{ AppContext, Breadcrumb, Breakdowns, BrowserContext, ClientSdkInfo, Contexts, Csp, DebugMeta, DefaultContext, DeviceContext, EventType, Exception, ExpectCt, ExpectStaple, Fingerprint, Hpkp, - LenientString, Level, LogEntry, Measurements, Metrics, OsContext, RelayInfo, Request, - ResponseContext, Span, Stacktrace, Tags, TemplateInfo, Thread, Timestamp, TraceContext, - TransactionInfo, User, Values, + LenientString, Level, LogEntry, Measurements, Metrics, OsContext, ProfileContext, RelayInfo, + Request, ResponseContext, Span, Stacktrace, Tags, TemplateInfo, Thread, Timestamp, + TraceContext, TransactionInfo, User, Values, }; /// Wrapper around a UUID with slightly different formatting. @@ -708,6 +708,12 @@ impl Getter for Event { "contexts.browser.version" => { self.context::()?.version.as_str()?.into() } + "contexts.profile.profile_id" => self + .context::()? + .profile_id + .value()? + .0 + .into(), "contexts.device.uuid" => self.context::()?.uuid.value()?.into(), "contexts.trace.status" => self .context::()? @@ -1102,6 +1108,11 @@ mod tests { kernel_version: Annotated::new("17.4.0".to_string()), ..OsContext::default() }); + contexts.add(ProfileContext { + profile_id: Annotated::new(EventId(uuid!( + "abadcade-feed-dead-beef-8addadfeedaa" + ))), + }); contexts }), ..Default::default() @@ -1185,6 +1196,10 @@ mod tests { Some(Val::String("https://sentry.io")), event.get_value("event.request.url") ); + assert_eq!( + Some(Val::Uuid(uuid!("abadcade-feed-dead-beef-8addadfeedaa"))), + event.get_value("event.contexts.profile.profile_id") + ) } #[test] diff --git a/relay-profiling/src/lib.rs b/relay-profiling/src/lib.rs index 689aed5b396..eb8f08e7a04 100644 --- a/relay-profiling/src/lib.rs +++ b/relay-profiling/src/lib.rs @@ -118,10 +118,15 @@ mod utils; const MAX_PROFILE_DURATION: Duration = Duration::from_secs(30); +/// Unique identifier for a profile. +/// +/// Same format as event IDs. +pub type ProfileId = EventId; + #[derive(Debug, Deserialize)] struct MinimalProfile { #[serde(alias = "profile_id")] - event_id: EventId, + event_id: ProfileId, platform: String, #[serde(default)] version: sample::Version, @@ -134,7 +139,7 @@ fn minimal_profile_from_json( serde_path_to_error::deserialize(d) } -pub fn parse_metadata(payload: &[u8], project_id: ProjectId) -> Result<(), ProfileError> { +pub fn parse_metadata(payload: &[u8], project_id: ProjectId) -> Result { let profile = match minimal_profile_from_json(payload) { Ok(profile) => profile, Err(err) => { @@ -183,10 +188,10 @@ pub fn parse_metadata(payload: &[u8], project_id: ProjectId) -> Result<(), Profi _ => return Err(ProfileError::PlatformNotSupported), }, }; - Ok(()) + Ok(profile.event_id) } -pub fn expand_profile(payload: &[u8], event: &Event) -> Result<(EventId, Vec), ProfileError> { +pub fn expand_profile(payload: &[u8], event: &Event) -> Result<(ProfileId, Vec), ProfileError> { let profile = match minimal_profile_from_json(payload) { Ok(profile) => profile, Err(err) => { diff --git a/relay-server/src/actors/processor.rs b/relay-server/src/actors/processor.rs index d1c02ffb202..f4c710db978 100644 --- a/relay-server/src/actors/processor.rs +++ b/relay-server/src/actors/processor.rs @@ -31,8 +31,8 @@ use relay_event_normalization::{GeoIpLookup, RawUserAgentInfo}; use relay_event_schema::processor::{self, ProcessingAction, ProcessingState}; use relay_event_schema::protocol::{ Breadcrumb, ClientReport, Contexts, Csp, Event, EventType, ExpectCt, ExpectStaple, Hpkp, - IpAddr, LenientString, Metrics, NetworkReportError, OtelContext, RelayInfo, Replay, - SecurityReportType, SessionAggregates, SessionAttributes, SessionStatus, SessionUpdate, + IpAddr, LenientString, Metrics, NetworkReportError, OtelContext, ProfileContext, RelayInfo, + Replay, SecurityReportType, SessionAggregates, SessionAttributes, SessionStatus, SessionUpdate, Timestamp, TraceContext, UserReport, Values, }; use relay_filter::FilterStatKey; @@ -58,7 +58,7 @@ use { crate::actors::project_cache::UpdateRateLimits, crate::utils::{EnvelopeLimiter, MetricsLimiter}, relay_event_normalization::{span, StoreConfig, StoreProcessor}, - relay_event_schema::protocol::{ProfileContext, Span}, + relay_event_schema::protocol::Span, relay_metrics::Aggregator, relay_quotas::{RateLimitingError, RedisRateLimiter}, relay_redis::RedisPool, @@ -1096,15 +1096,15 @@ impl EnvelopeProcessorService { .items() .filter(|item| item.ty() == &ItemType::Transaction) .count(); - let mut found_profile = false; + let mut profile_id = None; state.managed_envelope.retain_items(|item| match item.ty() { // Drop profile without a transaction in the same envelope. ItemType::Profile if transaction_count == 0 => ItemAction::DropSilently, // First profile found in the envelope, we'll keep it if metadata are valid. - ItemType::Profile if !found_profile => { + ItemType::Profile if profile_id.is_none() => { match relay_profiling::parse_metadata(&item.payload(), state.project_id) { - Ok(_) => { - found_profile = true; + Ok(id) => { + profile_id = Some(id); ItemAction::Keep } Err(err) => ItemAction::Drop(Outcome::Invalid(DiscardReason::Profiling( @@ -1118,7 +1118,22 @@ impl EnvelopeProcessorService { ))), _ => ItemAction::Keep, }); - state.has_profile = found_profile; + state.has_profile = profile_id.is_some(); + + if let Some(event) = state.event.value_mut() { + if event.ty.value() == Some(&EventType::Transaction) { + let contexts = event.contexts.get_or_insert_with(Contexts::new); + // If we found a profile, add its ID to the profile context on the transaction. + if let Some(profile_id) = profile_id { + contexts.add(ProfileContext { + profile_id: Annotated::new(profile_id), + }); + // If not, we delete the profile context. + } else { + contexts.remove::(); + } + } + } } /// Normalize monitor check-ins and remove invalid ones. @@ -1182,17 +1197,13 @@ impl EnvelopeProcessorService { } _ => ItemAction::Keep, }); - if let Some(event) = state.event.value_mut() { - if event.ty.value() == Some(&EventType::Transaction) { - let contexts = event.contexts.get_or_insert_with(Contexts::new); - // If we found a profile, add its ID to the profile context on the transaction. - if let Some(profile_id) = found_profile_id { - contexts.add(ProfileContext { - profile_id: Annotated::new(profile_id), - }); - // If not, we delete the profile context. - } else { - contexts.remove::(); + if found_profile_id.is_none() { + // Remove profile context from event. + if let Some(event) = state.event.value_mut() { + if event.ty.value() == Some(&EventType::Transaction) { + if let Some(contexts) = event.contexts.value_mut() { + contexts.remove::(); + } } } } @@ -2776,7 +2787,6 @@ impl EnvelopeProcessorService { if_processing!({ self.enforce_quotas(state)?; - // We need the event parsed in order to set the profile context on it self.process_profiles(state); self.process_check_ins(state); });