2019-03-06 17:32:41 -08:00
|
|
|
//! Request logging middleware
|
|
|
|
use std::collections::HashSet;
|
|
|
|
use std::env;
|
|
|
|
use std::fmt::{self, Display, Formatter};
|
2019-03-09 09:49:11 -08:00
|
|
|
use std::marker::PhantomData;
|
2019-03-06 19:19:27 -08:00
|
|
|
use std::rc::Rc;
|
2019-03-06 17:32:41 -08:00
|
|
|
|
2019-03-06 19:19:27 -08:00
|
|
|
use actix_service::{Service, Transform};
|
|
|
|
use bytes::Bytes;
|
|
|
|
use futures::future::{ok, FutureResult};
|
|
|
|
use futures::{Async, Future, Poll};
|
2019-03-06 17:32:41 -08:00
|
|
|
use regex::Regex;
|
|
|
|
use time;
|
|
|
|
|
2019-03-27 09:24:55 -07:00
|
|
|
use crate::dev::{BodySize, MessageBody, ResponseBody};
|
2019-03-06 19:19:27 -08:00
|
|
|
use crate::error::{Error, Result};
|
2019-04-06 15:02:02 -07:00
|
|
|
use crate::http::{HeaderName, HttpTryFrom};
|
2019-03-06 19:19:27 -08:00
|
|
|
use crate::service::{ServiceRequest, ServiceResponse};
|
2019-04-02 13:35:01 -07:00
|
|
|
use crate::HttpResponse;
|
2019-03-06 17:32:41 -08: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 13:02:10 -07:00
|
|
|
/// .wrap(Logger::default())
|
|
|
|
/// .wrap(Logger::new("%a %{User-Agent}i"));
|
2019-03-06 17:32:41 -08:00
|
|
|
/// }
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// ## Format
|
|
|
|
///
|
|
|
|
/// `%%` The percent sign
|
|
|
|
///
|
|
|
|
/// `%a` Remote IP-address (IP-address of proxy if using reverse proxy)
|
|
|
|
///
|
2019-05-31 10:09:21 +02:00
|
|
|
/// `%t` Time when the request was started to process (in rfc3339 format)
|
2019-03-06 17:32:41 -08: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
|
|
|
|
///
|
2019-04-03 20:41:42 -04:00
|
|
|
/// `%U` Request URL
|
|
|
|
///
|
2019-03-06 17:32:41 -08:00
|
|
|
/// `%{FOO}i` request.headers['FOO']
|
|
|
|
///
|
|
|
|
/// `%{FOO}o` response.headers['FOO']
|
|
|
|
///
|
|
|
|
/// `%{FOO}e` os.environ['FOO']
|
|
|
|
///
|
2019-03-06 19:19:27 -08:00
|
|
|
pub struct Logger(Rc<Inner>);
|
|
|
|
|
|
|
|
struct Inner {
|
2019-03-06 17:32:41 -08:00
|
|
|
format: Format,
|
|
|
|
exclude: HashSet<String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Logger {
|
|
|
|
/// Create `Logger` middleware with the specified `format`.
|
|
|
|
pub fn new(format: &str) -> Logger {
|
2019-03-06 19:19:27 -08:00
|
|
|
Logger(Rc::new(Inner {
|
2019-03-06 17:32:41 -08:00
|
|
|
format: Format::new(format),
|
|
|
|
exclude: HashSet::new(),
|
2019-03-06 19:19:27 -08:00
|
|
|
}))
|
2019-03-06 17:32:41 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Ignore and do not log access info for specified path.
|
|
|
|
pub fn exclude<T: Into<String>>(mut self, path: T) -> Self {
|
2019-03-06 19:19:27 -08:00
|
|
|
Rc::get_mut(&mut self.0)
|
|
|
|
.unwrap()
|
|
|
|
.exclude
|
|
|
|
.insert(path.into());
|
2019-03-06 17:32:41 -08: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-06 19:19:27 -08:00
|
|
|
Logger(Rc::new(Inner {
|
2019-03-06 17:32:41 -08:00
|
|
|
format: Format::default(),
|
|
|
|
exclude: HashSet::new(),
|
2019-03-06 19:19:27 -08:00
|
|
|
}))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-13 14:50:54 -07:00
|
|
|
impl<S, B> Transform<S> for Logger
|
2019-03-06 19:19:27 -08:00
|
|
|
where
|
2019-04-25 11:14:32 -07:00
|
|
|
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
2019-03-06 19:19:27 -08:00
|
|
|
B: MessageBody,
|
|
|
|
{
|
2019-04-13 14:50:54 -07:00
|
|
|
type Request = ServiceRequest;
|
2019-03-06 19:19:27 -08:00
|
|
|
type Response = ServiceResponse<StreamLog<B>>;
|
2019-04-25 11:14:32 -07:00
|
|
|
type Error = Error;
|
2019-03-06 19:19:27 -08:00
|
|
|
type InitError = ();
|
|
|
|
type Transform = LoggerMiddleware<S>;
|
|
|
|
type Future = FutureResult<Self::Transform, Self::InitError>;
|
|
|
|
|
|
|
|
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,
|
|
|
|
}
|
|
|
|
|
2019-04-13 14:50:54 -07:00
|
|
|
impl<S, B> Service for LoggerMiddleware<S>
|
2019-03-06 19:19:27 -08:00
|
|
|
where
|
2019-04-25 11:14:32 -07:00
|
|
|
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
2019-03-06 19:19:27 -08:00
|
|
|
B: MessageBody,
|
|
|
|
{
|
2019-04-13 14:50:54 -07:00
|
|
|
type Request = ServiceRequest;
|
2019-03-06 19:19:27 -08:00
|
|
|
type Response = ServiceResponse<StreamLog<B>>;
|
2019-04-25 11:14:32 -07:00
|
|
|
type Error = Error;
|
2019-04-13 14:50:54 -07:00
|
|
|
type Future = LoggerResponse<S, B>;
|
2019-03-06 19:19:27 -08:00
|
|
|
|
|
|
|
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
|
|
|
self.service.poll_ready()
|
|
|
|
}
|
|
|
|
|
2019-04-13 14:50:54 -07:00
|
|
|
fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
2019-03-06 19:19:27 -08:00
|
|
|
if self.inner.exclude.contains(req.path()) {
|
|
|
|
LoggerResponse {
|
|
|
|
fut: self.service.call(req),
|
|
|
|
format: None,
|
|
|
|
time: time::now(),
|
2019-03-09 09:49:11 -08:00
|
|
|
_t: PhantomData,
|
2019-03-06 19:19:27 -08:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
let now = time::now();
|
|
|
|
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,
|
2019-03-09 09:49:11 -08:00
|
|
|
_t: PhantomData,
|
2019-03-06 19:19:27 -08:00
|
|
|
}
|
2019-03-06 17:32:41 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-06 19:19:27 -08:00
|
|
|
#[doc(hidden)]
|
2019-04-13 14:50:54 -07:00
|
|
|
pub struct LoggerResponse<S, B>
|
2019-03-06 19:19:27 -08:00
|
|
|
where
|
|
|
|
B: MessageBody,
|
2019-03-09 09:49:11 -08:00
|
|
|
S: Service,
|
2019-03-06 19:19:27 -08:00
|
|
|
{
|
|
|
|
fut: S::Future,
|
|
|
|
time: time::Tm,
|
|
|
|
format: Option<Format>,
|
2019-04-13 14:50:54 -07:00
|
|
|
_t: PhantomData<(B,)>,
|
2019-03-06 19:19:27 -08:00
|
|
|
}
|
2019-03-06 17:32:41 -08:00
|
|
|
|
2019-04-13 14:50:54 -07:00
|
|
|
impl<S, B> Future for LoggerResponse<S, B>
|
2019-03-06 19:19:27 -08:00
|
|
|
where
|
|
|
|
B: MessageBody,
|
2019-04-25 11:14:32 -07:00
|
|
|
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
2019-03-06 19:19:27 -08:00
|
|
|
{
|
|
|
|
type Item = ServiceResponse<StreamLog<B>>;
|
2019-04-25 11:14:32 -07:00
|
|
|
type Error = Error;
|
2019-03-06 19:19:27 -08:00
|
|
|
|
|
|
|
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
|
|
|
let res = futures::try_ready!(self.fut.poll());
|
|
|
|
|
|
|
|
if let Some(ref mut format) = self.format {
|
|
|
|
for unit in &mut format.0 {
|
2019-04-02 13:35:01 -07:00
|
|
|
unit.render_response(res.response());
|
2019-03-06 19:19:27 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(Async::Ready(res.map_body(move |_, body| {
|
|
|
|
ResponseBody::Body(StreamLog {
|
|
|
|
body,
|
|
|
|
size: 0,
|
|
|
|
time: self.time,
|
|
|
|
format: self.format.take(),
|
|
|
|
})
|
|
|
|
})))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct StreamLog<B> {
|
|
|
|
body: ResponseBody<B>,
|
|
|
|
format: Option<Format>,
|
|
|
|
size: usize,
|
|
|
|
time: time::Tm,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<B> Drop for StreamLog<B> {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
if let Some(ref format) = self.format {
|
2019-03-06 17:32:41 -08:00
|
|
|
let render = |fmt: &mut Formatter| {
|
2019-03-06 19:19:27 -08:00
|
|
|
for unit in &format.0 {
|
|
|
|
unit.render(fmt, self.size, self.time)?;
|
2019-03-06 17:32:41 -08:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
};
|
2019-03-06 19:19:27 -08:00
|
|
|
log::info!("{}", FormatDisplay(&render));
|
2019-03-06 17:32:41 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-06 19:19:27 -08:00
|
|
|
impl<B: MessageBody> MessageBody for StreamLog<B> {
|
2019-04-10 12:24:17 -07:00
|
|
|
fn size(&self) -> BodySize {
|
|
|
|
self.body.size()
|
2019-03-06 17:32:41 -08:00
|
|
|
}
|
|
|
|
|
2019-03-06 19:19:27 -08:00
|
|
|
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> {
|
|
|
|
match self.body.poll_next()? {
|
|
|
|
Async::Ready(Some(chunk)) => {
|
|
|
|
self.size += chunk.len();
|
|
|
|
Ok(Async::Ready(Some(chunk)))
|
|
|
|
}
|
|
|
|
val => Ok(val),
|
|
|
|
}
|
2019-03-06 17:32:41 -08: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-06 19:19:27 -08:00
|
|
|
log::trace!("Access log format: {}", s);
|
2019-04-03 20:41:42 -04:00
|
|
|
let fmt = Regex::new(r"%(\{([A-Za-z0-9\-_]+)\}([ioe])|[atPrUsbTD]?)").unwrap();
|
2019-03-06 17:32:41 -08: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() {
|
2019-04-06 15:02:02 -07:00
|
|
|
"i" => FormatText::RequestHeader(
|
|
|
|
HeaderName::try_from(key.as_str()).unwrap(),
|
|
|
|
),
|
|
|
|
"o" => FormatText::ResponseHeader(
|
|
|
|
HeaderName::try_from(key.as_str()).unwrap(),
|
|
|
|
),
|
2019-03-06 17:32:41 -08: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,
|
2019-04-03 20:41:42 -04:00
|
|
|
"U" => FormatText::UrlPath,
|
2019-03-06 17:32:41 -08: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,
|
2019-04-03 20:41:42 -04:00
|
|
|
UrlPath,
|
2019-04-06 15:02:02 -07:00
|
|
|
RequestHeader(HeaderName),
|
|
|
|
ResponseHeader(HeaderName),
|
2019-03-06 17:32:41 -08:00
|
|
|
EnvironHeader(String),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl FormatText {
|
2019-03-06 19:19:27 -08:00
|
|
|
fn render(
|
|
|
|
&self,
|
|
|
|
fmt: &mut Formatter,
|
|
|
|
size: usize,
|
2019-03-06 17:32:41 -08:00
|
|
|
entry_time: time::Tm,
|
|
|
|
) -> Result<(), fmt::Error> {
|
|
|
|
match *self {
|
|
|
|
FormatText::Str(ref string) => fmt.write_str(string),
|
|
|
|
FormatText::Percent => "%".fmt(fmt),
|
2019-03-06 19:19:27 -08:00
|
|
|
FormatText::ResponseSize => size.fmt(fmt),
|
2019-03-06 17:32:41 -08:00
|
|
|
FormatText::Time => {
|
|
|
|
let rt = time::now() - entry_time;
|
|
|
|
let rt = (rt.num_nanoseconds().unwrap_or(0) as f64) / 1_000_000_000.0;
|
|
|
|
fmt.write_fmt(format_args!("{:.6}", rt))
|
|
|
|
}
|
|
|
|
FormatText::TimeMillis => {
|
|
|
|
let rt = time::now() - entry_time;
|
|
|
|
let rt = (rt.num_nanoseconds().unwrap_or(0) as f64) / 1_000_000.0;
|
|
|
|
fmt.write_fmt(format_args!("{:.6}", rt))
|
|
|
|
}
|
2019-03-06 19:19:27 -08:00
|
|
|
FormatText::EnvironHeader(ref name) => {
|
|
|
|
if let Ok(val) = env::var(name) {
|
|
|
|
fmt.write_fmt(format_args!("{}", val))
|
2019-03-06 17:32:41 -08:00
|
|
|
} else {
|
|
|
|
"-".fmt(fmt)
|
|
|
|
}
|
|
|
|
}
|
2019-03-06 19:19:27 -08: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-06 17:32:41 -08:00
|
|
|
if let Ok(s) = val.to_str() {
|
|
|
|
s
|
|
|
|
} else {
|
|
|
|
"-"
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
"-"
|
|
|
|
};
|
2019-03-06 19:19:27 -08:00
|
|
|
*self = FormatText::Str(s.to_string())
|
2019-03-06 17:32:41 -08:00
|
|
|
}
|
2019-03-06 19:19:27 -08:00
|
|
|
_ => (),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-13 14:50:54 -07:00
|
|
|
fn render_request(&mut self, now: time::Tm, req: &ServiceRequest) {
|
2019-03-06 19:19:27 -08: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()
|
|
|
|
))
|
|
|
|
};
|
|
|
|
}
|
2019-07-17 12:28:42 +05:30
|
|
|
FormatText::UrlPath => *self = FormatText::Str(req.path().to_string()),
|
2019-03-06 19:19:27 -08:00
|
|
|
FormatText::RequestTime => {
|
2019-07-17 12:28:42 +05:30
|
|
|
*self = FormatText::Str(now.rfc3339().to_string())
|
2019-03-06 19:19:27 -08:00
|
|
|
}
|
|
|
|
FormatText::RequestHeader(ref name) => {
|
|
|
|
let s = if let Some(val) = req.headers().get(name) {
|
2019-03-06 17:32:41 -08:00
|
|
|
if let Ok(s) = val.to_str() {
|
|
|
|
s
|
|
|
|
} else {
|
|
|
|
"-"
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
"-"
|
|
|
|
};
|
2019-03-06 19:19:27 -08:00
|
|
|
*self = FormatText::Str(s.to_string());
|
2019-03-06 17:32:41 -08:00
|
|
|
}
|
2019-04-16 10:11:38 -07:00
|
|
|
FormatText::RemoteAddr => {
|
|
|
|
let s = if let Some(remote) = req.connection_info().remote() {
|
|
|
|
FormatText::Str(remote.to_string())
|
|
|
|
} else {
|
|
|
|
FormatText::Str("-".to_string())
|
|
|
|
};
|
|
|
|
*self = s;
|
|
|
|
}
|
2019-03-06 19:19:27 -08:00
|
|
|
_ => (),
|
2019-03-06 17:32:41 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) struct FormatDisplay<'a>(&'a Fn(&mut Formatter) -> Result<(), fmt::Error>);
|
|
|
|
|
|
|
|
impl<'a> fmt::Display for FormatDisplay<'a> {
|
|
|
|
fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> {
|
|
|
|
(self.0)(fmt)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2019-05-12 08:34:51 -07:00
|
|
|
use actix_service::{IntoService, Service, Transform};
|
2019-03-06 17:32:41 -08:00
|
|
|
|
|
|
|
use super::*;
|
2019-03-06 19:19:27 -08:00
|
|
|
use crate::http::{header, StatusCode};
|
|
|
|
use crate::test::{block_on, TestRequest};
|
2019-03-06 17:32:41 -08:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_logger() {
|
2019-05-12 08:34:51 -07:00
|
|
|
let srv = |req: ServiceRequest| {
|
2019-03-06 19:19:27 -08:00
|
|
|
req.into_response(
|
|
|
|
HttpResponse::build(StatusCode::OK)
|
|
|
|
.header("X-Test", "ttt")
|
|
|
|
.finish(),
|
|
|
|
)
|
2019-05-12 08:34:51 -07:00
|
|
|
};
|
2019-03-06 17:32:41 -08:00
|
|
|
let logger = Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test");
|
|
|
|
|
2019-05-12 08:34:51 -07:00
|
|
|
let mut srv = block_on(logger.new_transform(srv.into_service())).unwrap();
|
2019-03-06 19:19:27 -08:00
|
|
|
|
2019-03-06 17:32:41 -08:00
|
|
|
let req = TestRequest::with_header(
|
|
|
|
header::USER_AGENT,
|
|
|
|
header::HeaderValue::from_static("ACTIX-WEB"),
|
2019-03-06 19:19:27 -08:00
|
|
|
)
|
2019-03-31 20:43:00 -07:00
|
|
|
.to_srv_request();
|
2019-03-06 19:19:27 -08:00
|
|
|
let _res = block_on(srv.call(req));
|
2019-03-06 17:32:41 -08:00
|
|
|
}
|
|
|
|
|
2019-04-03 20:41:42 -04:00
|
|
|
#[test]
|
|
|
|
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"),
|
2019-04-03 19:55:19 -07:00
|
|
|
)
|
|
|
|
.uri("/test/route/yeah")
|
|
|
|
.to_srv_request();
|
2019-04-03 20:41:42 -04:00
|
|
|
|
|
|
|
let now = time::now();
|
|
|
|
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 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-03-31 20:43:00 -07:00
|
|
|
#[test]
|
|
|
|
fn test_default_format() {
|
|
|
|
let mut format = Format::default();
|
|
|
|
|
|
|
|
let req = TestRequest::with_header(
|
|
|
|
header::USER_AGENT,
|
|
|
|
header::HeaderValue::from_static("ACTIX-WEB"),
|
|
|
|
)
|
|
|
|
.to_srv_request();
|
|
|
|
|
|
|
|
let now = time::now();
|
|
|
|
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 = time::now();
|
|
|
|
let render = |fmt: &mut Formatter| {
|
|
|
|
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("200 1024"));
|
|
|
|
assert!(s.contains("ACTIX-WEB"));
|
|
|
|
}
|
2019-05-31 10:09:21 +02:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_request_time_format() {
|
|
|
|
let mut format = Format::new("%t");
|
|
|
|
let req = TestRequest::default().to_srv_request();
|
|
|
|
|
|
|
|
let now = time::now();
|
|
|
|
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 render = |fmt: &mut Formatter| {
|
|
|
|
for unit in &format.0 {
|
|
|
|
unit.render(fmt, 1024, now)?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
};
|
|
|
|
let s = format!("{}", FormatDisplay(&render));
|
|
|
|
assert!(s.contains(&format!("{}", now.rfc3339())));
|
|
|
|
}
|
2019-03-06 17:32:41 -08:00
|
|
|
}
|