1
0
Fork 0
mirror of https://github.com/actix/actix-web.git synced 2024-06-02 13:29:24 +00:00
actix-web/src/middleware/logger.rs

666 lines
19 KiB
Rust
Raw Normal View History

2019-03-07 01:32:41 +00:00
//! Request logging middleware
use std::collections::HashSet;
2019-12-05 17:35:43 +00:00
use std::convert::TryFrom;
2019-03-07 01:32:41 +00:00
use std::env;
use std::fmt::{self, Display, Formatter};
2019-11-20 17:33:22 +00:00
use std::future::Future;
use std::marker::PhantomData;
2019-11-20 17:33:22 +00:00
use std::pin::Pin;
2019-03-07 03:19:27 +00:00
use std::rc::Rc;
2019-11-20 17:33:22 +00:00
use std::task::{Context, Poll};
2019-03-07 01:32:41 +00:00
2019-03-07 03:19:27 +00:00
use actix_service::{Service, Transform};
use bytes::Bytes;
2020-05-18 02:47:20 +00:00
use futures_util::future::{ok, Ready};
use log::debug;
2019-03-07 01:32:41 +00:00
use regex::Regex;
Upgrade `time` to 0.2.5 (#1254) * Use `OffsetDateTime` instead of `PrimitiveDateTime` * Parse time strings with `PrimitiveDateTime::parse` instead of `OffsetDateTime::parse` * Remove unused `time` dependency from actix-multipart * Fix a few errors with time related tests from the `time` upgrade * Implement logic to convert a RFC 850 two-digit year into a full length year, and organize time parsing related functions * Upgrade `time` to 0.2.2 * Correctly parse C's asctime time format using time 0.2's new format patterns * Update CHANGES.md * Use `time` without any of its deprecated functions * Enforce a UTC time offset when converting an `OffsetDateTime` into a Header value * Use the more readable version of `Duration::seconds(0)`, `Duration::zero()` * Remove unneeded conversion of time::Duration to std::time::Duration * Use `OffsetDateTime::as_seconds_f64` instead of manually calculating the amount of seconds from nanoseconds * Replace a few additional instances of `Duration::seconds(0)` with `Duration::zero()` * Truncate any nanoseconds from a supplied `Duration` within `Cookie::set_max_age` to ensure two Cookies with the same amount whole seconds equate to one another * Fix the actix-http::cookie::do_not_panic_on_large_max_ages test * Convert `Cookie::max_age` and `Cookie::expires` examples to `time` 0.2 Mainly minor changes. Type inference can be used alongside the new `time::parse` method, such that the type doesn't need to be specified. This will be useful if a refactoring takes place that changes the type. There are also new macros, which are used where possible. One change that is not immediately obvious, in `HttpDate`, there was an unnecessary conditional. As the time crate allows for negative durations (and can perform arithmetic with such), the if/else can be removed entirely. Time v0.2.3 also has some bug fixes, which is why I am not using a more general v0.2 in Cargo.toml. v0.2.3 has been yanked, as it was backwards imcompatible. This version reverts the breaking change, while still supporting rustc back to 1.34.0. * Add missing `time::offset` macro import * Fix type confusion when using `time::parse` followed by `using_offset` * Update `time` to 0.2.5 * Update CHANGES.md Co-authored-by: Jacob Pratt <the.z.cuber@gmail.com>
2020-01-28 11:44:22 +00:00
use time::OffsetDateTime;
2019-03-07 01:32:41 +00:00
2019-03-27 16:24:55 +00:00
use crate::dev::{BodySize, MessageBody, ResponseBody};
2019-03-07 03:19:27 +00:00
use crate::error::{Error, Result};
2019-12-05 17:35:43 +00:00
use crate::http::{HeaderName, StatusCode};
2019-03-07 03:19:27 +00:00
use crate::service::{ServiceRequest, ServiceResponse};
2019-04-02 20:35:01 +00:00
use crate::HttpResponse;
2019-03-07 01:32:41 +00:00
/// `Middleware` for logging request and response info to the terminal.
///
/// `Logger` middleware uses standard log crate to log information. You should
/// enable logger for `actix_web` package to see access log.
/// ([`env_logger`](https://docs.rs/env_logger/*/env_logger/) or similar)
///
/// ## Usage
///
/// Create `Logger` middleware with the specified `format`.
/// Default `Logger` could be created with `default` method, it uses the
/// default format:
///
/// ```ignore
/// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
/// ```
/// ```rust
/// use actix_web::middleware::Logger;
/// use actix_web::App;
///
/// fn main() {
/// std::env::set_var("RUST_LOG", "actix_web=info");
/// env_logger::init();
///
/// let app = App::new()
2019-03-25 20:02:10 +00:00
/// .wrap(Logger::default())
/// .wrap(Logger::new("%a %{User-Agent}i"));
2019-03-07 01:32:41 +00:00
/// }
/// ```
///
/// ## Format
///
/// `%%` The percent sign
///
/// `%a` Remote IP-address (IP-address of proxy if using reverse proxy)
///
/// `%t` Time when the request was started to process (in rfc3339 format)
2019-03-07 01:32:41 +00:00
///
/// `%r` First line of request
///
/// `%s` Response status code
///
/// `%b` Size of response in bytes, including HTTP headers
///
/// `%T` Time taken to serve the request, in seconds with floating fraction in
/// .06f format
///
/// `%D` Time taken to serve the request, in milliseconds
///
/// `%U` Request URL
///
/// `%{r}a` Real IP remote address **\***
///
2019-03-07 01:32:41 +00:00
/// `%{FOO}i` request.headers['FOO']
///
/// `%{FOO}o` response.headers['FOO']
///
/// `%{FOO}e` os.environ['FOO']
///
/// # Security
/// **\*** It is calculated using
/// [`ConnectionInfo::realip_remote_addr()`](../dev/struct.ConnectionInfo.html#method.realip_remote_addr)
///
/// If you use this value ensure that all requests come from trusted hosts, since it is trivial
/// for the remote client to simulate been another client.
///
2019-03-07 03:19:27 +00:00
pub struct Logger(Rc<Inner>);
struct Inner {
2019-03-07 01:32:41 +00:00
format: Format,
exclude: HashSet<String>,
}
impl Logger {
/// Create `Logger` middleware with the specified `format`.
pub fn new(format: &str) -> Logger {
2019-03-07 03:19:27 +00:00
Logger(Rc::new(Inner {
2019-03-07 01:32:41 +00:00
format: Format::new(format),
exclude: HashSet::new(),
2019-03-07 03:19:27 +00:00
}))
2019-03-07 01:32:41 +00:00
}
/// Ignore and do not log access info for specified path.
pub fn exclude<T: Into<String>>(mut self, path: T) -> Self {
2019-03-07 03:19:27 +00:00
Rc::get_mut(&mut self.0)
.unwrap()
.exclude
.insert(path.into());
2019-03-07 01:32:41 +00:00
self
}
}
impl Default for Logger {
/// Create `Logger` middleware with format:
///
/// ```ignore
/// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
/// ```
fn default() -> Logger {
2019-03-07 03:19:27 +00:00
Logger(Rc::new(Inner {
2019-03-07 01:32:41 +00:00
format: Format::default(),
exclude: HashSet::new(),
2019-03-07 03:19:27 +00:00
}))
}
}
impl<S, B> Transform<S> for Logger
2019-03-07 03:19:27 +00:00
where
2019-04-25 18:14:32 +00:00
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
2019-03-07 03:19:27 +00:00
B: MessageBody,
{
type Request = ServiceRequest;
2019-03-07 03:19:27 +00:00
type Response = ServiceResponse<StreamLog<B>>;
2019-04-25 18:14:32 +00:00
type Error = Error;
2019-03-07 03:19:27 +00:00
type InitError = ();
type Transform = LoggerMiddleware<S>;
2019-11-20 17:33:22 +00:00
type Future = Ready<Result<Self::Transform, Self::InitError>>;
2019-03-07 03:19:27 +00:00
fn new_transform(&self, service: S) -> Self::Future {
ok(LoggerMiddleware {
service,
inner: self.0.clone(),
})
}
}
/// Logger middleware
pub struct LoggerMiddleware<S> {
inner: Rc<Inner>,
service: S,
}
impl<S, B> Service for LoggerMiddleware<S>
2019-03-07 03:19:27 +00:00
where
2019-04-25 18:14:32 +00:00
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
2019-03-07 03:19:27 +00:00
B: MessageBody,
{
type Request = ServiceRequest;
2019-03-07 03:19:27 +00:00
type Response = ServiceResponse<StreamLog<B>>;
2019-04-25 18:14:32 +00:00
type Error = Error;
type Future = LoggerResponse<S, B>;
2019-03-07 03:19:27 +00:00
2019-12-07 18:46:51 +00:00
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
2019-11-20 17:33:22 +00:00
self.service.poll_ready(cx)
2019-03-07 03:19:27 +00:00
}
fn call(&mut self, req: ServiceRequest) -> Self::Future {
2019-03-07 03:19:27 +00:00
if self.inner.exclude.contains(req.path()) {
LoggerResponse {
fut: self.service.call(req),
format: None,
time: OffsetDateTime::now_utc(),
_t: PhantomData,
2019-03-07 03:19:27 +00:00
}
} else {
let now = OffsetDateTime::now_utc();
2019-03-07 03:19:27 +00:00
let mut format = self.inner.format.clone();
for unit in &mut format.0 {
unit.render_request(now, &req);
}
LoggerResponse {
fut: self.service.call(req),
format: Some(format),
time: now,
_t: PhantomData,
2019-03-07 03:19:27 +00:00
}
2019-03-07 01:32:41 +00:00
}
}
}
2019-03-07 03:19:27 +00:00
#[doc(hidden)]
2019-11-20 17:33:22 +00:00
#[pin_project::pin_project]
pub struct LoggerResponse<S, B>
2019-03-07 03:19:27 +00:00
where
B: MessageBody,
S: Service,
2019-03-07 03:19:27 +00:00
{
2019-11-20 17:33:22 +00:00
#[pin]
2019-03-07 03:19:27 +00:00
fut: S::Future,
Upgrade `time` to 0.2.5 (#1254) * Use `OffsetDateTime` instead of `PrimitiveDateTime` * Parse time strings with `PrimitiveDateTime::parse` instead of `OffsetDateTime::parse` * Remove unused `time` dependency from actix-multipart * Fix a few errors with time related tests from the `time` upgrade * Implement logic to convert a RFC 850 two-digit year into a full length year, and organize time parsing related functions * Upgrade `time` to 0.2.2 * Correctly parse C's asctime time format using time 0.2's new format patterns * Update CHANGES.md * Use `time` without any of its deprecated functions * Enforce a UTC time offset when converting an `OffsetDateTime` into a Header value * Use the more readable version of `Duration::seconds(0)`, `Duration::zero()` * Remove unneeded conversion of time::Duration to std::time::Duration * Use `OffsetDateTime::as_seconds_f64` instead of manually calculating the amount of seconds from nanoseconds * Replace a few additional instances of `Duration::seconds(0)` with `Duration::zero()` * Truncate any nanoseconds from a supplied `Duration` within `Cookie::set_max_age` to ensure two Cookies with the same amount whole seconds equate to one another * Fix the actix-http::cookie::do_not_panic_on_large_max_ages test * Convert `Cookie::max_age` and `Cookie::expires` examples to `time` 0.2 Mainly minor changes. Type inference can be used alongside the new `time::parse` method, such that the type doesn't need to be specified. This will be useful if a refactoring takes place that changes the type. There are also new macros, which are used where possible. One change that is not immediately obvious, in `HttpDate`, there was an unnecessary conditional. As the time crate allows for negative durations (and can perform arithmetic with such), the if/else can be removed entirely. Time v0.2.3 also has some bug fixes, which is why I am not using a more general v0.2 in Cargo.toml. v0.2.3 has been yanked, as it was backwards imcompatible. This version reverts the breaking change, while still supporting rustc back to 1.34.0. * Add missing `time::offset` macro import * Fix type confusion when using `time::parse` followed by `using_offset` * Update `time` to 0.2.5 * Update CHANGES.md Co-authored-by: Jacob Pratt <the.z.cuber@gmail.com>
2020-01-28 11:44:22 +00:00
time: OffsetDateTime,
2019-03-07 03:19:27 +00:00
format: Option<Format>,
_t: PhantomData<(B,)>,
2019-03-07 03:19:27 +00:00
}
2019-03-07 01:32:41 +00:00
impl<S, B> Future for LoggerResponse<S, B>
2019-03-07 03:19:27 +00:00
where
B: MessageBody,
2019-04-25 18:14:32 +00:00
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
2019-03-07 03:19:27 +00:00
{
2019-11-20 17:33:22 +00:00
type Output = Result<ServiceResponse<StreamLog<B>>, Error>;
2019-12-07 18:46:51 +00:00
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2019-11-20 17:33:22 +00:00
let this = self.project();
2019-03-07 03:19:27 +00:00
2020-05-18 02:47:20 +00:00
let res = match futures_util::ready!(this.fut.poll(cx)) {
2019-11-20 17:33:22 +00:00
Ok(res) => res,
Err(e) => return Poll::Ready(Err(e)),
};
2019-03-07 03:19:27 +00:00
if let Some(error) = res.response().error() {
if res.response().head().status != StatusCode::INTERNAL_SERVER_ERROR {
debug!("Error in response: {:?}", error);
}
}
2019-11-20 17:33:22 +00:00
if let Some(ref mut format) = this.format {
2019-03-07 03:19:27 +00:00
for unit in &mut format.0 {
2019-04-02 20:35:01 +00:00
unit.render_response(res.response());
2019-03-07 03:19:27 +00:00
}
}
2019-11-20 17:33:22 +00:00
let time = *this.time;
let format = this.format.take();
Poll::Ready(Ok(res.map_body(move |_, body| {
2019-03-07 03:19:27 +00:00
ResponseBody::Body(StreamLog {
body,
2019-11-20 17:33:22 +00:00
time,
format,
2019-03-07 03:19:27 +00:00
size: 0,
})
})))
}
}
use pin_project::{pin_project, pinned_drop};
#[pin_project(PinnedDrop)]
2019-03-07 03:19:27 +00:00
pub struct StreamLog<B> {
#[pin]
2019-03-07 03:19:27 +00:00
body: ResponseBody<B>,
format: Option<Format>,
size: usize,
Upgrade `time` to 0.2.5 (#1254) * Use `OffsetDateTime` instead of `PrimitiveDateTime` * Parse time strings with `PrimitiveDateTime::parse` instead of `OffsetDateTime::parse` * Remove unused `time` dependency from actix-multipart * Fix a few errors with time related tests from the `time` upgrade * Implement logic to convert a RFC 850 two-digit year into a full length year, and organize time parsing related functions * Upgrade `time` to 0.2.2 * Correctly parse C's asctime time format using time 0.2's new format patterns * Update CHANGES.md * Use `time` without any of its deprecated functions * Enforce a UTC time offset when converting an `OffsetDateTime` into a Header value * Use the more readable version of `Duration::seconds(0)`, `Duration::zero()` * Remove unneeded conversion of time::Duration to std::time::Duration * Use `OffsetDateTime::as_seconds_f64` instead of manually calculating the amount of seconds from nanoseconds * Replace a few additional instances of `Duration::seconds(0)` with `Duration::zero()` * Truncate any nanoseconds from a supplied `Duration` within `Cookie::set_max_age` to ensure two Cookies with the same amount whole seconds equate to one another * Fix the actix-http::cookie::do_not_panic_on_large_max_ages test * Convert `Cookie::max_age` and `Cookie::expires` examples to `time` 0.2 Mainly minor changes. Type inference can be used alongside the new `time::parse` method, such that the type doesn't need to be specified. This will be useful if a refactoring takes place that changes the type. There are also new macros, which are used where possible. One change that is not immediately obvious, in `HttpDate`, there was an unnecessary conditional. As the time crate allows for negative durations (and can perform arithmetic with such), the if/else can be removed entirely. Time v0.2.3 also has some bug fixes, which is why I am not using a more general v0.2 in Cargo.toml. v0.2.3 has been yanked, as it was backwards imcompatible. This version reverts the breaking change, while still supporting rustc back to 1.34.0. * Add missing `time::offset` macro import * Fix type confusion when using `time::parse` followed by `using_offset` * Update `time` to 0.2.5 * Update CHANGES.md Co-authored-by: Jacob Pratt <the.z.cuber@gmail.com>
2020-01-28 11:44:22 +00:00
time: OffsetDateTime,
2019-03-07 03:19:27 +00:00
}
#[pinned_drop]
impl<B> PinnedDrop for StreamLog<B> {
fn drop(self: Pin<&mut Self>) {
if let Some(ref format) = self.format {
2019-12-07 18:46:51 +00:00
let render = |fmt: &mut Formatter<'_>| {
2019-03-07 03:19:27 +00:00
for unit in &format.0 {
unit.render(fmt, self.size, self.time)?;
2019-03-07 01:32:41 +00:00
}
Ok(())
};
2019-03-07 03:19:27 +00:00
log::info!("{}", FormatDisplay(&render));
2019-03-07 01:32:41 +00:00
}
}
}
2019-03-07 03:19:27 +00:00
impl<B: MessageBody> MessageBody for StreamLog<B> {
fn size(&self) -> BodySize {
self.body.size()
2019-03-07 01:32:41 +00:00
}
2020-05-21 08:56:53 +00:00
fn poll_next(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Error>>> {
let this = self.project();
match this.body.poll_next(cx) {
2019-11-20 17:33:22 +00:00
Poll::Ready(Some(Ok(chunk))) => {
*this.size += chunk.len();
2019-11-20 17:33:22 +00:00
Poll::Ready(Some(Ok(chunk)))
2019-03-07 03:19:27 +00:00
}
2019-11-20 17:33:22 +00:00
val => val,
2019-03-07 03:19:27 +00:00
}
2019-03-07 01:32:41 +00:00
}
}
/// A formatting style for the `Logger`, consisting of multiple
/// `FormatText`s concatenated into one line.
#[derive(Clone)]
#[doc(hidden)]
struct Format(Vec<FormatText>);
impl Default for Format {
/// Return the default formatting style for the `Logger`:
fn default() -> Format {
Format::new(r#"%a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T"#)
}
}
impl Format {
/// Create a `Format` from a format string.
///
/// Returns `None` if the format string syntax is incorrect.
pub fn new(s: &str) -> Format {
2019-03-07 03:19:27 +00:00
log::trace!("Access log format: {}", s);
let fmt = Regex::new(r"%(\{([A-Za-z0-9\-_]+)\}([aioe])|[atPrUsbTD]?)").unwrap();
2019-03-07 01:32:41 +00:00
let mut idx = 0;
let mut results = Vec::new();
for cap in fmt.captures_iter(s) {
let m = cap.get(0).unwrap();
let pos = m.start();
if idx != pos {
results.push(FormatText::Str(s[idx..pos].to_owned()));
}
idx = m.end();
if let Some(key) = cap.get(2) {
results.push(match cap.get(3).unwrap().as_str() {
2020-05-21 08:56:53 +00:00
"a" => {
if key.as_str() == "r" {
FormatText::RealIPRemoteAddr
} else {
unreachable!()
}
}
"i" => FormatText::RequestHeader(
HeaderName::try_from(key.as_str()).unwrap(),
),
"o" => FormatText::ResponseHeader(
HeaderName::try_from(key.as_str()).unwrap(),
),
2019-03-07 01:32:41 +00:00
"e" => FormatText::EnvironHeader(key.as_str().to_owned()),
_ => unreachable!(),
})
} else {
let m = cap.get(1).unwrap();
results.push(match m.as_str() {
"%" => FormatText::Percent,
"a" => FormatText::RemoteAddr,
"t" => FormatText::RequestTime,
"r" => FormatText::RequestLine,
"s" => FormatText::ResponseStatus,
"b" => FormatText::ResponseSize,
"U" => FormatText::UrlPath,
2019-03-07 01:32:41 +00:00
"T" => FormatText::Time,
"D" => FormatText::TimeMillis,
_ => FormatText::Str(m.as_str().to_owned()),
});
}
}
if idx != s.len() {
results.push(FormatText::Str(s[idx..].to_owned()));
}
Format(results)
}
}
/// A string of text to be logged. This is either one of the data
/// fields supported by the `Logger`, or a custom `String`.
#[doc(hidden)]
#[derive(Debug, Clone)]
pub enum FormatText {
Str(String),
Percent,
RequestLine,
RequestTime,
ResponseStatus,
ResponseSize,
Time,
TimeMillis,
RemoteAddr,
RealIPRemoteAddr,
UrlPath,
RequestHeader(HeaderName),
ResponseHeader(HeaderName),
2019-03-07 01:32:41 +00:00
EnvironHeader(String),
}
impl FormatText {
2019-03-07 03:19:27 +00:00
fn render(
&self,
2019-12-07 18:46:51 +00:00
fmt: &mut Formatter<'_>,
2019-03-07 03:19:27 +00:00
size: usize,
Upgrade `time` to 0.2.5 (#1254) * Use `OffsetDateTime` instead of `PrimitiveDateTime` * Parse time strings with `PrimitiveDateTime::parse` instead of `OffsetDateTime::parse` * Remove unused `time` dependency from actix-multipart * Fix a few errors with time related tests from the `time` upgrade * Implement logic to convert a RFC 850 two-digit year into a full length year, and organize time parsing related functions * Upgrade `time` to 0.2.2 * Correctly parse C's asctime time format using time 0.2's new format patterns * Update CHANGES.md * Use `time` without any of its deprecated functions * Enforce a UTC time offset when converting an `OffsetDateTime` into a Header value * Use the more readable version of `Duration::seconds(0)`, `Duration::zero()` * Remove unneeded conversion of time::Duration to std::time::Duration * Use `OffsetDateTime::as_seconds_f64` instead of manually calculating the amount of seconds from nanoseconds * Replace a few additional instances of `Duration::seconds(0)` with `Duration::zero()` * Truncate any nanoseconds from a supplied `Duration` within `Cookie::set_max_age` to ensure two Cookies with the same amount whole seconds equate to one another * Fix the actix-http::cookie::do_not_panic_on_large_max_ages test * Convert `Cookie::max_age` and `Cookie::expires` examples to `time` 0.2 Mainly minor changes. Type inference can be used alongside the new `time::parse` method, such that the type doesn't need to be specified. This will be useful if a refactoring takes place that changes the type. There are also new macros, which are used where possible. One change that is not immediately obvious, in `HttpDate`, there was an unnecessary conditional. As the time crate allows for negative durations (and can perform arithmetic with such), the if/else can be removed entirely. Time v0.2.3 also has some bug fixes, which is why I am not using a more general v0.2 in Cargo.toml. v0.2.3 has been yanked, as it was backwards imcompatible. This version reverts the breaking change, while still supporting rustc back to 1.34.0. * Add missing `time::offset` macro import * Fix type confusion when using `time::parse` followed by `using_offset` * Update `time` to 0.2.5 * Update CHANGES.md Co-authored-by: Jacob Pratt <the.z.cuber@gmail.com>
2020-01-28 11:44:22 +00:00
entry_time: OffsetDateTime,
2019-03-07 01:32:41 +00:00
) -> Result<(), fmt::Error> {
match *self {
FormatText::Str(ref string) => fmt.write_str(string),
FormatText::Percent => "%".fmt(fmt),
2019-03-07 03:19:27 +00:00
FormatText::ResponseSize => size.fmt(fmt),
2019-03-07 01:32:41 +00:00
FormatText::Time => {
let rt = OffsetDateTime::now_utc() - entry_time;
Upgrade `time` to 0.2.5 (#1254) * Use `OffsetDateTime` instead of `PrimitiveDateTime` * Parse time strings with `PrimitiveDateTime::parse` instead of `OffsetDateTime::parse` * Remove unused `time` dependency from actix-multipart * Fix a few errors with time related tests from the `time` upgrade * Implement logic to convert a RFC 850 two-digit year into a full length year, and organize time parsing related functions * Upgrade `time` to 0.2.2 * Correctly parse C's asctime time format using time 0.2's new format patterns * Update CHANGES.md * Use `time` without any of its deprecated functions * Enforce a UTC time offset when converting an `OffsetDateTime` into a Header value * Use the more readable version of `Duration::seconds(0)`, `Duration::zero()` * Remove unneeded conversion of time::Duration to std::time::Duration * Use `OffsetDateTime::as_seconds_f64` instead of manually calculating the amount of seconds from nanoseconds * Replace a few additional instances of `Duration::seconds(0)` with `Duration::zero()` * Truncate any nanoseconds from a supplied `Duration` within `Cookie::set_max_age` to ensure two Cookies with the same amount whole seconds equate to one another * Fix the actix-http::cookie::do_not_panic_on_large_max_ages test * Convert `Cookie::max_age` and `Cookie::expires` examples to `time` 0.2 Mainly minor changes. Type inference can be used alongside the new `time::parse` method, such that the type doesn't need to be specified. This will be useful if a refactoring takes place that changes the type. There are also new macros, which are used where possible. One change that is not immediately obvious, in `HttpDate`, there was an unnecessary conditional. As the time crate allows for negative durations (and can perform arithmetic with such), the if/else can be removed entirely. Time v0.2.3 also has some bug fixes, which is why I am not using a more general v0.2 in Cargo.toml. v0.2.3 has been yanked, as it was backwards imcompatible. This version reverts the breaking change, while still supporting rustc back to 1.34.0. * Add missing `time::offset` macro import * Fix type confusion when using `time::parse` followed by `using_offset` * Update `time` to 0.2.5 * Update CHANGES.md Co-authored-by: Jacob Pratt <the.z.cuber@gmail.com>
2020-01-28 11:44:22 +00:00
let rt = rt.as_seconds_f64();
2019-03-07 01:32:41 +00:00
fmt.write_fmt(format_args!("{:.6}", rt))
}
FormatText::TimeMillis => {
let rt = OffsetDateTime::now_utc() - entry_time;
Upgrade `time` to 0.2.5 (#1254) * Use `OffsetDateTime` instead of `PrimitiveDateTime` * Parse time strings with `PrimitiveDateTime::parse` instead of `OffsetDateTime::parse` * Remove unused `time` dependency from actix-multipart * Fix a few errors with time related tests from the `time` upgrade * Implement logic to convert a RFC 850 two-digit year into a full length year, and organize time parsing related functions * Upgrade `time` to 0.2.2 * Correctly parse C's asctime time format using time 0.2's new format patterns * Update CHANGES.md * Use `time` without any of its deprecated functions * Enforce a UTC time offset when converting an `OffsetDateTime` into a Header value * Use the more readable version of `Duration::seconds(0)`, `Duration::zero()` * Remove unneeded conversion of time::Duration to std::time::Duration * Use `OffsetDateTime::as_seconds_f64` instead of manually calculating the amount of seconds from nanoseconds * Replace a few additional instances of `Duration::seconds(0)` with `Duration::zero()` * Truncate any nanoseconds from a supplied `Duration` within `Cookie::set_max_age` to ensure two Cookies with the same amount whole seconds equate to one another * Fix the actix-http::cookie::do_not_panic_on_large_max_ages test * Convert `Cookie::max_age` and `Cookie::expires` examples to `time` 0.2 Mainly minor changes. Type inference can be used alongside the new `time::parse` method, such that the type doesn't need to be specified. This will be useful if a refactoring takes place that changes the type. There are also new macros, which are used where possible. One change that is not immediately obvious, in `HttpDate`, there was an unnecessary conditional. As the time crate allows for negative durations (and can perform arithmetic with such), the if/else can be removed entirely. Time v0.2.3 also has some bug fixes, which is why I am not using a more general v0.2 in Cargo.toml. v0.2.3 has been yanked, as it was backwards imcompatible. This version reverts the breaking change, while still supporting rustc back to 1.34.0. * Add missing `time::offset` macro import * Fix type confusion when using `time::parse` followed by `using_offset` * Update `time` to 0.2.5 * Update CHANGES.md Co-authored-by: Jacob Pratt <the.z.cuber@gmail.com>
2020-01-28 11:44:22 +00:00
let rt = (rt.whole_nanoseconds() as f64) / 1_000_000.0;
2019-03-07 01:32:41 +00:00
fmt.write_fmt(format_args!("{:.6}", rt))
}
2019-03-07 03:19:27 +00:00
FormatText::EnvironHeader(ref name) => {
if let Ok(val) = env::var(name) {
fmt.write_fmt(format_args!("{}", val))
2019-03-07 01:32:41 +00:00
} else {
"-".fmt(fmt)
}
}
2019-03-07 03:19:27 +00:00
_ => Ok(()),
}
}
fn render_response<B>(&mut self, res: &HttpResponse<B>) {
match *self {
FormatText::ResponseStatus => {
*self = FormatText::Str(format!("{}", res.status().as_u16()))
}
FormatText::ResponseHeader(ref name) => {
let s = if let Some(val) = res.headers().get(name) {
2019-03-07 01:32:41 +00:00
if let Ok(s) = val.to_str() {
s
} else {
"-"
}
} else {
"-"
};
2019-03-07 03:19:27 +00:00
*self = FormatText::Str(s.to_string())
2019-03-07 01:32:41 +00:00
}
2019-03-07 03:19:27 +00:00
_ => (),
}
}
Upgrade `time` to 0.2.5 (#1254) * Use `OffsetDateTime` instead of `PrimitiveDateTime` * Parse time strings with `PrimitiveDateTime::parse` instead of `OffsetDateTime::parse` * Remove unused `time` dependency from actix-multipart * Fix a few errors with time related tests from the `time` upgrade * Implement logic to convert a RFC 850 two-digit year into a full length year, and organize time parsing related functions * Upgrade `time` to 0.2.2 * Correctly parse C's asctime time format using time 0.2's new format patterns * Update CHANGES.md * Use `time` without any of its deprecated functions * Enforce a UTC time offset when converting an `OffsetDateTime` into a Header value * Use the more readable version of `Duration::seconds(0)`, `Duration::zero()` * Remove unneeded conversion of time::Duration to std::time::Duration * Use `OffsetDateTime::as_seconds_f64` instead of manually calculating the amount of seconds from nanoseconds * Replace a few additional instances of `Duration::seconds(0)` with `Duration::zero()` * Truncate any nanoseconds from a supplied `Duration` within `Cookie::set_max_age` to ensure two Cookies with the same amount whole seconds equate to one another * Fix the actix-http::cookie::do_not_panic_on_large_max_ages test * Convert `Cookie::max_age` and `Cookie::expires` examples to `time` 0.2 Mainly minor changes. Type inference can be used alongside the new `time::parse` method, such that the type doesn't need to be specified. This will be useful if a refactoring takes place that changes the type. There are also new macros, which are used where possible. One change that is not immediately obvious, in `HttpDate`, there was an unnecessary conditional. As the time crate allows for negative durations (and can perform arithmetic with such), the if/else can be removed entirely. Time v0.2.3 also has some bug fixes, which is why I am not using a more general v0.2 in Cargo.toml. v0.2.3 has been yanked, as it was backwards imcompatible. This version reverts the breaking change, while still supporting rustc back to 1.34.0. * Add missing `time::offset` macro import * Fix type confusion when using `time::parse` followed by `using_offset` * Update `time` to 0.2.5 * Update CHANGES.md Co-authored-by: Jacob Pratt <the.z.cuber@gmail.com>
2020-01-28 11:44:22 +00:00
fn render_request(&mut self, now: OffsetDateTime, req: &ServiceRequest) {
2019-03-07 03:19:27 +00:00
match *self {
FormatText::RequestLine => {
*self = if req.query_string().is_empty() {
FormatText::Str(format!(
"{} {} {:?}",
req.method(),
req.path(),
req.version()
))
} else {
FormatText::Str(format!(
"{} {}?{} {:?}",
req.method(),
req.path(),
req.query_string(),
req.version()
))
};
}
FormatText::UrlPath => *self = FormatText::Str(req.path().to_string()),
2019-03-07 03:19:27 +00:00
FormatText::RequestTime => {
Upgrade `time` to 0.2.5 (#1254) * Use `OffsetDateTime` instead of `PrimitiveDateTime` * Parse time strings with `PrimitiveDateTime::parse` instead of `OffsetDateTime::parse` * Remove unused `time` dependency from actix-multipart * Fix a few errors with time related tests from the `time` upgrade * Implement logic to convert a RFC 850 two-digit year into a full length year, and organize time parsing related functions * Upgrade `time` to 0.2.2 * Correctly parse C's asctime time format using time 0.2's new format patterns * Update CHANGES.md * Use `time` without any of its deprecated functions * Enforce a UTC time offset when converting an `OffsetDateTime` into a Header value * Use the more readable version of `Duration::seconds(0)`, `Duration::zero()` * Remove unneeded conversion of time::Duration to std::time::Duration * Use `OffsetDateTime::as_seconds_f64` instead of manually calculating the amount of seconds from nanoseconds * Replace a few additional instances of `Duration::seconds(0)` with `Duration::zero()` * Truncate any nanoseconds from a supplied `Duration` within `Cookie::set_max_age` to ensure two Cookies with the same amount whole seconds equate to one another * Fix the actix-http::cookie::do_not_panic_on_large_max_ages test * Convert `Cookie::max_age` and `Cookie::expires` examples to `time` 0.2 Mainly minor changes. Type inference can be used alongside the new `time::parse` method, such that the type doesn't need to be specified. This will be useful if a refactoring takes place that changes the type. There are also new macros, which are used where possible. One change that is not immediately obvious, in `HttpDate`, there was an unnecessary conditional. As the time crate allows for negative durations (and can perform arithmetic with such), the if/else can be removed entirely. Time v0.2.3 also has some bug fixes, which is why I am not using a more general v0.2 in Cargo.toml. v0.2.3 has been yanked, as it was backwards imcompatible. This version reverts the breaking change, while still supporting rustc back to 1.34.0. * Add missing `time::offset` macro import * Fix type confusion when using `time::parse` followed by `using_offset` * Update `time` to 0.2.5 * Update CHANGES.md Co-authored-by: Jacob Pratt <the.z.cuber@gmail.com>
2020-01-28 11:44:22 +00:00
*self = FormatText::Str(now.format("%Y-%m-%dT%H:%M:%S"))
2019-03-07 03:19:27 +00:00
}
FormatText::RequestHeader(ref name) => {
let s = if let Some(val) = req.headers().get(name) {
2019-03-07 01:32:41 +00:00
if let Ok(s) = val.to_str() {
s
} else {
"-"
}
} else {
"-"
};
2019-03-07 03:19:27 +00:00
*self = FormatText::Str(s.to_string());
2019-03-07 01:32:41 +00:00
}
2019-04-16 17:11:38 +00:00
FormatText::RemoteAddr => {
let s = if let Some(ref peer) = req.connection_info().remote_addr() {
FormatText::Str(peer.to_string())
} else {
FormatText::Str("-".to_string())
};
*self = s;
}
FormatText::RealIPRemoteAddr => {
2020-05-21 08:56:53 +00:00
let s = if let Some(remote) = req.connection_info().realip_remote_addr()
{
2019-04-16 17:11:38 +00:00
FormatText::Str(remote.to_string())
} else {
FormatText::Str("-".to_string())
};
*self = s;
}
2019-03-07 03:19:27 +00:00
_ => (),
2019-03-07 01:32:41 +00:00
}
}
}
2019-07-17 09:48:37 +00:00
pub(crate) struct FormatDisplay<'a>(
2019-12-07 18:46:51 +00:00
&'a dyn Fn(&mut Formatter<'_>) -> Result<(), fmt::Error>,
2019-07-17 09:48:37 +00:00
);
2019-03-07 01:32:41 +00:00
impl<'a> fmt::Display for FormatDisplay<'a> {
2019-12-07 18:46:51 +00:00
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> {
2019-03-07 01:32:41 +00:00
(self.0)(fmt)
}
}
#[cfg(test)]
mod tests {
2019-05-12 15:34:51 +00:00
use actix_service::{IntoService, Service, Transform};
2020-05-18 02:47:20 +00:00
use futures_util::future::ok;
2019-03-07 01:32:41 +00:00
use super::*;
2019-03-07 03:19:27 +00:00
use crate::http::{header, StatusCode};
2019-11-26 05:25:50 +00:00
use crate::test::TestRequest;
2019-03-07 01:32:41 +00:00
2019-11-26 05:25:50 +00:00
#[actix_rt::test]
async fn test_logger() {
2019-05-12 15:34:51 +00:00
let srv = |req: ServiceRequest| {
2019-11-20 17:33:22 +00:00
ok(req.into_response(
2019-03-07 03:19:27 +00:00
HttpResponse::build(StatusCode::OK)
.header("X-Test", "ttt")
.finish(),
2019-11-20 17:33:22 +00:00
))
2019-05-12 15:34:51 +00:00
};
2019-03-07 01:32:41 +00:00
let logger = Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test");
2019-11-26 05:25:50 +00:00
let mut srv = logger.new_transform(srv.into_service()).await.unwrap();
2019-03-07 03:19:27 +00:00
2019-03-07 01:32:41 +00:00
let req = TestRequest::with_header(
header::USER_AGENT,
header::HeaderValue::from_static("ACTIX-WEB"),
2019-03-07 03:19:27 +00:00
)
2019-04-01 03:43:00 +00:00
.to_srv_request();
2019-11-26 05:25:50 +00:00
let _res = srv.call(req).await;
2019-03-07 01:32:41 +00:00
}
2019-11-26 05:25:50 +00:00
#[actix_rt::test]
async fn test_url_path() {
let mut format = Format::new("%T %U");
let req = TestRequest::with_header(
header::USER_AGENT,
header::HeaderValue::from_static("ACTIX-WEB"),
)
.uri("/test/route/yeah")
.to_srv_request();
let now = OffsetDateTime::now_utc();
for unit in &mut format.0 {
unit.render_request(now, &req);
}
let resp = HttpResponse::build(StatusCode::OK).force_close().finish();
for unit in &mut format.0 {
unit.render_response(&resp);
}
2019-12-07 18:46:51 +00:00
let render = |fmt: &mut Formatter<'_>| {
for unit in &format.0 {
unit.render(fmt, 1024, now)?;
}
Ok(())
};
let s = format!("{}", FormatDisplay(&render));
println!("{}", s);
assert!(s.contains("/test/route/yeah"));
}
2019-11-26 05:25:50 +00:00
#[actix_rt::test]
async fn test_default_format() {
2019-04-01 03:43:00 +00:00
let mut format = Format::default();
let req = TestRequest::with_header(
header::USER_AGENT,
header::HeaderValue::from_static("ACTIX-WEB"),
)
.peer_addr("127.0.0.1:8081".parse().unwrap())
2019-04-01 03:43:00 +00:00
.to_srv_request();
let now = OffsetDateTime::now_utc();
2019-04-01 03:43:00 +00:00
for unit in &mut format.0 {
unit.render_request(now, &req);
}
let resp = HttpResponse::build(StatusCode::OK).force_close().finish();
for unit in &mut format.0 {
unit.render_response(&resp);
}
let entry_time = OffsetDateTime::now_utc();
2019-12-07 18:46:51 +00:00
let render = |fmt: &mut Formatter<'_>| {
2019-04-01 03:43:00 +00:00
for unit in &format.0 {
unit.render(fmt, 1024, entry_time)?;
}
Ok(())
};
let s = format!("{}", FormatDisplay(&render));
assert!(s.contains("GET / HTTP/1.1"));
assert!(s.contains("127.0.0.1"));
2019-04-01 03:43:00 +00:00
assert!(s.contains("200 1024"));
assert!(s.contains("ACTIX-WEB"));
}
2019-11-26 05:25:50 +00:00
#[actix_rt::test]
async fn test_request_time_format() {
let mut format = Format::new("%t");
let req = TestRequest::default().to_srv_request();
let now = OffsetDateTime::now_utc();
for unit in &mut format.0 {
unit.render_request(now, &req);
}
let resp = HttpResponse::build(StatusCode::OK).force_close().finish();
for unit in &mut format.0 {
unit.render_response(&resp);
}
2019-12-07 18:46:51 +00:00
let render = |fmt: &mut Formatter<'_>| {
for unit in &format.0 {
unit.render(fmt, 1024, now)?;
}
Ok(())
};
let s = format!("{}", FormatDisplay(&render));
Upgrade `time` to 0.2.5 (#1254) * Use `OffsetDateTime` instead of `PrimitiveDateTime` * Parse time strings with `PrimitiveDateTime::parse` instead of `OffsetDateTime::parse` * Remove unused `time` dependency from actix-multipart * Fix a few errors with time related tests from the `time` upgrade * Implement logic to convert a RFC 850 two-digit year into a full length year, and organize time parsing related functions * Upgrade `time` to 0.2.2 * Correctly parse C's asctime time format using time 0.2's new format patterns * Update CHANGES.md * Use `time` without any of its deprecated functions * Enforce a UTC time offset when converting an `OffsetDateTime` into a Header value * Use the more readable version of `Duration::seconds(0)`, `Duration::zero()` * Remove unneeded conversion of time::Duration to std::time::Duration * Use `OffsetDateTime::as_seconds_f64` instead of manually calculating the amount of seconds from nanoseconds * Replace a few additional instances of `Duration::seconds(0)` with `Duration::zero()` * Truncate any nanoseconds from a supplied `Duration` within `Cookie::set_max_age` to ensure two Cookies with the same amount whole seconds equate to one another * Fix the actix-http::cookie::do_not_panic_on_large_max_ages test * Convert `Cookie::max_age` and `Cookie::expires` examples to `time` 0.2 Mainly minor changes. Type inference can be used alongside the new `time::parse` method, such that the type doesn't need to be specified. This will be useful if a refactoring takes place that changes the type. There are also new macros, which are used where possible. One change that is not immediately obvious, in `HttpDate`, there was an unnecessary conditional. As the time crate allows for negative durations (and can perform arithmetic with such), the if/else can be removed entirely. Time v0.2.3 also has some bug fixes, which is why I am not using a more general v0.2 in Cargo.toml. v0.2.3 has been yanked, as it was backwards imcompatible. This version reverts the breaking change, while still supporting rustc back to 1.34.0. * Add missing `time::offset` macro import * Fix type confusion when using `time::parse` followed by `using_offset` * Update `time` to 0.2.5 * Update CHANGES.md Co-authored-by: Jacob Pratt <the.z.cuber@gmail.com>
2020-01-28 11:44:22 +00:00
assert!(s.contains(&format!("{}", now.format("%Y-%m-%dT%H:%M:%S"))));
}
#[actix_rt::test]
async fn test_remote_addr_format() {
let mut format = Format::new("%{r}a");
let req = TestRequest::with_header(
header::FORWARDED,
2020-05-21 08:56:53 +00:00
header::HeaderValue::from_static(
"for=192.0.2.60;proto=http;by=203.0.113.43",
),
)
.to_srv_request();
let now = OffsetDateTime::now_utc();
for unit in &mut format.0 {
unit.render_request(now, &req);
}
let resp = HttpResponse::build(StatusCode::OK).force_close().finish();
for unit in &mut format.0 {
unit.render_response(&resp);
}
let entry_time = OffsetDateTime::now_utc();
let render = |fmt: &mut Formatter<'_>| {
for unit in &format.0 {
unit.render(fmt, 1024, entry_time)?;
}
Ok(())
};
let s = format!("{}", FormatDisplay(&render));
println!("{}", s);
assert!(s.contains("192.0.2.60"));
}
2019-03-07 01:32:41 +00:00
}