Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add feature for limit request to log in middleware logger using status code #3086

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions actix-web/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Added

- Add `web::Html` responder.
- Add `Logger::statuses` to filter the range of statuses logged.
- Add `HttpRequest::full_url()` method to get the complete URL of the request.

### Fixed
Expand Down
1 change: 1 addition & 0 deletions actix-web/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ tls-openssl = { package = "openssl", version = "0.10.55" }
tls-rustls = { package = "rustls", version = "0.23" }
tokio = { version = "1.24.2", features = ["rt-multi-thread", "macros"] }
zstd = "0.13"
capture-logger = "0.1"

[[test]]
name = "test_server"
Expand Down
97 changes: 93 additions & 4 deletions actix-web/src/middleware/logger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::{
fmt::{self, Display as _},
future::Future,
marker::PhantomData,
ops::{Bound, RangeBounds},
pin::Pin,
rc::Rc,
task::{Context, Poll},
Expand All @@ -26,7 +27,7 @@ use time::{format_description::well_known::Rfc3339, OffsetDateTime};

use crate::{
body::{BodySize, MessageBody},
http::header::HeaderName,
http::{header::HeaderName, StatusCode},
service::{ServiceRequest, ServiceResponse},
Error, Result,
};
Expand Down Expand Up @@ -92,6 +93,7 @@ struct Inner {
exclude: HashSet<String>,
exclude_regex: Vec<Regex>,
log_target: Cow<'static, str>,
status_range: (Bound<StatusCode>, Bound<StatusCode>),
}

impl Logger {
Expand All @@ -102,6 +104,10 @@ impl Logger {
exclude: HashSet::new(),
exclude_regex: Vec::new(),
log_target: Cow::Borrowed(module_path!()),
status_range: (
Bound::Included(StatusCode::from_u16(100).unwrap()),
Bound::Included(StatusCode::from_u16(999).unwrap()),
),
}))
}

Expand All @@ -121,6 +127,23 @@ impl Logger {
self
}

/// Set a range of status to include in the logging
///
/// # Examples
/// ```
/// use actix_web::{middleware::Logger, App, http::StatusCode};
///
/// // Log only the requests with status code higher or equal to BAD_REQUEST(400)
/// let app = App::new()
/// .wrap(Logger::default().statuses(StatusCode::BAD_REQUEST..));
///
/// ```
pub fn statuses<R: RangeBounds<StatusCode>>(mut self, status: R) -> Self {
let inner = Rc::get_mut(&mut self.0).unwrap();
inner.status_range = (status.start_bound().cloned(), status.end_bound().cloned());
self
}

/// Sets the logging target to `target`.
///
/// By default, the log target is `module_path!()` of the log call location. In our case, that
Expand Down Expand Up @@ -242,6 +265,10 @@ impl Default for Logger {
exclude: HashSet::new(),
exclude_regex: Vec::new(),
log_target: Cow::Borrowed(module_path!()),
status_range: (
Bound::Included(StatusCode::from_u16(100).unwrap()),
Bound::Included(StatusCode::from_u16(999).unwrap()),
),
}))
}
}
Expand Down Expand Up @@ -310,6 +337,7 @@ where
LoggerResponse {
fut: self.service.call(req),
format: None,
status_range: self.inner.status_range,
time: OffsetDateTime::now_utc(),
log_target: Cow::Borrowed(""),
_phantom: PhantomData,
Expand All @@ -325,6 +353,7 @@ where
LoggerResponse {
fut: self.service.call(req),
format: Some(format),
status_range: self.inner.status_range,
time: now,
log_target: self.inner.log_target.clone(),
_phantom: PhantomData,
Expand All @@ -343,6 +372,7 @@ pin_project! {
fut: S::Future,
time: OffsetDateTime,
format: Option<Format>,
status_range:(Bound<StatusCode>,Bound<StatusCode>),
log_target: Cow<'static, str>,
_phantom: PhantomData<B>,
}
Expand All @@ -367,7 +397,13 @@ where
debug!("Error in response: {:?}", error);
}

let res = if let Some(ref mut format) = this.format {
let mut format = if this.status_range.contains(&res.status()) {
this.format.take()
} else {
None
};

let res = if let Some(ref mut format) = format {
// to avoid polluting all the Logger types with the body parameter we swap the body
// out temporarily since it's not usable in custom response functions anyway

Expand All @@ -388,7 +424,6 @@ where
};

let time = *this.time;
let format = this.format.take();
let log_target = this.log_target.clone();

Poll::Ready(Ok(res.map_body(move |_, body| StreamLog {
Expand Down Expand Up @@ -749,7 +784,13 @@ mod tests {
header::HeaderValue::from_static("ACTIX-WEB"),
))
.to_srv_request();
let _res = srv.call(req).await;
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().unwrap();
assert!(log.message().contains("ttt"));
assert!(log.message().contains("ACTIX-WEB"));
capture_logger::end_capture();
}

#[actix_rt::test]
Expand All @@ -775,6 +816,54 @@ mod tests {
let _res = srv.call(req).await.unwrap();
}

#[actix_rt::test]
async fn test_logger_status_range_include() {
let srv = |req: ServiceRequest| {
ok(req.into_response(HttpResponse::build(StatusCode::OK).finish()))
};
let logger = Logger::new("%{User-Agent}i test_included %s").statuses(StatusCode::OK..);

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().unwrap();
assert!(log.message().contains("200"));
assert!(log.message().contains("ACTIX-WEB"));
capture_logger::end_capture();
}

#[actix_rt::test]
async fn test_logger_status_range_exclude() {
let srv = |req: ServiceRequest| {
ok(req.into_response(HttpResponse::build(StatusCode::OK).finish()))
};
let logger =
Logger::new("%{User-Agent}i test_excluded %s").statuses(StatusCode::BAD_REQUEST..);

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!(log.is_none());
capture_logger::end_capture();
}

#[actix_rt::test]
async fn test_escape_percent() {
let mut format = Format::new("%%{r}a");
Expand Down
Loading