diff --git a/.env b/.env index fcdc44d6..c9da62cb 100644 --- a/.env +++ b/.env @@ -14,3 +14,4 @@ SMTP_PORT="1025" # SMTP_LOGIN="" # SMTP_PASSWORD="" SMTP_TLSTYPE="none" +RUST_LOG="info,actix_server=error" diff --git a/Cargo.lock b/Cargo.lock index 70a9a2f0..b88763b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -818,6 +818,10 @@ dependencies = [ "sqlx", "static-files", "thiserror", + "tracing", + "tracing-actix-web", + "tracing-panic", + "tracing-subscriber", "zxcvbn", ] @@ -1432,8 +1436,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "531e46835a22af56d1e3b66f04844bed63158bc094a628bec1d321d9b4c44bf2" dependencies = [ "bit-set", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", ] [[package]] @@ -2248,6 +2252,15 @@ dependencies = [ "value-bag", ] +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + [[package]] name = "maud" version = "0.27.0" @@ -2323,6 +2336,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "mutually_exclusive_features" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94e1e6445d314f972ff7395df2de295fe51b71821694f0b0e1e79c4f12c8577" + [[package]] name = "nom" version = "8.0.0" @@ -2332,6 +2351,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-bigint-dig" version = "0.8.4" @@ -2406,6 +2435,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "parking" version = "2.2.1" @@ -2747,8 +2782,17 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", ] [[package]] @@ -2759,7 +2803,7 @@ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.5", ] [[package]] @@ -2768,6 +2812,12 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.8.5" @@ -2991,6 +3041,15 @@ dependencies = [ "digest", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -3399,6 +3458,16 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "time" version = "0.3.41" @@ -3497,6 +3566,19 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-actix-web" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2340b7722695166c7fc9b3e3cd1166e7c74fedb9075b8f0c74d3822d2e41caf5" +dependencies = [ + "actix-web", + "mutually_exclusive_features", + "pin-project", + "tracing", + "uuid", +] + [[package]] name = "tracing-attributes" version = "0.1.28" @@ -3515,6 +3597,46 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-panic" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bf1298a179837099f9309243af3b554e840f7f67f65e9f55294913299bd4cc5" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", ] [[package]] @@ -3607,12 +3729,27 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" +dependencies = [ + "getrandom 0.3.2", +] + [[package]] name = "v_htmlescape" version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e8257fbc510f0a46eb602c10215901938b5c2a7d5e70fc11483b1d3c9b5b18c" +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "value-bag" version = "1.11.1" diff --git a/web/Cargo.toml b/web/Cargo.toml index 88a2f436..6568e964 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -32,6 +32,10 @@ actix-http = "3.9.0" askama = "0.13.0" garde = { version = "0.22.0", features = ["derive", "email"] } maud = "0.27.0" +tracing-actix-web = "0.7.18" +tracing = "0.1.41" +tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } +tracing-panic = "0.1.2" [build-dependencies] built = "0.7.4" diff --git a/web/src/main.rs b/web/src/main.rs index 7c6a78b4..53134309 100644 --- a/web/src/main.rs +++ b/web/src/main.rs @@ -13,15 +13,21 @@ use chrono::NaiveTime; use mail::Mailer; use sqlx::postgres::PgPool; use sqlx::{Pool, Postgres}; +use tracing::info; +use tracing_actix_web::TracingLogger; +use tracing_panic::panic_hook; +use tracing_subscriber::layer::SubscriberExt; +use tracing_subscriber::util::SubscriberInitExt; +use tracing_subscriber::{fmt, EnvFilter}; use crate::postgres_session_store::SqlxPostgresqlSessionStore; use crate::utils::manage_commands::{handle_command, parse_args}; mod endpoints; +mod mail; mod middleware; mod models; mod utils; -mod mail; mod filters; mod postgres_session_store; @@ -37,6 +43,8 @@ async fn main() -> anyhow::Result<()> { let env = get_env()?; let config: Config = load_config(&env)?; + init_tracing(); + let args = parse_args()?; let pool = PgPool::connect(&config.database_url).await?; @@ -46,7 +54,7 @@ async fn main() -> anyhow::Result<()> { let address = config.server_address; let port = config.server_port; - println!("Starting server on http://{address}:{port}.",); + info!("Starting server on http://{address}:{port}."); HttpServer::new(move || create_app(config.clone(), pool.clone(), mailer.clone())) .bind((address, port))? @@ -77,6 +85,8 @@ pub fn create_app( .app_data(web::Data::new(pool)) .app_data(web::Data::new(mailer)) .configure(endpoints::init) + .wrap(middleware::ErrorAppender) + .wrap(TracingLogger::default()) .wrap(middleware::RedirectToLogin) .wrap(middleware::LoadCurrentUser) .wrap( @@ -87,3 +97,16 @@ pub fn create_app( .wrap(SessionMiddleware::new(store.clone(), secret_key.clone())) .service(ResourceFiles::new("/static", generated)) } + +fn init_tracing() { + let filter = EnvFilter::try_from_default_env() + .or_else(|_| EnvFilter::try_new("info")) + .unwrap(); + + tracing_subscriber::registry() + .with(fmt::layer()) + .with(filter) + .init(); + + std::panic::set_hook(Box::new(panic_hook)); +} diff --git a/web/src/middleware/append_request_id_to_error.rs b/web/src/middleware/append_request_id_to_error.rs new file mode 100644 index 00000000..677c12be --- /dev/null +++ b/web/src/middleware/append_request_id_to_error.rs @@ -0,0 +1,77 @@ +use std::{ + future::{ready, Ready}, + str::from_utf8, +}; + +use actix_web::{ + body::BoxBody, + dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform}, + Error, HttpMessage, +}; +use futures_util::future::LocalBoxFuture; +use tracing_actix_web::RequestId; + +pub struct ErrorAppender; + +impl Transform for ErrorAppender +where + S: Service, + S::Future: 'static, +{ + type Response = ServiceResponse; + type Error = Error; + type InitError = (); + type Transform = ErrorAppenderMiddleware; + type Future = Ready>; + + fn new_transform(&self, service: S) -> Self::Future { + ready(Ok(ErrorAppenderMiddleware { service })) + } +} + +pub struct ErrorAppenderMiddleware { + service: S, +} + +impl Service for ErrorAppenderMiddleware +where + S: Service, + S::Future: 'static, +{ + type Response = ServiceResponse; + type Error = Error; + type Future = LocalBoxFuture<'static, Result>; + + forward_ready!(service); + + fn call(&self, req: ServiceRequest) -> Self::Future { + let id = req.extensions().get::().copied(); + + let fut = self.service.call(req); + + Box::pin(async move { + let res = fut.await?; + if res.status().is_server_error() { + let (req, res) = res.into_parts(); + let (res, body) = res.into_parts(); + + let body_bytes = actix_web::body::to_bytes(body).await.ok().unwrap(); + let mut s = from_utf8(&body_bytes).unwrap().to_string(); + + let id_str = if let Some(id) = id { + id.to_string() + } else { + String::new() + }; + + s.push_str(" // request-id: "); + s.push_str(&id_str); + + let res = res.set_body(BoxBody::new(s)); + return Ok(ServiceResponse::new(req, res)); + } + + Ok(res) + }) + } +} diff --git a/web/src/middleware/mod.rs b/web/src/middleware/mod.rs index 1a8c931d..3c01fc29 100644 --- a/web/src/middleware/mod.rs +++ b/web/src/middleware/mod.rs @@ -1,5 +1,7 @@ mod redirect_to_login; mod load_current_user_from_db; +mod append_request_id_to_error; pub use redirect_to_login::RedirectToLogin; pub use load_current_user_from_db::LoadCurrentUser; +pub use append_request_id_to_error::ErrorAppender; diff --git a/web/src/utils/application_error.rs b/web/src/utils/application_error.rs index 5211b60d..641714cd 100644 --- a/web/src/utils/application_error.rs +++ b/web/src/utils/application_error.rs @@ -3,7 +3,6 @@ use thiserror::Error; use super::password_change::PasswordChangeError; - #[derive(Debug, Error)] pub enum ApplicationError { #[error("unsupported value '{value}' for enum '{enum_name}'")] diff --git a/web/src/utils/mod.rs b/web/src/utils/mod.rs index 084ef5b4..9d28b76f 100644 --- a/web/src/utils/mod.rs +++ b/web/src/utils/mod.rs @@ -2,9 +2,9 @@ mod application_error; pub mod auth; pub mod event_planning_template; pub mod manage_commands; -pub mod token_generation; -mod template_response_trait; pub mod password_change; +mod template_response_trait; +pub mod token_generation; #[cfg(test)] pub mod test_helper;