diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index 953befb7f0e..cae38cdf738 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -2,6 +2,10 @@ ## Unreleased +### Added + +- Add `Logger::level` to change the log level. + ### Changed - Updated `zstd` dependency to `0.13`. diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index d9cf0b94f92..a709c91fcea 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -123,6 +123,7 @@ tls-openssl = { package = "openssl", version = "0.10.55" } tls-rustls = { package = "rustls", version = "0.21" } tokio = { version = "1.24.2", features = ["rt-multi-thread", "macros"] } zstd = "0.13" +capture-logger = "0.1" [[test]] name = "test_server" diff --git a/actix-web/src/middleware/logger.rs b/actix-web/src/middleware/logger.rs index ce2caacd96e..b86ad4a2567 100644 --- a/actix-web/src/middleware/logger.rs +++ b/actix-web/src/middleware/logger.rs @@ -16,7 +16,7 @@ use actix_service::{Service, Transform}; use actix_utils::future::{ready, Ready}; use bytes::Bytes; use futures_core::ready; -use log::{debug, warn}; +use log::{debug, warn, Level}; use pin_project_lite::pin_project; use regex::{Regex, RegexSet}; use time::{format_description::well_known::Rfc3339, OffsetDateTime}; @@ -47,7 +47,7 @@ use crate::{ /// ``` /// use actix_web::{middleware::Logger, App}; /// -/// // access logs are printed with the INFO level so ensure it is enabled by default +/// // access logs by default are printed with the INFO level so ensure it is enabled by default /// env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); /// /// let app = App::new() @@ -89,6 +89,7 @@ struct Inner { exclude: HashSet, exclude_regex: RegexSet, log_target: Cow<'static, str>, + level: Level, } impl Logger { @@ -99,6 +100,7 @@ impl Logger { exclude: HashSet::new(), exclude_regex: RegexSet::empty(), log_target: Cow::Borrowed(module_path!()), + level: Level::Info, })) } @@ -139,6 +141,24 @@ impl Logger { self } + /// Sets the logging level. + /// + /// By default, the log level is `Level::Info` + /// + /// # Examples + /// ``` + /// # use log::Level; + /// # use actix_web::middleware::Logger; + /// Logger::default() + /// .level(Level::Debug); + /// ``` + /// + pub fn level(mut self, level: Level) -> Self { + let inner = Rc::get_mut(&mut self.0).unwrap(); + inner.level = level; + self + } + /// Register a function that receives a ServiceRequest and returns a String for use in the /// log line. The label passed as the first argument should match a replacement substring in /// the logger format like `%{label}xi`. @@ -242,6 +262,7 @@ impl Default for Logger { exclude: HashSet::new(), exclude_regex: RegexSet::empty(), log_target: Cow::Borrowed(module_path!()), + level: Level::Info, })) } } @@ -308,6 +329,7 @@ where format: None, time: OffsetDateTime::now_utc(), log_target: Cow::Borrowed(""), + level: self.inner.level, _phantom: PhantomData, } } else { @@ -323,6 +345,7 @@ where format: Some(format), time: now, log_target: self.inner.log_target.clone(), + level: self.inner.level, _phantom: PhantomData, } } @@ -340,6 +363,7 @@ pin_project! { time: OffsetDateTime, format: Option, log_target: Cow<'static, str>, + level:Level, _phantom: PhantomData, } } @@ -386,6 +410,7 @@ where let time = *this.time; let format = this.format.take(); let log_target = this.log_target.clone(); + let level = *this.level; Poll::Ready(Ok(res.map_body(move |_, body| StreamLog { body, @@ -393,6 +418,7 @@ where format, size: 0, log_target, + level, }))) } } @@ -405,6 +431,7 @@ pin_project! { size: usize, time: OffsetDateTime, log_target: Cow<'static, str>, + level:Level, } impl PinnedDrop for StreamLog { @@ -417,8 +444,7 @@ pin_project! { Ok(()) }; - log::info!( - target: this.log_target.as_ref(), + log::log!(target: this.log_target.as_ref(), this.level, "{}", FormatDisplay(&render) ); } @@ -1004,4 +1030,27 @@ mod tests { let req = TestRequest::default().to_srv_request(); srv.call(req).await.unwrap(); } + + #[actix_rt::test] + async fn test_logger_level() { + let srv = |req: ServiceRequest| { + ok(req.into_response(HttpResponse::build(StatusCode::OK).finish())) + }; + let logger = Logger::new("%{User-Agent}i test_level %s").level(log::Level::Trace); + + let srv = logger.new_transform(srv.into_service()).await.unwrap(); + + let req = TestRequest::default() + .insert_header(( + header::USER_AGENT, + header::HeaderValue::from_static("ACTIX-WEB"), + )) + .to_srv_request(); + capture_logger::begin_capture(); + // The log is executed on drop, so the result need to be dropped + let _ = srv.call(req).await; + let log = capture_logger::pop_captured(); + assert_eq!(log.unwrap().level(), log::Level::Trace); + capture_logger::end_capture(); + } }