From 21652381eeec6d322af5194c8bfd8ac84a93a100 Mon Sep 17 00:00:00 2001 From: Ryan Albrecht Date: Mon, 14 Jul 2025 14:23:05 -0700 Subject: [PATCH 1/6] feat(session): Add `unhandled` session status type --- relay-event-schema/src/protocol/session.rs | 37 ++++++++++++++----- .../src/metrics_extraction/sessions/mod.rs | 27 ++++++++++++++ 2 files changed, 55 insertions(+), 9 deletions(-) diff --git a/relay-event-schema/src/protocol/session.rs b/relay-event-schema/src/protocol/session.rs index edd4d38e4ae..87d8c1dc019 100644 --- a/relay-event-schema/src/protocol/session.rs +++ b/relay-event-schema/src/protocol/session.rs @@ -18,12 +18,14 @@ pub enum SessionStatus { Ok, /// The session terminated normally. Exited, - /// The session resulted in an application crash. - Crashed, /// The session had an unexpected abrupt termination (not crashing). Abnormal, /// The session exited cleanly but experienced some errors during its run. Errored, + /// The session had an unhandled error, but did not crash. + Unhandled, + /// The session resulted in an application crash. + Crashed, /// Unknown status, for forward compatibility. Unknown(String), } @@ -46,10 +48,11 @@ impl SessionStatus { fn as_str(&self) -> &str { match self { SessionStatus::Ok => "ok", - SessionStatus::Crashed => "crashed", - SessionStatus::Abnormal => "abnormal", SessionStatus::Exited => "exited", + SessionStatus::Abnormal => "abnormal", SessionStatus::Errored => "errored", + SessionStatus::Unhandled => "unhandled", + SessionStatus::Crashed => "crashed", SessionStatus::Unknown(s) => s.as_str(), } } @@ -69,10 +72,11 @@ impl std::str::FromStr for SessionStatus { fn from_str(s: &str) -> Result { Ok(match s { "ok" => SessionStatus::Ok, - "crashed" => SessionStatus::Crashed, - "abnormal" => SessionStatus::Abnormal, "exited" => SessionStatus::Exited, + "abnormal" => SessionStatus::Abnormal, "errored" => SessionStatus::Errored, + "unhandled" => SessionStatus::Unhandled, + "crashed" => SessionStatus::Crashed, other => SessionStatus::Unknown(other.to_owned()), }) } @@ -173,6 +177,7 @@ pub trait SessionLike { fn distinct_id(&self) -> Option<&String>; fn total_count(&self) -> u32; fn abnormal_count(&self) -> u32; + fn unhandled_count(&self) -> u32; fn crashed_count(&self) -> u32; fn all_errors(&self) -> Option; fn abnormal_mechanism(&self) -> AbnormalMechanism; @@ -250,6 +255,13 @@ impl SessionLike for SessionUpdate { } } + fn unhandled_count(&self) -> u32 { + match self.status { + SessionStatus::Unhandled => 1, + _ => 0, + } + } + fn crashed_count(&self) -> u32 { match self.status { SessionStatus::Crashed => 1, @@ -299,6 +311,9 @@ pub struct SessionAggregateItem { /// The number of abnormal sessions that ocurred. #[serde(default, skip_serializing_if = "is_zero")] pub abnormal: u32, + /// The number of unhandled sessions that ocurred. + #[serde(default, skip_serializing_if = "is_zero")] + pub unhandled: u32, /// The number of crashed sessions that ocurred. #[serde(default, skip_serializing_if = "is_zero")] pub crashed: u32, @@ -314,21 +329,25 @@ impl SessionLike for SessionAggregateItem { } fn total_count(&self) -> u32 { - self.exited + self.errored + self.abnormal + self.crashed + self.exited + self.abnormal + self.errored + self.unhandled + self.crashed } fn abnormal_count(&self) -> u32 { self.abnormal } + fn unhandled_count(&self) -> u32 { + self.unhandled + } + fn crashed_count(&self) -> u32 { self.crashed } fn all_errors(&self) -> Option { - // Errors contain crashed & abnormal as well. + // Errors contain all of: abnormal, unhandled, and crashed. // See https://github.com/getsentry/snuba/blob/c45f2a8636f9ea3dfada4e2d0ae5efef6c6248de/snuba/migrations/snuba_migrations/sessions/0003_sessions_matview.py#L80-L81 - let all_errored = self.abnormal + self.crashed + self.errored; + let all_errored = self.abnormal + self.errored + self.unhandled + self.crashed; if all_errored > 0 { Some(SessionErrored::Aggregated(all_errored)) } else { diff --git a/relay-server/src/metrics_extraction/sessions/mod.rs b/relay-server/src/metrics_extraction/sessions/mod.rs index 056cdf0af23..c134ac75d10 100644 --- a/relay-server/src/metrics_extraction/sessions/mod.rs +++ b/relay-server/src/metrics_extraction/sessions/mod.rs @@ -148,6 +148,33 @@ pub fn extract_session_metrics( } } + if session.unhandled_count() > 0 { + target.push( + SessionMetric::Session { + counter: session.unhandled_count().into(), + tags: SessionSessionTags { + status: "unhandled".to_owned(), + common_tags: common_tags.clone(), + }, + } + .into_metric(timestamp), + ); + + if let Some(distinct_id) = nil_to_none(session.distinct_id()) { + target.push( + SessionMetric::User { + distinct_id: distinct_id.clone(), + tags: SessionUserTags { + status: Some(SessionStatus::Unhandled), + abnormal_mechanism: None, + common_tags, + }, + } + .into_metric(timestamp), + ); + } + } + if session.crashed_count() > 0 { target.push( SessionMetric::Session { From 2e0e357a9800ef5e972dd0f3de93d59afbdc75e1 Mon Sep 17 00:00:00 2001 From: Colton Allen Date: Tue, 22 Jul 2025 12:16:32 -0500 Subject: [PATCH 2/6] Fix ordering --- relay-event-schema/src/protocol/session.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/relay-event-schema/src/protocol/session.rs b/relay-event-schema/src/protocol/session.rs index 87d8c1dc019..305f570d201 100644 --- a/relay-event-schema/src/protocol/session.rs +++ b/relay-event-schema/src/protocol/session.rs @@ -18,14 +18,14 @@ pub enum SessionStatus { Ok, /// The session terminated normally. Exited, + /// The session resulted in an application crash. + Crashed, /// The session had an unexpected abrupt termination (not crashing). Abnormal, /// The session exited cleanly but experienced some errors during its run. Errored, /// The session had an unhandled error, but did not crash. Unhandled, - /// The session resulted in an application crash. - Crashed, /// Unknown status, for forward compatibility. Unknown(String), } @@ -48,11 +48,11 @@ impl SessionStatus { fn as_str(&self) -> &str { match self { SessionStatus::Ok => "ok", - SessionStatus::Exited => "exited", + SessionStatus::Crashed => "crashed", SessionStatus::Abnormal => "abnormal", + SessionStatus::Exited => "exited", SessionStatus::Errored => "errored", SessionStatus::Unhandled => "unhandled", - SessionStatus::Crashed => "crashed", SessionStatus::Unknown(s) => s.as_str(), } } @@ -72,11 +72,11 @@ impl std::str::FromStr for SessionStatus { fn from_str(s: &str) -> Result { Ok(match s { "ok" => SessionStatus::Ok, - "exited" => SessionStatus::Exited, + "crashed" => SessionStatus::Crashed, "abnormal" => SessionStatus::Abnormal, + "exited" => SessionStatus::Exited, "errored" => SessionStatus::Errored, "unhandled" => SessionStatus::Unhandled, - "crashed" => SessionStatus::Crashed, other => SessionStatus::Unknown(other.to_owned()), }) } From 92e3074fd96aac0292e74c52614c0b7b37a079ad Mon Sep 17 00:00:00 2001 From: Ryan Albrecht Date: Tue, 22 Jul 2025 10:46:12 -0700 Subject: [PATCH 3/6] add missing .clone() --- relay-server/src/metrics_extraction/sessions/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/relay-server/src/metrics_extraction/sessions/mod.rs b/relay-server/src/metrics_extraction/sessions/mod.rs index c134ac75d10..3def0f18088 100644 --- a/relay-server/src/metrics_extraction/sessions/mod.rs +++ b/relay-server/src/metrics_extraction/sessions/mod.rs @@ -167,7 +167,7 @@ pub fn extract_session_metrics( tags: SessionUserTags { status: Some(SessionStatus::Unhandled), abnormal_mechanism: None, - common_tags, + common_tags: common_tags.clone(), }, } .into_metric(timestamp), From 6d60a627e579ae669d11f2ac422aad5bb0136cdc Mon Sep 17 00:00:00 2001 From: Ryan Albrecht Date: Tue, 22 Jul 2025 11:24:55 -0700 Subject: [PATCH 4/6] update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 762c2586d76..780c3964ea5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +**Features**: +- Add `unhandled` status type for Release Health `session` and `sessions` envelopes. ([#4939](https://github.com/getsentry/relay/pull/4939)) + **Internal**: - Emit outcomes for skipped large attachments on playstation crashes. ([#4862](https://github.com/getsentry/relay/pull/4862)) From 9b61ab2453ca43c17a9283a5eaf3b6c5847ce693 Mon Sep 17 00:00:00 2001 From: Ryan Albrecht Date: Sun, 27 Jul 2025 11:04:45 -0700 Subject: [PATCH 5/6] tests for metric extraction --- .../src/metrics_extraction/sessions/mod.rs | 138 ++++++++++++++++-- 1 file changed, 125 insertions(+), 13 deletions(-) diff --git a/relay-server/src/metrics_extraction/sessions/mod.rs b/relay-server/src/metrics_extraction/sessions/mod.rs index 3def0f18088..8ff021dc063 100644 --- a/relay-server/src/metrics_extraction/sessions/mod.rs +++ b/relay-server/src/metrics_extraction/sessions/mod.rs @@ -356,7 +356,7 @@ mod tests { } #[test] - fn test_extract_session_metrics_fatal() { + fn test_extract_session_metrics_crashed() { let session = SessionUpdate::parse( r#"{ "init": false, @@ -394,6 +394,45 @@ mod tests { assert_eq!(user_metric.tags["session.status"], "crashed"); } + #[test] + fn test_extract_session_metrics_unhandled() { + let session = SessionUpdate::parse( + r#"{ + "init": false, + "started": "2021-04-26T08:00:00+0100", + "attrs": { + "release": "1.0.0" + }, + "did": "user123", + "status": "unhandled" + }"# + .as_bytes(), + ) + .unwrap(); + + let mut metrics = vec![]; + + extract_session_metrics(&session.attributes, &session, None, &mut metrics, true); + + assert_eq!(metrics.len(), 4); + + assert_eq!(&*metrics[0].name, "s:sessions/error@none"); + assert_eq!(&*metrics[1].name, "s:sessions/user@none"); + assert_eq!(metrics[1].tags["session.status"], "errored"); + + let session_metric = &metrics[2]; + assert_eq!(session_metric.timestamp, started()); + assert_eq!(&*session_metric.name, "c:sessions/session@none"); + assert!(matches!(session_metric.value, BucketValue::Counter(_))); + assert_eq!(session_metric.tags["session.status"], "unhandled"); + + let user_metric = &metrics[3]; + assert_eq!(user_metric.timestamp, started()); + assert_eq!(&*user_metric.name, "s:sessions/user@none"); + assert!(matches!(user_metric.value, BucketValue::Set(_))); + assert_eq!(user_metric.tags["session.status"], "unhandled"); + } + #[test] fn test_extract_session_metrics_abnormal() { for (abnormal_mechanism, expected_tag_value) in [ @@ -470,16 +509,18 @@ mod tests { r#"{ "aggregates": [ { - "started": "2020-02-07T14:16:00Z", - "exited": 123, - "abnormal": 5, - "crashed": 7 + "started": "2020-02-07T14:16:00Z", + "exited": 143, + "abnormal": 5, + "crashed": 7, + "unhandled": 10 }, { - "started": "2020-02-07T14:16:01Z", - "did": "optional distinct user id", - "exited": 12, - "errored": 3 + "started": "2020-02-07T14:16:01Z", + "did": "optional distinct user id", + "exited": 22, + "errored": 3, + "unhandled": 10 } ], "attrs": { @@ -510,7 +551,7 @@ mod tests { "c:sessions/session@none", ), value: Counter( - 135.0, + 165.0, ), tags: { "environment": "development", @@ -533,7 +574,7 @@ mod tests { "c:sessions/session@none", ), value: Counter( - 12.0, + 22.0, ), tags: { "environment": "development", @@ -572,6 +613,29 @@ mod tests { extracted_from_indexed: false, }, }, + Bucket { + timestamp: UnixTimestamp(1581084960), + width: 0, + name: MetricName( + "c:sessions/session@none", + ), + value: Counter( + 10.0, + ), + tags: { + "environment": "development", + "release": "my-project-name@1.0.0", + "sdk": "sentry-test/1.0", + "session.status": "unhandled", + }, + metadata: BucketMetadata { + merges: 1, + received_at: Some( + UnixTimestamp(0), + ), + extracted_from_indexed: false, + }, + }, Bucket { timestamp: UnixTimestamp(1581084960), width: 0, @@ -602,7 +666,7 @@ mod tests { "c:sessions/session@none", ), value: Counter( - 15.0, + 35.0, ), tags: { "environment": "development", @@ -625,7 +689,7 @@ mod tests { "c:sessions/session@none", ), value: Counter( - 3.0, + 13.0, ), tags: { "environment": "development", @@ -666,6 +730,54 @@ mod tests { extracted_from_indexed: false, }, }, + Bucket { + timestamp: UnixTimestamp(1581084961), + width: 0, + name: MetricName( + "c:sessions/session@none", + ), + value: Counter( + 10.0, + ), + tags: { + "environment": "development", + "release": "my-project-name@1.0.0", + "sdk": "sentry-test/1.0", + "session.status": "unhandled", + }, + metadata: BucketMetadata { + merges: 1, + received_at: Some( + UnixTimestamp(0), + ), + extracted_from_indexed: false, + }, + }, + Bucket { + timestamp: UnixTimestamp(1581084961), + width: 0, + name: MetricName( + "s:sessions/user@none", + ), + value: Set( + { + 3097475539, + }, + ), + tags: { + "environment": "development", + "release": "my-project-name@1.0.0", + "sdk": "sentry-test/1.0", + "session.status": "unhandled", + }, + metadata: BucketMetadata { + merges: 1, + received_at: Some( + UnixTimestamp(0), + ), + extracted_from_indexed: false, + }, + }, ] "###); } From ba79b5edc780f883a03d63d9fafbda65fbba557b Mon Sep 17 00:00:00 2001 From: Colton Allen Date: Tue, 29 Jul 2025 10:47:49 -0500 Subject: [PATCH 6/6] Update CHANGELOG.md Co-authored-by: David Herberth --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 780c3964ea5..c0d96542677 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased **Features**: + - Add `unhandled` status type for Release Health `session` and `sessions` envelopes. ([#4939](https://github.com/getsentry/relay/pull/4939)) **Internal**: