From fb28efee19465fdd2a790d089cc3abb00ae05796 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Fri, 9 Oct 2026 08:08:13 +0000 Subject: [PATCH 1/4] chore(deps): Bump sentry to 0.49.3 --- Cargo.lock | 70 ++++++++++++------------- Cargo.toml | 2 +- objectstore-server/src/observability.rs | 48 ++++++++--------- objectstore-server/src/web/app.rs | 5 +- 4 files changed, 61 insertions(+), 64 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aa2c00a7..581818fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1087,7 +1087,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1835,7 +1835,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2 0.6.4", "system-configuration", "tokio", "tower-service", @@ -2500,7 +2500,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -3516,7 +3516,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.5.10", + "socket2 0.6.4", "thiserror", "tokio", "tracing", @@ -3554,7 +3554,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.4", "tracing", "windows-sys 0.60.2", ] @@ -3981,7 +3981,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4040,7 +4040,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4151,7 +4151,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b55fb86dfd3a2f5f76ea78310a88f96c4ea21a3031f8d212443d56123fd0521" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4162,9 +4162,9 @@ checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "sentry" -version = "0.48.3" +version = "0.49.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1975064d9f3d9d87a7c8f0371f7fac10d876906ca3e39faad72e61c263ff9b87" +checksum = "9fa951f4e644464ebfd2347a7ab03b0828c512d2448a76262cad2607b343dff4" dependencies = [ "cfg_aliases", "httpdate", @@ -4185,9 +4185,9 @@ dependencies = [ [[package]] name = "sentry-actix" -version = "0.48.3" +version = "0.49.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488b43adc03f07b01563ea078c33285c5a9bbbc34d07aaa483d82922db959637" +checksum = "909d0a93b15d1ddcde3894aa23d003cbefc39ef53fe6d7c8294357ced2969cfb" dependencies = [ "actix-http", "actix-web", @@ -4198,9 +4198,9 @@ dependencies = [ [[package]] name = "sentry-backtrace" -version = "0.48.3" +version = "0.49.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134f552b6b147f77aeee46ece875d67a8bfc1d56fe3860d78446ed4c25bb5f9f" +checksum = "f4f27cf048aa63ccd5bc94c29989133eabb82e9c6bf7441b58e9d4a95837a105" dependencies = [ "backtrace", "regex", @@ -4209,9 +4209,9 @@ dependencies = [ [[package]] name = "sentry-contexts" -version = "0.48.3" +version = "0.49.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98482b938fb1f31ecd23506fb7f08b2ba20d60b9cc72581b1205a9acd31d32fa" +checksum = "c5ed680585dbdacdf9859c2b4dc53358ff02bd256766882e22e343836db36b22" dependencies = [ "hostname", "libc", @@ -4223,11 +4223,11 @@ dependencies = [ [[package]] name = "sentry-core" -version = "0.48.3" +version = "0.49.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cff96247f4dc36867511d108709e703eb354143e7893319d763d2dd1edb4f48" +checksum = "c3ed398494bb8fc412a11a182c1883a10ae147587b8f6314abd222d1d626f31b" dependencies = [ - "rand 0.9.4", + "rand 0.10.2", "sentry-types", "serde", "serde_json", @@ -4236,9 +4236,9 @@ dependencies = [ [[package]] name = "sentry-debug-images" -version = "0.48.3" +version = "0.49.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b603a51b083141062a142d73e36391b14244b4bd0bfe28bcd80e8327bb1d7698" +checksum = "55854bb40681e84ffd2a69578ad4ef9bfaf5bf2bf1380a745578b591fa24af22" dependencies = [ "findshlibs", "sentry-core", @@ -4246,9 +4246,9 @@ dependencies = [ [[package]] name = "sentry-log" -version = "0.48.3" +version = "0.49.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74df4d09a38fd59da258269ffd7f344bd308470117d68eda5a95b4fbd65683d0" +checksum = "9a3dc14009e1c24b07a8ff9ff8e1bcd0913b3e448b1565e679414776f2e58651" dependencies = [ "bitflags", "log", @@ -4289,9 +4289,9 @@ dependencies = [ [[package]] name = "sentry-panic" -version = "0.48.3" +version = "0.49.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61de3f4a1fc77d4c57e8bacd8ef064c26aaa88ea5b2541a761d37c7c3703b9a9" +checksum = "b7153e68642b761ed3aa62c022795c11b2facc728b2db7484084492373c944f9" dependencies = [ "sentry-backtrace", "sentry-core", @@ -4299,9 +4299,9 @@ dependencies = [ [[package]] name = "sentry-tower" -version = "0.48.3" +version = "0.49.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d5cb61301e328cbd42d2fede094aca746674b359fcd9c44691f027820e8fe59" +checksum = "624ee2c60caf72fa2cd6be00334b3a3ce0c6df3b1f01941ea62e3d244492d334" dependencies = [ "axum", "http 1.4.2", @@ -4314,9 +4314,9 @@ dependencies = [ [[package]] name = "sentry-tracing" -version = "0.48.3" +version = "0.49.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc59b27dae3bb495e37e6d62d252214840a2e7e1875531ee9fa2953df8985537" +checksum = "e7e7db1aefdf623f4e48e21ec9e4af535024187aeafb2a4cc7e29d035def5a13" dependencies = [ "bitflags", "sentry-backtrace", @@ -4327,13 +4327,13 @@ dependencies = [ [[package]] name = "sentry-types" -version = "0.48.3" +version = "0.49.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d7e78132ccfb4a1c777b959fb2944d1f6d5145bffe6be2a98ee6b25d244f72c" +checksum = "1c18bf7b159bdfc7c8f97816f476b4947a1937fb6bea9d38d3479e58631614ac" dependencies = [ "debugid", "hex", - "rand 0.9.4", + "rand 0.10.2", "serde", "serde_json", "thiserror", @@ -4562,7 +4562,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4698,7 +4698,7 @@ dependencies = [ "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -5491,7 +5491,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 3282e21c..cc0093e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,7 +83,7 @@ reqwest = { version = "0.13.4", default-features = false } ring = "0.17.14" rustls = { version = "0.23.40", default-features = false } secrecy = "0.10.3" -sentry = "0.48.3" +sentry = "0.49.3" sentry-options = "1.2.4" # - `ssl` is required for SASL/SCRAM # - `librdkafka` must be built from source either way, and `cmake-build` is the diff --git a/objectstore-server/src/observability.rs b/objectstore-server/src/observability.rs index 1a43ef2f..dbc0d892 100644 --- a/objectstore-server/src/observability.rs +++ b/objectstore-server/src/observability.rs @@ -30,30 +30,30 @@ pub fn init_sentry(config: &Config) -> Option { } }; - let guard = sentry::init(sentry::ClientOptions { - dsn, - release: Some(RELEASE.into()), - environment: config.environment.clone(), - server_name: config.server_name.clone(), - sample_rate: config.sample_rate, - traces_sampler: { - let traces_sample_rate = config.traces_sample_rate; - let inherit_sampling_decision = config.inherit_sampling_decision; - Some(std::sync::Arc::new(move |ctx| { - if let Some(sampled) = ctx.sampled() - && inherit_sampling_decision - { - f32::from(sampled) - } else { - traces_sample_rate - } - })) - }, - enable_logs: true, - attach_stacktrace: config.attach_stacktrace, - debug: config.debug, - ..Default::default() - }); + let traces_sample_rate = config.traces_sample_rate; + let inherit_sampling_decision = config.inherit_sampling_decision; + let mut options = sentry::ClientOptions::new() + .release(RELEASE) + .traces_sampler(move |ctx| { + if let Some(sampled) = ctx.sampled() + && inherit_sampling_decision + { + f32::from(sampled) + } else { + traces_sample_rate + } + }) + .attach_stacktrace(config.attach_stacktrace) + .debug(config.debug); + // Assigned directly rather than via the builder: the `dsn` setter only accepts an unparsed + // string, `environment`/`server_name` setters don't accept `Option`, and the `sample_rate` + // setter panics on out-of-range values while the struct field never did. + options.dsn = dsn; + options.environment = config.environment.clone(); + options.server_name = config.server_name.clone(); + options.event_sampling_strategy = sentry::EventSamplingStrategy::FixedRate(config.sample_rate); + + let guard = sentry::init(options); sentry::configure_scope(|scope| { for (k, v) in &config.tags { diff --git a/objectstore-server/src/web/app.rs b/objectstore-server/src/web/app.rs index 5c8aebde..f3dd96c7 100644 --- a/objectstore-server/src/web/app.rs +++ b/objectstore-server/src/web/app.rs @@ -165,10 +165,7 @@ mod tests { assert!(String::from_utf8_lossy(&body).contains("404 Not Found")); }) }, - sentry::ClientOptions { - traces_sample_rate: 1.0, - ..Default::default() - }, + sentry::ClientOptions::new().traces_sample_rate(1.0), ); let transactions: Vec<_> = envelopes .iter() From eed563dbdb696ee7910faeeba297651672993c3b Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Fri, 9 Oct 2026 09:08:22 +0000 Subject: [PATCH 2/4] ref(server): Configure Sentry via builder, validate sample rates, raise transport capacity - Use ClientOptions builder setters for all options - Reject sample rates outside 0.0..=1.0 when loading config - Set transport_channel_capacity to 100 (SDK default 30 dropped envelopes) --- objectstore-server/src/config.rs | 39 ++++++++++++++++++++-- objectstore-server/src/observability.rs | 43 ++++++++++++++----------- 2 files changed, 61 insertions(+), 21 deletions(-) diff --git a/objectstore-server/src/config.rs b/objectstore-server/src/config.rs index beb87d9d..e8fe610d 100644 --- a/objectstore-server/src/config.rs +++ b/objectstore-server/src/config.rs @@ -222,7 +222,8 @@ pub struct Sentry { /// Error event sampling rate. /// /// Controls what percentage of error events are sent to Sentry. A value of `1.0` sends all - /// errors, while `0.5` sends 50% of errors, and `0.0` sends no errors. + /// errors, while `0.5` sends 50% of errors, and `0.0` sends no errors. Must be between `0.0` + /// and `1.0`, inclusive; other values are rejected when loading the configuration. /// /// # Default /// @@ -231,12 +232,15 @@ pub struct Sentry { /// # Environment Variable /// /// `OS__SENTRY__SAMPLE_RATE` + #[serde(deserialize_with = "deserialize_sample_rate")] pub sample_rate: f32, /// Performance trace sampling rate. /// /// Controls what percentage of transactions (traces) are sent to Sentry for performance - /// monitoring. A value of `1.0` sends all traces, while `0.01` sends 1% of traces. + /// monitoring. A value of `1.0` sends all traces, while `0.01` sends 1% of traces. Must be + /// between `0.0` and `1.0`, inclusive; other values are rejected when loading the + /// configuration. /// /// **Important**: Performance traces can generate significant data volume in high-traffic /// systems. Start with a low rate (0.01-0.1) and adjust based on traffic and Sentry quota. @@ -248,6 +252,7 @@ pub struct Sentry { /// # Environment Variable /// /// `OS__SENTRY__TRACES_SAMPLE_RATE` + #[serde(deserialize_with = "deserialize_sample_rate")] pub traces_sample_rate: f32, /// Whether to inherit sampling decisions from incoming traces. @@ -323,6 +328,20 @@ pub struct Sentry { pub tags: BTreeMap, } +/// Deserializes a sample rate, rejecting values outside of `0.0..=1.0`. +fn deserialize_sample_rate<'de, D>(deserializer: D) -> std::result::Result +where + D: serde::Deserializer<'de>, +{ + let rate = f32::deserialize(deserializer)?; + if !(0.0..=1.0).contains(&rate) { + return Err(serde::de::Error::custom(format!( + "sample rate must be between 0.0 and 1.0, got {rate}" + ))); + } + Ok(rate) +} + impl Sentry { /// Returns whether Sentry integration is enabled. /// @@ -988,6 +1007,22 @@ mod tests { }); } + #[test] + fn sentry_rejects_out_of_range_sample_rates() { + for (var, value) in [ + ("OS__SENTRY__SAMPLE_RATE", "1.5"), + ("OS__SENTRY__SAMPLE_RATE", "-0.1"), + ("OS__SENTRY__TRACES_SAMPLE_RATE", "1.01"), + ("OS__SENTRY__TRACES_SAMPLE_RATE", "NaN"), + ] { + figment::Jail::expect_with(|jail| { + jail.set_env(var, value); + assert!(Config::load(None).is_err(), "accepted {var}={value}"); + Ok(()) + }); + } + } + #[test] fn encryption_rejects_invalid_configuration() { let mut valid = tempfile::NamedTempFile::new().unwrap(); diff --git a/objectstore-server/src/observability.rs b/objectstore-server/src/observability.rs index dbc0d892..90724918 100644 --- a/objectstore-server/src/observability.rs +++ b/objectstore-server/src/observability.rs @@ -11,6 +11,11 @@ use crate::config::Config; /// The full release name including the objectstore version and SHA. const RELEASE: &str = std::env!("OBJECTSTORE_RELEASE"); +/// Capacity of the Sentry transport's envelope queue. +/// +/// Raised from the SDK default of 30, which dropped events, spans, and logs under load. +const TRANSPORT_CHANNEL_CAPACITY: usize = 100; + /// Initializes the Sentry error-reporting client, if a DSN is configured. /// /// Returns `None` when `config.sentry.dsn` is not set. The returned @@ -18,22 +23,13 @@ const RELEASE: &str = std::env!("OBJECTSTORE_RELEASE"); /// dropping it flushes the event queue and shuts down the Sentry client. pub fn init_sentry(config: &Config) -> Option { let config = &config.sentry; - let dsn = config.dsn.as_ref()?; - - let dsn = match dsn.expose_secret().parse() { - Ok(dsn) => Some(dsn), - Err(error) => { - // Sentry is initialized before the tracing subscriber, so a `warn!` here would be - // dropped. Write to stderr instead to make the misconfiguration visible. - eprintln!("WARN: invalid Sentry DSN, error reporting is disabled: {error}"); - None - } - }; + let dsn = config.dsn.as_ref()?.expose_secret().as_str(); let traces_sample_rate = config.traces_sample_rate; let inherit_sampling_decision = config.inherit_sampling_decision; let mut options = sentry::ClientOptions::new() .release(RELEASE) + .sample_rate(config.sample_rate) .traces_sampler(move |ctx| { if let Some(sampled) = ctx.sampled() && inherit_sampling_decision @@ -44,14 +40,23 @@ pub fn init_sentry(config: &Config) -> Option { } }) .attach_stacktrace(config.attach_stacktrace) - .debug(config.debug); - // Assigned directly rather than via the builder: the `dsn` setter only accepts an unparsed - // string, `environment`/`server_name` setters don't accept `Option`, and the `sample_rate` - // setter panics on out-of-range values while the struct field never did. - options.dsn = dsn; - options.environment = config.environment.clone(); - options.server_name = config.server_name.clone(); - options.event_sampling_strategy = sentry::EventSamplingStrategy::FixedRate(config.sample_rate); + .debug(config.debug) + .transport_channel_capacity(TRANSPORT_CHANNEL_CAPACITY); + + match dsn.parse::() { + Ok(_) => options = options.dsn(dsn), + Err(error) => { + // Sentry is initialized before the tracing subscriber, so a `warn!` here would be + // dropped. Write to stderr instead to make the misconfiguration visible. + eprintln!("WARN: invalid Sentry DSN, error reporting is disabled: {error}"); + } + } + if let Some(environment) = &config.environment { + options = options.environment(environment.clone()); + } + if let Some(server_name) = &config.server_name { + options = options.server_name(server_name.clone()); + } let guard = sentry::init(options); From 68d7cb27778e5a25b69f6c45fae8e79b307fde93 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Fri, 9 Oct 2026 09:14:09 +0000 Subject: [PATCH 3/4] test(server): Remove sample rate validation test --- objectstore-server/src/config.rs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/objectstore-server/src/config.rs b/objectstore-server/src/config.rs index e8fe610d..5e2d78b0 100644 --- a/objectstore-server/src/config.rs +++ b/objectstore-server/src/config.rs @@ -1007,22 +1007,6 @@ mod tests { }); } - #[test] - fn sentry_rejects_out_of_range_sample_rates() { - for (var, value) in [ - ("OS__SENTRY__SAMPLE_RATE", "1.5"), - ("OS__SENTRY__SAMPLE_RATE", "-0.1"), - ("OS__SENTRY__TRACES_SAMPLE_RATE", "1.01"), - ("OS__SENTRY__TRACES_SAMPLE_RATE", "NaN"), - ] { - figment::Jail::expect_with(|jail| { - jail.set_env(var, value); - assert!(Config::load(None).is_err(), "accepted {var}={value}"); - Ok(()) - }); - } - } - #[test] fn encryption_rejects_invalid_configuration() { let mut valid = tempfile::NamedTempFile::new().unwrap(); From 818eae7dc0ac33066eafbc138a4dce6b61412d7e Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Fri, 9 Oct 2026 09:22:23 +0000 Subject: [PATCH 4/4] feat(server): Make Sentry transport channel capacity configurable Co-Authored-By: Lorenzo Cian <17258265+lcian@users.noreply.github.com> --- objectstore-server/src/config.rs | 16 ++++++++++++++++ objectstore-server/src/observability.rs | 7 +------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/objectstore-server/src/config.rs b/objectstore-server/src/config.rs index 5e2d78b0..a87541e5 100644 --- a/objectstore-server/src/config.rs +++ b/objectstore-server/src/config.rs @@ -302,6 +302,21 @@ pub struct Sentry { /// `OS__SENTRY__DEBUG` pub debug: bool, + /// Capacity of the Sentry transport's envelope queue. + /// + /// Maximum number of envelopes (events, transactions, logs) buffered for sending to Sentry. + /// When the queue is full, new envelopes are dropped. Raise this if Sentry data is being + /// dropped under load. + /// + /// # Default + /// + /// `60` + /// + /// # Environment Variable + /// + /// `OS__SENTRY__TRANSPORT_CHANNEL_CAPACITY` + pub transport_channel_capacity: usize, + /// Additional tags to attach to all Sentry events. /// /// Key-value pairs that are sent as tags with every event reported to Sentry. Useful for adding @@ -362,6 +377,7 @@ impl Default for Sentry { inherit_sampling_decision: true, attach_stacktrace: false, debug: false, + transport_channel_capacity: 60, tags: BTreeMap::new(), } } diff --git a/objectstore-server/src/observability.rs b/objectstore-server/src/observability.rs index 90724918..51e891f5 100644 --- a/objectstore-server/src/observability.rs +++ b/objectstore-server/src/observability.rs @@ -11,11 +11,6 @@ use crate::config::Config; /// The full release name including the objectstore version and SHA. const RELEASE: &str = std::env!("OBJECTSTORE_RELEASE"); -/// Capacity of the Sentry transport's envelope queue. -/// -/// Raised from the SDK default of 30, which dropped events, spans, and logs under load. -const TRANSPORT_CHANNEL_CAPACITY: usize = 100; - /// Initializes the Sentry error-reporting client, if a DSN is configured. /// /// Returns `None` when `config.sentry.dsn` is not set. The returned @@ -41,7 +36,7 @@ pub fn init_sentry(config: &Config) -> Option { }) .attach_stacktrace(config.attach_stacktrace) .debug(config.debug) - .transport_channel_capacity(TRANSPORT_CHANNEL_CAPACITY); + .transport_channel_capacity(config.transport_channel_capacity); match dsn.parse::() { Ok(_) => options = options.dsn(dsn),