From c8474511a701b033b359decc3260fb822b38e19e Mon Sep 17 00:00:00 2001 From: Benjamin Bouvier Date: Thu, 20 Mar 2025 17:33:30 +0100 Subject: [PATCH] feat(ffi): add support for sentry logging --- Cargo.lock | 172 ++++++++++++++++++++++++ Cargo.toml | 2 + bindings/matrix-sdk-ffi/CHANGELOG.md | 2 + bindings/matrix-sdk-ffi/Cargo.toml | 2 + bindings/matrix-sdk-ffi/src/platform.rs | 155 ++++++++++++++++++--- 5 files changed, 311 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 565630f40..e798d2f7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1207,6 +1207,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" dependencies = [ + "serde", "uuid", ] @@ -2060,6 +2061,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hkdf" version = "0.12.4" @@ -2078,6 +2085,17 @@ dependencies = [ "digest", ] +[[package]] +name = "hostname" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba" +dependencies = [ + "cfg-if", + "libc", + "windows", +] + [[package]] name = "html5ever" version = "0.29.0" @@ -3096,6 +3114,8 @@ dependencies = [ "once_cell", "paranoid-android", "ruma", + "sentry", + "sentry-tracing", "serde", "serde_json", "thiserror 2.0.11", @@ -3623,6 +3643,17 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "os_info" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a604e53c24761286860eba4e2c8b23a0161526476b1de520139d69cdb85a6b5" +dependencies = [ + "log", + "serde", + "windows-sys 0.52.0", +] + [[package]] name = "overload" version = "0.1.1" @@ -4301,6 +4332,7 @@ dependencies = [ "async-compression", "base64", "bytes", + "futures-channel", "futures-core", "futures-util", "h2", @@ -4744,6 +4776,114 @@ dependencies = [ "serde", ] +[[package]] +name = "sentry" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a7332159e544e34db06b251b1eda5e546bd90285c3f58d9c8ff8450b484e0da" +dependencies = [ + "httpdate", + "native-tls", + "reqwest", + "sentry-backtrace", + "sentry-contexts", + "sentry-core", + "sentry-debug-images", + "sentry-panic", + "sentry-tracing", + "tokio", + "ureq", +] + +[[package]] +name = "sentry-backtrace" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "565ec31ad37bab8e6d9f289f34913ed8768347b133706192f10606dabd5c6bc4" +dependencies = [ + "backtrace", + "once_cell", + "regex", + "sentry-core", +] + +[[package]] +name = "sentry-contexts" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e860275f25f27e8c0c7726ce116c7d5c928c5bba2ee73306e52b20a752298ea6" +dependencies = [ + "hostname", + "libc", + "os_info", + "rustc_version", + "sentry-core", + "uname", +] + +[[package]] +name = "sentry-core" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "653942e6141f16651273159f4b8b1eaeedf37a7554c00cd798953e64b8a9bf72" +dependencies = [ + "once_cell", + "rand", + "sentry-types", + "serde", + "serde_json", +] + +[[package]] +name = "sentry-debug-images" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60bc2154e6df59beed0ac13d58f8dfaf5ad20a88548a53e29e4d92e8e835c2" +dependencies = [ + "findshlibs", + "once_cell", + "sentry-core", +] + +[[package]] +name = "sentry-panic" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "105e3a956c8aa9dab1e4087b1657b03271bfc49d838c6ae9bfc7c58c802fd0ef" +dependencies = [ + "sentry-backtrace", + "sentry-core", +] + +[[package]] +name = "sentry-tracing" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e75c831b4d8b34a5aec1f65f67c5d46a26c7c5d3c7abd8b5ef430796900cf8" +dependencies = [ + "sentry-backtrace", + "sentry-core", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "sentry-types" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d4203359e60724aa05cf2385aaf5d4f147e837185d7dd2b9ccf1ee77f4420c8" +dependencies = [ + "debugid", + "hex", + "rand", + "serde", + "serde_json", + "thiserror 1.0.63", + "time", + "url", + "uuid", +] + [[package]] name = "serde" version = "1.0.217" @@ -5613,6 +5753,15 @@ dependencies = [ "web-time", ] +[[package]] +name = "uname" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b72f89f0ca32e4db1c04e2a72f5345d59796d4866a1ee0609084569f73683dc8" +dependencies = [ + "libc", +] + [[package]] name = "unarray" version = "0.1.4" @@ -5827,6 +5976,19 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" +dependencies = [ + "base64", + "log", + "native-tls", + "once_cell", + "url", +] + [[package]] name = "url" version = "2.5.4" @@ -6153,6 +6315,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core", + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index 935bdc60e..f2238ee0d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,6 +78,8 @@ ruma = { git = "https://github.com/ruma/ruma", rev = "a8fd1b0322649bf59e2a5cfc73 "unstable-msc4278", ] } ruma-common = { git = "https://github.com/ruma/ruma", rev = "a8fd1b0322649bf59e2a5cfc73ab4fe46b21edd7" } +sentry = "0.36.0" +sentry-tracing = "0.36.0" serde = "1.0.217" serde_html_form = "0.2.7" serde_json = "1.0.138" diff --git a/bindings/matrix-sdk-ffi/CHANGELOG.md b/bindings/matrix-sdk-ffi/CHANGELOG.md index 8a41d0769..075c4c9ac 100644 --- a/bindings/matrix-sdk-ffi/CHANGELOG.md +++ b/bindings/matrix-sdk-ffi/CHANGELOG.md @@ -19,6 +19,8 @@ Breaking changes: Additions: +- Support for adding a Sentry layer to the FFI bindings has been added. Only `tracing` statements with + the field `sentry=true` will be forwarded to Sentry, in addition to default Sentry filters. - Add room topic string to `StateEventContent` - Add `UploadSource` for representing upload data - this is analogous to `matrix_sdk_ui::timeline::AttachmentSource` - Add `Client::observe_account_data_event` and `Client::observe_room_account_data_event` to diff --git a/bindings/matrix-sdk-ffi/Cargo.toml b/bindings/matrix-sdk-ffi/Cargo.toml index 6b8f9d51f..5e1fa5f08 100644 --- a/bindings/matrix-sdk-ffi/Cargo.toml +++ b/bindings/matrix-sdk-ffi/Cargo.toml @@ -34,6 +34,8 @@ matrix-sdk-ui = { workspace = true, features = ["uniffi"] } mime = "0.3.16" once_cell = { workspace = true } ruma = { workspace = true, features = ["html", "unstable-unspecified", "unstable-msc3488", "compat-unset-avatar", "unstable-msc3245-v1-compat", "unstable-msc4278"] } +sentry = { workspace = true } +sentry-tracing = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } thiserror = { workspace = true } diff --git a/bindings/matrix-sdk-ffi/src/platform.rs b/bindings/matrix-sdk-ffi/src/platform.rs index 4da3859fb..bf36b0c24 100644 --- a/bindings/matrix-sdk-ffi/src/platform.rs +++ b/bindings/matrix-sdk-ffi/src/platform.rs @@ -1,3 +1,6 @@ +use std::sync::{atomic::AtomicBool, Arc, OnceLock}; + +use tracing::warn; use tracing_appender::rolling::{RollingFileAppender, Rotation}; use tracing_core::Subscriber; use tracing_subscriber::{ @@ -8,19 +11,13 @@ use tracing_subscriber::{ time::FormatTime, FormatEvent, FormatFields, FormattedFields, }, - layer::SubscriberExt, + layer::SubscriberExt as _, registry::LookupSpan, - util::SubscriberInitExt, - EnvFilter, Layer, + util::SubscriberInitExt as _, + Layer, }; -use crate::tracing::LogLevel; - -pub fn log_panics() { - std::env::set_var("RUST_BACKTRACE", "1"); - - log_panics::init(); -} +use crate::{error::ClientError, tracing::LogLevel}; fn text_layers(config: TracingConfiguration) -> impl Layer where @@ -343,6 +340,20 @@ impl TraceLogPacks { } } +struct SentryLoggingCtx { + /// The Sentry client guard, which keeps the Sentry context alive. + _guard: sentry::ClientInitGuard, + + /// Whether the Sentry layer is enabled or not, at a global level. + enabled: Arc, +} + +struct LoggingCtx { + sentry: Option, +} + +static LOGGING: OnceLock = OnceLock::new(); + #[derive(uniffi::Record)] pub struct TracingConfiguration { /// The desired log level. @@ -363,6 +374,89 @@ pub struct TracingConfiguration { /// If set, configures rotated log files where to write additional logs. write_to_files: Option, + + /// If set, the Sentry DSN to use for error reporting. + sentry_dsn: Option, +} + +impl TracingConfiguration { + /// Sets up the tracing configuration and return a [`Logger`] instance + /// holding onto it. + fn build(mut self) -> LoggingCtx { + // Show full backtraces, if we run into panics. + std::env::set_var("RUST_BACKTRACE", "1"); + + // Log panics. + log_panics::init(); + + // Prepare the Sentry layer, if a DSN is provided. + let (sentry_layer, sentry_logging_ctx) = if let Some(sentry_dsn) = self.sentry_dsn.take() { + // Initialize the Sentry client with the given options. + let sentry_guard = sentry::init(( + sentry_dsn, + sentry::ClientOptions { + traces_sample_rate: 0.0, + attach_stacktrace: true, + ..sentry::ClientOptions::default() + }, + )); + + let sentry_enabled = Arc::new(AtomicBool::new(true)); + + // Add a Sentry layer to the tracing subscriber. + // + // Pass custom event and span filters, which will ignore anything, if the Sentry + // support has been globally disabled, or if the statement doesn't include a + // `sentry` field set to `true`. + let sentry_layer = sentry_tracing::layer() + .event_filter({ + let enabled = sentry_enabled.clone(); + + move |metadata| { + if enabled.load(std::sync::atomic::Ordering::SeqCst) + && metadata.fields().field("sentry").is_some() + { + sentry_tracing::default_event_filter(metadata) + } else { + // Ignore the event. + sentry_tracing::EventFilter::Ignore + } + } + }) + .span_filter({ + let enabled = sentry_enabled.clone(); + + move |metadata| { + if enabled.load(std::sync::atomic::Ordering::SeqCst) { + sentry_tracing::default_span_filter(metadata) + } else { + // Ignore, if sentry is globally disabled. + false + } + } + }); + + ( + Some(sentry_layer), + Some(SentryLoggingCtx { _guard: sentry_guard, enabled: sentry_enabled }), + ) + } else { + (None, None) + }; + + let env_filter = build_tracing_filter(&self); + + tracing_subscriber::registry() + .with(tracing_subscriber::EnvFilter::new(&env_filter)) + .with(crate::platform::text_layers(self)) + .with(sentry_layer) + .init(); + + // Log the log levels 🧠. + tracing::info!(env_filter, "Logging has been set up"); + + LoggingCtx { sentry: sentry_logging_ctx } + } } fn build_tracing_filter(config: &TracingConfiguration) -> String { @@ -407,24 +501,38 @@ fn build_tracing_filter(config: &TracingConfiguration) -> String { /// the NSE process on iOS). Otherwise, this can remain false, in which case a /// multithreaded tokio runtime will be set up. #[matrix_sdk_ffi_macros::export] -pub fn init_platform(config: TracingConfiguration, use_lightweight_tokio_runtime: bool) { - log_panics(); - - let env_filter = build_tracing_filter(&config); - - tracing_subscriber::registry() - .with(EnvFilter::new(&env_filter)) - .with(text_layers(config)) - .init(); - - // Log the log levels 🧠. - tracing::info!(env_filter, "Logging has been set up"); +pub fn init_platform( + config: TracingConfiguration, + use_lightweight_tokio_runtime: bool, +) -> Result<(), ClientError> { + LOGGING.set(config.build()).map_err(|_| ClientError::Generic { + msg: "logger already initialized".to_owned(), + details: None, + })?; if use_lightweight_tokio_runtime { setup_lightweight_tokio_runtime(); } else { setup_multithreaded_tokio_runtime(); } + + Ok(()) +} + +/// Set the global enablement level for the Sentry layer (after the logs have +/// been set up). +#[matrix_sdk_ffi_macros::export] +pub fn enable_sentry_logging(enabled: bool) { + if let Some(ctx) = LOGGING.get() { + if let Some(sentry_ctx) = &ctx.sentry { + sentry_ctx.enabled.store(enabled, std::sync::atomic::Ordering::SeqCst); + } else { + warn!("Sentry logging is not enabled"); + } + } else { + // Can't use log statements here, since logging hasn't been enabled yet 🧠 + eprintln!("Logging hasn't been enabled yet"); + }; } fn setup_multithreaded_tokio_runtime() { @@ -479,6 +587,7 @@ mod tests { extra_targets: vec!["super_duper_app".to_owned()], write_to_stdout_or_system: true, write_to_files: None, + sentry_dsn: None, }; let filter = build_tracing_filter(&config); @@ -515,6 +624,7 @@ mod tests { extra_targets: vec!["super_duper_app".to_owned(), "some_other_span".to_owned()], write_to_stdout_or_system: true, write_to_files: None, + sentry_dsn: None, }; let filter = build_tracing_filter(&config); @@ -552,6 +662,7 @@ mod tests { extra_targets: vec!["super_duper_app".to_owned()], write_to_stdout_or_system: true, write_to_files: None, + sentry_dsn: None, }; let filter = build_tracing_filter(&config);