From 9ba3dd370c81c6366007f2e73f9a3fc88d9118db Mon Sep 17 00:00:00 2001 From: Daniel Szoke Date: Fri, 22 May 2026 14:08:04 +0200 Subject: [PATCH] feat(client-reports): Client report protocol Added WIP client report protocol. Resolves [#1001](https://github.com/getsentry/sentry-rust/issues/1001) Resolves [RUST-153](https://linear.app/getsentry/issue/RUST-153/add-client-report-protocol-envelope-item-support-in-sentry-types) --- .../src/protocol/client_report/list.rs | 66 +++++++++++++++++++ .../src/protocol/client_report/mod.rs | 60 +++++++++++++++++ sentry-types/src/protocol/mod.rs | 1 + sentry-types/src/protocol/v7.rs | 1 + 4 files changed, 128 insertions(+) create mode 100644 sentry-types/src/protocol/client_report/list.rs create mode 100644 sentry-types/src/protocol/client_report/mod.rs diff --git a/sentry-types/src/protocol/client_report/list.rs b/sentry-types/src/protocol/client_report/list.rs new file mode 100644 index 00000000..a8ca3112 --- /dev/null +++ b/sentry-types/src/protocol/client_report/list.rs @@ -0,0 +1,66 @@ +//! Module with code for representing the underlying list of client reports. + +use std::collections::HashMap; + +use serde::ser::SerializeSeq as _; +use serde::{Serialize, Serializer}; + +use super::{DataCategory, DiscardReason}; + +#[derive(Debug)] +pub(super) struct ClientReportList(HashMap); + +#[derive(Debug, Serialize)] +struct ClientReportItem { + #[serde(flatten)] + reason_category: ReasonCategory, + quantity: u64, +} + +/// A reason/category pair. Used to key the discarded events. +#[derive(Debug, Serialize, PartialEq, Eq, Hash, Clone, Copy)] +struct ReasonCategory { + reason: DiscardReason, + category: DataCategory, +} + +impl ClientReportList { + /// Insert an item into the list. + /// + /// Records `quantity` discarded events in the given data `category` for the given discard + /// `reason`. If there is already a record for that (`category`, `reason`) pair, we increment + /// the quantity of the existing pair, accordingly. + pub(super) fn add(&mut self, category: DataCategory, reason: DiscardReason, quantity: u64) { + let reason_category = ReasonCategory { category, reason }; + let val = self.0.entry(reason_category).or_default(); + *val = val.saturating_add(quantity); + } + + fn iter(&self) -> impl Iterator + '_ { + self.0 + .iter() + .map(|(&reason_category, &quantity)| ClientReportItem { + reason_category, + quantity, + }) + } + + fn len(&self) -> usize { + self.0.len() + } +} + +impl Serialize for ClientReportList { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let seq = serializer.serialize_seq(Some(self.len()))?; + + self.iter() + .try_fold(seq, |mut seq, item| { + seq.serialize_element(&item).map(|()| seq) + })? + .end() + } +} diff --git a/sentry-types/src/protocol/client_report/mod.rs b/sentry-types/src/protocol/client_report/mod.rs new file mode 100644 index 00000000..737e6638 --- /dev/null +++ b/sentry-types/src/protocol/client_report/mod.rs @@ -0,0 +1,60 @@ +//! Module containing types related to [Client Reports]. +//! +//! [Client Reports]: https://develop.sentry.dev/sdk/telemetry/client-reports/ + +use std::time::SystemTime; + +use serde::Serialize; + +use self::list::ClientReportList; +use crate::utils; + +mod list; + +/// A [client report]. +/// +/// [client report]: https://develop.sentry.dev/sdk/telemetry/client-reports/ +#[derive(Debug, Serialize)] +pub struct ClientReport { + #[serde(with = "utils::ts_seconds_float")] + timestamp: SystemTime, + discarded_events: ClientReportList, +} + +impl ClientReport { + /// Insert an item into the `discarded_events` list. + /// + /// Records `quantity` discarded events in the given data `category` for the given discard + /// `reason`. If there is already a record for that (`category`, `reason`) pair, we increment + /// the quantity of the existing pair, accordingly. + pub fn add_discarded_event( + &mut self, + category: DataCategory, + reason: DiscardReason, + quantity: u64, + ) { + self.discarded_events.add(category, reason, quantity); + } +} + +/// The reason why a telemetry item was discarded. +/// +/// Valid discard reasons are listed in the [develop docs]; this enum may only define a subset of +/// these data categories, but we will add further categories as we begin using them in the SDK. +/// +/// [develop docs]: https://develop.sentry.dev/sdk/telemetry/client-reports/#discard-reasons-1 +#[derive(Debug, Serialize, PartialEq, Eq, Hash, Clone, Copy)] +#[serde(rename_all = "snake_case")] +#[non_exhaustive] +pub enum DiscardReason {} + +/// The category of data which was dropped. +/// +/// Valid categories are listed in the [develop docs]; this enum may only define a subset of these +/// valid data categories, but we will add further categories as we begin using them in the SDK. +/// +/// [develop docs]: https://develop.sentry.dev/sdk/foundations/transport/rate-limiting/#definitions +#[derive(Debug, Serialize, PartialEq, Eq, Hash, Clone, Copy)] +#[serde(rename_all = "snake_case")] +#[non_exhaustive] +pub enum DataCategory {} diff --git a/sentry-types/src/protocol/mod.rs b/sentry-types/src/protocol/mod.rs index ec80a4db..7639466c 100644 --- a/sentry-types/src/protocol/mod.rs +++ b/sentry-types/src/protocol/mod.rs @@ -14,6 +14,7 @@ pub const LATEST: u16 = 7; pub use v7 as latest; mod attachment; +mod client_report; mod envelope; mod monitor; mod session; diff --git a/sentry-types/src/protocol/v7.rs b/sentry-types/src/protocol/v7.rs index c952ee2b..4d1099a5 100644 --- a/sentry-types/src/protocol/v7.rs +++ b/sentry-types/src/protocol/v7.rs @@ -25,6 +25,7 @@ pub use uuid::Uuid; use crate::utils::{display_from_str_opt, ts_rfc3339_opt, ts_seconds_float}; pub use super::attachment::*; +pub use super::client_report::ClientReport; pub use super::envelope::*; pub use super::monitor::*; pub use super::session::*;