diff --git a/Cargo.lock b/Cargo.lock index 833c7cd3..1bfdcea7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,7 +18,16 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" dependencies = [ - "gimli", + "gimli 0.26.2", +] + +[[package]] +name = "addr2line" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" +dependencies = [ + "gimli 0.27.1", ] [[package]] @@ -711,6 +720,21 @@ dependencies = [ "syn", ] +[[package]] +name = "backtrace" +version = "0.3.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" +dependencies = [ + "addr2line 0.19.0", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object 0.30.3", + "rustc-demangle", +] + [[package]] name = "base16ct" version = "0.1.1" @@ -1169,7 +1193,7 @@ dependencies = [ "cranelift-codegen-shared", "cranelift-entity", "cranelift-isle", - "gimli", + "gimli 0.26.2", "hashbrown", "log", "regalloc2", @@ -1470,6 +1494,16 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaa37046cc0f6c3cc6090fbdbf73ef0b8ef4cfcc37f6befc0020f63e8cf121e1" +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "serde", + "uuid", +] + [[package]] name = "der" version = "0.6.1" @@ -1988,6 +2022,12 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "gimli" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "221996f774192f0f718773def8201c4ae31f02616a54ccfc2d358bb0e5cefdec" + [[package]] name = "globset" version = "0.4.10" @@ -2745,6 +2785,9 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "rustls", + "sentry", + "sentry-tower", + "sentry-tracing", "serde_json", "serde_yaml", "sqlx", @@ -3444,6 +3487,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "object" +version = "0.30.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.17.0" @@ -3658,6 +3710,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "os_info" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4750134fb6a5d49afc80777394ad5d95b04bc12068c6abb92fae8f43817270f" +dependencies = [ + "log", + "serde", + "winapi", +] + [[package]] name = "os_str_bytes" version = "6.4.1" @@ -4691,6 +4754,116 @@ version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" +[[package]] +name = "sentry" +version = "0.29.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6097dc270a9c4555c5d6222ed243eaa97ff38e29299ed7c5cb36099033c604e" +dependencies = [ + "httpdate", + "reqwest", + "rustls", + "sentry-backtrace", + "sentry-contexts", + "sentry-core", + "sentry-panic", + "sentry-tower", + "tokio", + "ureq", + "webpki-roots", +] + +[[package]] +name = "sentry-backtrace" +version = "0.29.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d92d1e4d591534ae4f872d6142f3b500f4ffc179a6aed8a3e86c7cc96d10a6a" +dependencies = [ + "backtrace", + "once_cell", + "regex", + "sentry-core", +] + +[[package]] +name = "sentry-contexts" +version = "0.29.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3afa877b1898ff67dd9878cf4bec4e53cef7d3be9f14b1fc9e4fcdf36f8e4259" +dependencies = [ + "hostname", + "libc", + "os_info", + "rustc_version", + "sentry-core", + "uname", +] + +[[package]] +name = "sentry-core" +version = "0.29.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc43eb7e4e3a444151a0fe8a0e9ce60eabd905dae33d66e257fa26f1b509c1bd" +dependencies = [ + "once_cell", + "rand 0.8.5", + "sentry-types", + "serde", + "serde_json", +] + +[[package]] +name = "sentry-panic" +version = "0.29.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccab4fab11e3e63c45f4524bee2e75cde39cdf164cb0b0cbe6ccd1948ceddf66" +dependencies = [ + "sentry-backtrace", + "sentry-core", +] + +[[package]] +name = "sentry-tower" +version = "0.29.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "849fbf257de067f41ae1e2f9253538ceaedd79739e79800c4169b463e9160f19" +dependencies = [ + "http", + "pin-project", + "sentry-core", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "sentry-tracing" +version = "0.29.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46ebf58c2bd1d97152c316ff170ee736c59a16967738aeb8ed0d79b80e3713ae" +dependencies = [ + "sentry-core", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "sentry-types" +version = "0.29.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63708ec450b6bdcb657af760c447416d69c38ce421f34e5e2e9ce8118410bc7" +dependencies = [ + "debugid", + "getrandom 0.2.8", + "hex", + "serde", + "serde_json", + "thiserror", + "time 0.3.17", + "url", + "uuid", +] + [[package]] name = "serde" version = "1.0.152" @@ -5681,6 +5854,15 @@ dependencies = [ "uuid", ] +[[package]] +name = "uname" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b72f89f0ca32e4db1c04e2a72f5345d59796d4866a1ee0609084569f73683dc8" +dependencies = [ + "libc", +] + [[package]] name = "uncased" version = "0.9.7" @@ -5816,6 +5998,21 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "ureq" +version = "2.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "338b31dd1314f68f3aabf3ed57ab922df95ffcd902476ca7ba3c4ce7b908c46d" +dependencies = [ + "base64 0.13.1", + "log", + "once_cell", + "rustls", + "url", + "webpki", + "webpki-roots", +] + [[package]] name = "url" version = "2.3.1" @@ -5839,6 +6036,10 @@ name = "uuid" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" +dependencies = [ + "getrandom 0.2.8", + "serde", +] [[package]] name = "valuable" @@ -5986,7 +6187,7 @@ dependencies = [ "indexmap", "libc", "log", - "object", + "object 0.29.0", "once_cell", "paste", "psm", @@ -6064,9 +6265,9 @@ dependencies = [ "cranelift-frontend", "cranelift-native", "cranelift-wasm", - "gimli", + "gimli 0.26.2", "log", - "object", + "object 0.29.0", "target-lexicon", "thiserror", "wasmparser", @@ -6081,10 +6282,10 @@ checksum = "e5a2a5f0fb93aa837a727a48dd1076e8a9f882cc2fee20b433c04a18740ff63b" dependencies = [ "anyhow", "cranelift-entity", - "gimli", + "gimli 0.26.2", "indexmap", "log", - "object", + "object 0.29.0", "serde", "target-lexicon", "thiserror", @@ -6111,14 +6312,14 @@ version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01c78f9fb2922dbb5a95f009539d4badb44866caeeb53d156bf2cf4d683c3afd" dependencies = [ - "addr2line", + "addr2line 0.17.0", "anyhow", "bincode", "cfg-if", "cpp_demangle", - "gimli", + "gimli 0.26.2", "log", - "object", + "object 0.29.0", "rustc-demangle", "serde", "target-lexicon", diff --git a/crates/axum-utils/src/client_authorization.rs b/crates/axum-utils/src/client_authorization.rs index 8dff5cdf..d42e5d76 100644 --- a/crates/axum-utils/src/client_authorization.rs +++ b/crates/axum-utils/src/client_authorization.rs @@ -72,6 +72,17 @@ pub enum Credentials { } impl Credentials { + /// Get the `client_id` of the credentials + #[must_use] + pub fn client_id(&self) -> &str { + match self { + Credentials::None { client_id } + | Credentials::ClientSecretBasic { client_id, .. } + | Credentials::ClientSecretPost { client_id, .. } + | Credentials::ClientAssertionJwtBearer { client_id, .. } => client_id, + } + } + pub async fn fetch( &self, repo: &mut impl RepositoryAccess, @@ -217,6 +228,14 @@ pub struct ClientAuthorization { pub form: Option, } +impl ClientAuthorization { + /// Get the `client_id` from the credentials. + #[must_use] + pub fn client_id(&self) -> &str { + self.credentials.client_id() + } +} + #[derive(Debug)] pub enum ClientAuthorizationError { InvalidHeader, diff --git a/crates/axum-utils/src/fancy_error.rs b/crates/axum-utils/src/fancy_error.rs index 3f238298..ed9cbba4 100644 --- a/crates/axum-utils/src/fancy_error.rs +++ b/crates/axum-utils/src/fancy_error.rs @@ -23,6 +23,23 @@ pub struct FancyError { context: ErrorContext, } +impl std::fmt::Display for FancyError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let code = self.context.code().unwrap_or("Internal error"); + match (self.context.description(), self.context.details()) { + (Some(description), Some(details)) => { + write!(f, "{code}: {description} ({details})") + } + (Some(message), None) | (None, Some(message)) => { + write!(f, "{code}: {message}") + } + (None, None) => { + write!(f, "{code}") + } + } + } +} + impl From for FancyError { fn from(err: E) -> Self { let context = ErrorContext::new() diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index cfd99144..ff02d766 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -39,6 +39,9 @@ opentelemetry-zipkin = { version = "0.16.0", features = ["opentelemetry-http"], opentelemetry-http = { version = "0.7.0", features = ["tokio", "hyper"], optional = true } opentelemetry-prometheus = { version = "0.11.0", optional = true } prometheus = { version = "0.13.3", optional = true } +sentry = { version = "0.29.2", default-features = false, features = ["backtrace", "contexts", "panic", "reqwest", "rustls", "tower"] } +sentry-tracing = "0.29.2" +sentry-tower = { version = "0.29.2", features = ["http"] } mas-config = { path = "../config" } mas-email = { path = "../email" } diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index b0f7af05..7252a78f 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -60,12 +60,17 @@ async fn try_main() -> anyhow::Result<()> { // Don't fill the telemetry layer for now, we want to configure it based on the // app config, so we need to delay that a bit - let (telemetry_layer, handle) = reload::Layer::new(None); + let (telemetry_layer, telemetry_handle) = reload::Layer::new(None); // We only want "INFO" level spans to go through OpenTelemetry let telemetry_layer = telemetry_layer.with_filter(LevelFilter::INFO); + // Don't fill the Sentry layer for now, we want to configure it based on the + // app config, so we need to delay that a bit + let (sentry_layer, sentry_handle) = reload::Layer::new(None); + let subscriber = Registry::default() .with(telemetry_layer) + .with(sentry_layer) .with(filter_layer) .with(fmt_layer); subscriber @@ -88,13 +93,19 @@ async fn try_main() -> anyhow::Result<()> { // Falling back to default. let telemetry_config: TelemetryConfig = opts.load_config().unwrap_or_default(); + // Setup Sentry + let sentry = sentry::init(telemetry_config.sentry.dsn.as_deref()); + if sentry.is_enabled() { + sentry_handle.reload(sentry_tracing::layer())?; + } + // Setup OpenTelemtry tracing and metrics let (tracer, _meter) = telemetry::setup(&telemetry_config) .await .context("failed to setup opentelemetry")?; if let Some(tracer) = tracer { // Now we can swap out the actual opentelemetry tracing layer - handle.reload( + telemetry_handle.reload( tracing_opentelemetry::layer() .with_tracer(tracer) .with_tracked_inactivity(false), diff --git a/crates/cli/src/server.rs b/crates/cli/src/server.rs index fab20f0e..2fe548fa 100644 --- a/crates/cli/src/server.rs +++ b/crates/cli/src/server.rs @@ -31,6 +31,7 @@ use mas_spa::ViteManifestService; use mas_templates::Templates; use opentelemetry::KeyValue; use rustls::ServerConfig; +use sentry_tower::{NewSentryLayer, SentryHttpLayer}; use tower::Layer; use tower_http::{compression::CompressionLayer, services::ServeDir}; @@ -119,6 +120,8 @@ where } router + .layer(SentryHttpLayer::new()) + .layer(NewSentryLayer::new_from_top()) .layer(trace_layer) .layer(CompressionLayer::new()) .with_state(state) diff --git a/crates/config/src/sections/telemetry.rs b/crates/config/src/sections/telemetry.rs index 2c21bd28..cc87850b 100644 --- a/crates/config/src/sections/telemetry.rs +++ b/crates/config/src/sections/telemetry.rs @@ -257,6 +257,19 @@ pub struct MetricsConfig { pub exporter: MetricsExporterConfig, } +fn sentry_dsn_example() -> &'static str { + "https://public@host:port/1" +} + +/// Configuration related to the Sentry integration +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +pub struct SentryConfig { + /// Sentry DSN + #[schemars(url, example = "sentry_dsn_example")] + #[serde(default)] + pub dsn: Option, +} + /// Configuration related to sending monitoring data #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct TelemetryConfig { @@ -267,6 +280,10 @@ pub struct TelemetryConfig { /// Configuration related to exporting metrics #[serde(default)] pub metrics: MetricsConfig, + + /// Configuration related to the Sentry integration + #[serde(default)] + pub sentry: SentryConfig, } #[async_trait] diff --git a/crates/handlers/src/compat/login.rs b/crates/handlers/src/compat/login.rs index 07077ea1..62dc5a43 100644 --- a/crates/handlers/src/compat/login.rs +++ b/crates/handlers/src/compat/login.rs @@ -64,6 +64,7 @@ struct LoginTypes { flows: Vec, } +#[tracing::instrument(name = "handlers.compat.login.get", skip_all)] pub(crate) async fn get() -> impl IntoResponse { let res = LoginTypes { flows: vec![ @@ -190,7 +191,7 @@ impl IntoResponse for RouteError { } } -#[tracing::instrument(skip_all, err)] +#[tracing::instrument(name = "handlers.compat.login.post", skip_all, err)] pub(crate) async fn post( mut rng: BoxRng, clock: BoxClock, diff --git a/crates/handlers/src/compat/login_sso_complete.rs b/crates/handlers/src/compat/login_sso_complete.rs index 54028746..eb286ec7 100644 --- a/crates/handlers/src/compat/login_sso_complete.rs +++ b/crates/handlers/src/compat/login_sso_complete.rs @@ -51,6 +51,12 @@ pub struct Params { action: Option, } +#[tracing::instrument( + name = "handlers.compat.login_sso_complete.get", + fields(compat_sso_login.id = %id), + skip_all, + err, +)] pub async fn get( mut rng: BoxRng, clock: BoxClock, @@ -113,6 +119,12 @@ pub async fn get( Ok((cookie_jar, Html(content)).into_response()) } +#[tracing::instrument( + name = "handlers.compat.login_sso_complete.post", + fields(compat_sso_login.id = %id), + skip_all, + err, +)] pub async fn post( mut rng: BoxRng, clock: BoxClock, diff --git a/crates/handlers/src/compat/login_sso_redirect.rs b/crates/handlers/src/compat/login_sso_redirect.rs index da013cf7..48d0d561 100644 --- a/crates/handlers/src/compat/login_sso_redirect.rs +++ b/crates/handlers/src/compat/login_sso_redirect.rs @@ -55,6 +55,7 @@ impl IntoResponse for RouteError { } } +#[tracing::instrument(name = "handlers.compat.login_sso_redirect.get", skip_all, err)] pub async fn get( mut rng: BoxRng, clock: BoxClock, diff --git a/crates/handlers/src/compat/logout.rs b/crates/handlers/src/compat/logout.rs index 096b22de..0a52eddc 100644 --- a/crates/handlers/src/compat/logout.rs +++ b/crates/handlers/src/compat/logout.rs @@ -65,6 +65,7 @@ impl IntoResponse for RouteError { } } +#[tracing::instrument(name = "handlers.compat.logout.post", skip_all, err)] pub(crate) async fn post( clock: BoxClock, mut repo: BoxRepository, diff --git a/crates/handlers/src/compat/refresh.rs b/crates/handlers/src/compat/refresh.rs index eb970c57..f18a6ecb 100644 --- a/crates/handlers/src/compat/refresh.rs +++ b/crates/handlers/src/compat/refresh.rs @@ -85,6 +85,7 @@ pub struct ResponseBody { expires_in_ms: Duration, } +#[tracing::instrument(name = "handlers.compat.refresh.post", skip_all, err)] pub(crate) async fn post( mut rng: BoxRng, clock: BoxClock, diff --git a/crates/handlers/src/oauth2/authorization/complete.rs b/crates/handlers/src/oauth2/authorization/complete.rs index 6b6869da..100bb32c 100644 --- a/crates/handlers/src/oauth2/authorization/complete.rs +++ b/crates/handlers/src/oauth2/authorization/complete.rs @@ -75,6 +75,12 @@ impl_from_error_for_route!(mas_policy::EvaluationError); impl_from_error_for_route!(super::callback::IntoCallbackDestinationError); impl_from_error_for_route!(super::callback::CallbackDestinationError); +#[tracing::instrument( + name = "handlers.oauth2.authorization_complete.get", + fields(grant.id = %grant_id), + skip_all, + err, +)] pub(crate) async fn get( rng: BoxRng, clock: BoxClock, diff --git a/crates/handlers/src/oauth2/authorization/mod.rs b/crates/handlers/src/oauth2/authorization/mod.rs index 1aa8ce64..f1bbfd06 100644 --- a/crates/handlers/src/oauth2/authorization/mod.rs +++ b/crates/handlers/src/oauth2/authorization/mod.rs @@ -128,6 +128,12 @@ fn resolve_response_mode( } } +#[tracing::instrument( + name = "handlers.oauth2.authorization.get", + fields(client.id = %params.auth.client_id), + skip_all, + err, +)] #[allow(clippy::too_many_lines)] pub(crate) async fn get( mut rng: BoxRng, diff --git a/crates/handlers/src/oauth2/consent.rs b/crates/handlers/src/oauth2/consent.rs index ffaa6106..8fd9bf7b 100644 --- a/crates/handlers/src/oauth2/consent.rs +++ b/crates/handlers/src/oauth2/consent.rs @@ -71,6 +71,12 @@ impl IntoResponse for RouteError { } } +#[tracing::instrument( + name = "handlers.oauth2.consent.get", + fields(grant.id = %grant_id), + skip_all, + err, +)] pub(crate) async fn get( mut rng: BoxRng, clock: BoxClock, @@ -125,6 +131,12 @@ pub(crate) async fn get( } } +#[tracing::instrument( + name = "handlers.oauth2.consent.post", + fields(grant.id = %grant_id), + skip_all, + err, +)] pub(crate) async fn post( mut rng: BoxRng, clock: BoxClock, diff --git a/crates/handlers/src/oauth2/discovery.rs b/crates/handlers/src/oauth2/discovery.rs index a79b5c45..719f0595 100644 --- a/crates/handlers/src/oauth2/discovery.rs +++ b/crates/handlers/src/oauth2/discovery.rs @@ -26,6 +26,7 @@ use oauth2_types::{ scope, }; +#[tracing::instrument(name = "handlers.oauth2.discovery.get", skip_all)] #[allow(clippy::too_many_lines)] pub(crate) async fn get( State(key_store): State, diff --git a/crates/handlers/src/oauth2/introspection.rs b/crates/handlers/src/oauth2/introspection.rs index 3b44c511..44a0ad05 100644 --- a/crates/handlers/src/oauth2/introspection.rs +++ b/crates/handlers/src/oauth2/introspection.rs @@ -120,6 +120,12 @@ const INACTIVE: IntrospectionResponse = IntrospectionResponse { const API_SCOPE: ScopeToken = ScopeToken::from_static("urn:matrix:org.matrix.msc2967.client:api:*"); +#[tracing::instrument( + name = "handlers.oauth2.introspection.post", + fields(client.id = client_authorization.client_id()), + skip_all, + err, +)] #[allow(clippy::too_many_lines)] pub(crate) async fn post( clock: BoxClock, diff --git a/crates/handlers/src/oauth2/keys.rs b/crates/handlers/src/oauth2/keys.rs index 68d70f97..31e6374d 100644 --- a/crates/handlers/src/oauth2/keys.rs +++ b/crates/handlers/src/oauth2/keys.rs @@ -15,6 +15,7 @@ use axum::{extract::State, response::IntoResponse, Json}; use mas_keystore::Keystore; +#[tracing::instrument(name = "handlers.oauth2.keys.get", skip_all)] pub(crate) async fn get(State(key_store): State) -> impl IntoResponse { let jwks = key_store.public_jwks(); Json(jwks) diff --git a/crates/handlers/src/oauth2/registration.rs b/crates/handlers/src/oauth2/registration.rs index 650a19ab..35fdf3e1 100644 --- a/crates/handlers/src/oauth2/registration.rs +++ b/crates/handlers/src/oauth2/registration.rs @@ -103,7 +103,7 @@ impl IntoResponse for RouteError { } } -#[tracing::instrument(skip_all, err)] +#[tracing::instrument(name = "handlers.oauth2.registration.post", skip_all, err)] pub(crate) async fn post( mut rng: BoxRng, clock: BoxClock, diff --git a/crates/handlers/src/oauth2/token.rs b/crates/handlers/src/oauth2/token.rs index 682813bf..5fddcea9 100644 --- a/crates/handlers/src/oauth2/token.rs +++ b/crates/handlers/src/oauth2/token.rs @@ -155,7 +155,12 @@ impl_from_error_for_route!(mas_jose::claims::ClaimError); impl_from_error_for_route!(mas_jose::claims::TokenHashError); impl_from_error_for_route!(mas_jose::jwt::JwtSignatureError); -#[tracing::instrument(skip_all, err)] +#[tracing::instrument( + name = "handlers.oauth2.token.post", + fields(client.id = client_authorization.client_id()), + skip_all, + err, +)] pub(crate) async fn post( mut rng: BoxRng, clock: BoxClock, diff --git a/crates/handlers/src/oauth2/userinfo.rs b/crates/handlers/src/oauth2/userinfo.rs index d2d27cd0..8394d16c 100644 --- a/crates/handlers/src/oauth2/userinfo.rs +++ b/crates/handlers/src/oauth2/userinfo.rs @@ -95,6 +95,7 @@ impl IntoResponse for RouteError { } } +#[tracing::instrument(name = "handlers.oauth2.userinfo.get", skip_all, err)] pub async fn get( mut rng: BoxRng, clock: BoxClock, diff --git a/crates/handlers/src/oauth2/webfinger.rs b/crates/handlers/src/oauth2/webfinger.rs index fb6b8dee..7f925d15 100644 --- a/crates/handlers/src/oauth2/webfinger.rs +++ b/crates/handlers/src/oauth2/webfinger.rs @@ -35,6 +35,7 @@ fn jrd() -> mime::Mime { "application/jrd+json".parse().unwrap() } +#[tracing::instrument(name = "handlers.oauth2.webfinger.get", skip_all)] pub(crate) async fn get( Query(params): Query, State(url_builder): State, diff --git a/crates/handlers/src/upstream_oauth2/authorize.rs b/crates/handlers/src/upstream_oauth2/authorize.rs index 8da6231a..b56bae88 100644 --- a/crates/handlers/src/upstream_oauth2/authorize.rs +++ b/crates/handlers/src/upstream_oauth2/authorize.rs @@ -55,6 +55,12 @@ impl IntoResponse for RouteError { } } +#[tracing::instrument( + name = "handlers.upstream_oauth2.authorize.get", + fields(upstream_oauth_provider.id = %provider_id), + skip_all, + err, +)] pub(crate) async fn get( mut rng: BoxRng, clock: BoxClock, diff --git a/crates/handlers/src/upstream_oauth2/callback.rs b/crates/handlers/src/upstream_oauth2/callback.rs index bc24c399..03846dfb 100644 --- a/crates/handlers/src/upstream_oauth2/callback.rs +++ b/crates/handlers/src/upstream_oauth2/callback.rs @@ -117,6 +117,12 @@ impl IntoResponse for RouteError { } } +#[tracing::instrument( + name = "handlers.upstream_oauth2.callback.get", + fields(upstream_oauth_provider.id = %provider_id), + skip_all, + err, +)] #[allow(clippy::too_many_lines, clippy::too_many_arguments)] pub(crate) async fn get( mut rng: BoxRng, diff --git a/crates/handlers/src/upstream_oauth2/link.rs b/crates/handlers/src/upstream_oauth2/link.rs index 30d678cf..2959517d 100644 --- a/crates/handlers/src/upstream_oauth2/link.rs +++ b/crates/handlers/src/upstream_oauth2/link.rs @@ -91,6 +91,12 @@ pub(crate) enum FormData { Login, } +#[tracing::instrument( + name = "handlers.upstream_oauth2.link.get", + fields(upstream_oauth_link.id = %link_id), + skip_all, + err, +)] pub(crate) async fn get( mut rng: BoxRng, clock: BoxClock, @@ -207,6 +213,12 @@ pub(crate) async fn get( Ok((cookie_jar, Html(render))) } +#[tracing::instrument( + name = "handlers.upstream_oauth2.link.post", + fields(upstream_oauth_link.id = %link_id), + skip_all, + err, +)] pub(crate) async fn post( mut rng: BoxRng, clock: BoxClock, diff --git a/crates/handlers/src/views/account/emails/add.rs b/crates/handlers/src/views/account/emails/add.rs index e26c9cc1..13a47fac 100644 --- a/crates/handlers/src/views/account/emails/add.rs +++ b/crates/handlers/src/views/account/emails/add.rs @@ -36,6 +36,7 @@ pub struct EmailForm { email: String, } +#[tracing::instrument(name = "handlers.views.account_email_add.get", skip_all, err)] pub(crate) async fn get( mut rng: BoxRng, clock: BoxClock, @@ -64,6 +65,7 @@ pub(crate) async fn get( Ok((cookie_jar, Html(content)).into_response()) } +#[tracing::instrument(name = "handlers.views.account_email_add.post", skip_all, err)] pub(crate) async fn post( mut rng: BoxRng, clock: BoxClock, diff --git a/crates/handlers/src/views/account/emails/mod.rs b/crates/handlers/src/views/account/emails/mod.rs index ad997e0d..276cba63 100644 --- a/crates/handlers/src/views/account/emails/mod.rs +++ b/crates/handlers/src/views/account/emails/mod.rs @@ -48,6 +48,7 @@ pub enum ManagementForm { Remove { id: String }, } +#[tracing::instrument(name = "handlers.views.account_email_list.get", skip_all, err)] pub(crate) async fn get( mut rng: BoxRng, clock: BoxClock, @@ -121,6 +122,7 @@ async fn start_email_verification( Ok(()) } +#[tracing::instrument(name = "handlers.views.account_email_list.post", skip_all, err)] pub(crate) async fn post( mut rng: BoxRng, clock: BoxClock, diff --git a/crates/handlers/src/views/account/emails/verify.rs b/crates/handlers/src/views/account/emails/verify.rs index d7f074b8..3173d8ee 100644 --- a/crates/handlers/src/views/account/emails/verify.rs +++ b/crates/handlers/src/views/account/emails/verify.rs @@ -36,6 +36,12 @@ pub struct CodeForm { code: String, } +#[tracing::instrument( + name = "handlers.views.account_email_verify.get", + fields(user_email.id = %id), + skip_all, + err, +)] pub(crate) async fn get( mut rng: BoxRng, clock: BoxClock, @@ -79,6 +85,12 @@ pub(crate) async fn get( Ok((cookie_jar, Html(content)).into_response()) } +#[tracing::instrument( + name = "handlers.views.account_email_verify.post", + fields(user_email.id = %id), + skip_all, + err, +)] pub(crate) async fn post( clock: BoxClock, mut repo: BoxRepository, diff --git a/crates/handlers/src/views/account/mod.rs b/crates/handlers/src/views/account/mod.rs index 162b7899..aaffd99e 100644 --- a/crates/handlers/src/views/account/mod.rs +++ b/crates/handlers/src/views/account/mod.rs @@ -29,6 +29,7 @@ use mas_storage::{ }; use mas_templates::{AccountContext, TemplateContext, Templates}; +#[tracing::instrument(name = "handlers.views.account.get", skip_all, err)] pub(crate) async fn get( mut rng: BoxRng, clock: BoxClock, diff --git a/crates/handlers/src/views/account/password.rs b/crates/handlers/src/views/account/password.rs index d9e02610..2786e4bb 100644 --- a/crates/handlers/src/views/account/password.rs +++ b/crates/handlers/src/views/account/password.rs @@ -43,6 +43,7 @@ pub struct ChangeForm { new_password_confirm: String, } +#[tracing::instrument(name = "handlers.views.account_password.get", skip_all, err)] pub(crate) async fn get( mut rng: BoxRng, clock: BoxClock, @@ -80,6 +81,7 @@ async fn render( Ok((cookie_jar, Html(content)).into_response()) } +#[tracing::instrument(name = "handlers.views.account_password.post", skip_all, err)] pub(crate) async fn post( mut rng: BoxRng, clock: BoxClock, diff --git a/crates/handlers/src/views/index.rs b/crates/handlers/src/views/index.rs index 7b4be7df..4185f004 100644 --- a/crates/handlers/src/views/index.rs +++ b/crates/handlers/src/views/index.rs @@ -23,6 +23,7 @@ use mas_router::UrlBuilder; use mas_storage::{BoxClock, BoxRepository, BoxRng}; use mas_templates::{IndexContext, TemplateContext, Templates}; +#[tracing::instrument(name = "handlers.views.index.get", skip_all, err)] pub async fn get( mut rng: BoxRng, clock: BoxClock, diff --git a/crates/handlers/src/views/login.rs b/crates/handlers/src/views/login.rs index 3083eae0..853e5e05 100644 --- a/crates/handlers/src/views/login.rs +++ b/crates/handlers/src/views/login.rs @@ -48,6 +48,7 @@ impl ToFormState for LoginForm { type Field = LoginFormField; } +#[tracing::instrument(name = "handlers.views.login.get", skip_all, err)] pub(crate) async fn get( mut rng: BoxRng, clock: BoxClock, @@ -79,6 +80,7 @@ pub(crate) async fn get( } } +#[tracing::instrument(name = "handlers.views.login.post", skip_all, err)] pub(crate) async fn post( mut rng: BoxRng, clock: BoxClock, diff --git a/crates/handlers/src/views/logout.rs b/crates/handlers/src/views/logout.rs index 9b0f3602..35dc731e 100644 --- a/crates/handlers/src/views/logout.rs +++ b/crates/handlers/src/views/logout.rs @@ -22,6 +22,7 @@ use mas_keystore::Encrypter; use mas_router::{PostAuthAction, Route}; use mas_storage::{user::BrowserSessionRepository, BoxClock, BoxRepository}; +#[tracing::instrument(name = "handlers.views.logout.post", skip_all, err)] pub(crate) async fn post( clock: BoxClock, mut repo: BoxRepository, diff --git a/crates/handlers/src/views/reauth.rs b/crates/handlers/src/views/reauth.rs index 12f205d6..0ad81a81 100644 --- a/crates/handlers/src/views/reauth.rs +++ b/crates/handlers/src/views/reauth.rs @@ -40,6 +40,7 @@ pub(crate) struct ReauthForm { password: String, } +#[tracing::instrument(name = "handlers.views.reauth.get", skip_all, err)] pub(crate) async fn get( mut rng: BoxRng, clock: BoxClock, @@ -76,6 +77,7 @@ pub(crate) async fn get( Ok((cookie_jar, Html(content)).into_response()) } +#[tracing::instrument(name = "handlers.views.reauth.post", skip_all, err)] pub(crate) async fn post( mut rng: BoxRng, clock: BoxClock, diff --git a/crates/handlers/src/views/register.rs b/crates/handlers/src/views/register.rs index 64e30af7..53cc959a 100644 --- a/crates/handlers/src/views/register.rs +++ b/crates/handlers/src/views/register.rs @@ -58,6 +58,7 @@ impl ToFormState for RegisterForm { type Field = RegisterFormField; } +#[tracing::instrument(name = "handlers.views.register.get", skip_all, err)] pub(crate) async fn get( mut rng: BoxRng, clock: BoxClock, @@ -88,6 +89,7 @@ pub(crate) async fn get( } } +#[tracing::instrument(name = "handlers.views.register.post", skip_all, err)] #[allow(clippy::too_many_lines, clippy::too_many_arguments)] pub(crate) async fn post( mut rng: BoxRng, diff --git a/crates/templates/src/context.rs b/crates/templates/src/context.rs index cd70289b..90110a30 100644 --- a/crates/templates/src/context.rs +++ b/crates/templates/src/context.rs @@ -912,10 +912,27 @@ impl ErrorContext { } /// Add the error details to the context - #[allow(dead_code)] #[must_use] pub fn with_details(mut self, details: String) -> Self { self.details = Some(details); self } + + /// Get the error code, if any + #[must_use] + pub fn code(&self) -> Option<&'static str> { + self.code + } + + /// Get the description, if any + #[must_use] + pub fn description(&self) -> Option<&str> { + self.description.as_deref() + } + + /// Get the details, if any + #[must_use] + pub fn details(&self) -> Option<&str> { + self.details.as_deref() + } } diff --git a/docs/config.schema.json b/docs/config.schema.json index 42582e88..d4dbf185 100644 --- a/docs/config.schema.json +++ b/docs/config.schema.json @@ -173,6 +173,9 @@ "metrics": { "exporter": "none" }, + "sentry": { + "dsn": null + }, "tracing": { "exporter": "none", "propagators": [] @@ -1578,6 +1581,21 @@ } } }, + "SentryConfig": { + "description": "Configuration related to the Sentry integration", + "type": "object", + "properties": { + "dsn": { + "description": "Sentry DSN", + "default": null, + "examples": [ + "https://public@host:port/1" + ], + "type": "string", + "format": "uri" + } + } + }, "TelemetryConfig": { "description": "Configuration related to sending monitoring data", "type": "object", @@ -1593,6 +1611,17 @@ } ] }, + "sentry": { + "description": "Configuration related to the Sentry integration", + "default": { + "dsn": null + }, + "allOf": [ + { + "$ref": "#/definitions/SentryConfig" + } + ] + }, "tracing": { "description": "Configuration related to exporting traces", "default": {