2017-10-22 07:59:09 +02:00
|
|
|
//! Request logging middleware
|
2017-11-10 21:29:54 +01:00
|
|
|
use std::env;
|
2017-10-22 07:59:09 +02:00
|
|
|
use std::fmt;
|
|
|
|
use std::fmt::{Display, Formatter};
|
|
|
|
|
|
|
|
use time;
|
2017-11-10 21:29:54 +01:00
|
|
|
use regex::Regex;
|
2017-10-22 07:59:09 +02:00
|
|
|
|
|
|
|
use httprequest::HttpRequest;
|
|
|
|
use httpresponse::HttpResponse;
|
2017-11-10 07:08:54 +01:00
|
|
|
use middlewares::{Middleware, Started, Finished};
|
2017-10-22 07:59:09 +02:00
|
|
|
|
|
|
|
/// `Middleware` for logging request and response info to the terminal.
|
2017-11-10 21:29:54 +01:00
|
|
|
///
|
|
|
|
/// ## Usage
|
|
|
|
///
|
|
|
|
/// Create `Logger` middlewares with the specified `format`.
|
|
|
|
/// Default `Logger` could be created with `default` method, it uses the default format:
|
|
|
|
///
|
|
|
|
/// ```ignore
|
|
|
|
/// %a %t "%r" %s %b "%{Referrer}i" "%{User-Agent}i %T"
|
|
|
|
/// ```
|
|
|
|
/// ```rust,ignore
|
|
|
|
///
|
|
|
|
/// let app = Application::default("/")
|
|
|
|
/// .middleware(Logger::default())
|
|
|
|
/// .middleware(Logger::new("%a %{User-Agent}i"))
|
|
|
|
/// .finish()
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// ## 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
|
|
|
|
///
|
|
|
|
/// `%P` The process ID of the child that serviced the request
|
|
|
|
///
|
|
|
|
/// `%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
|
|
|
|
///
|
|
|
|
/// `%{FOO}i` request.headers['FOO']
|
|
|
|
///
|
|
|
|
/// `%{FOO}o` response.headers['FOO']
|
|
|
|
///
|
|
|
|
/// `%{FOO}e` os.environ['FOO']
|
|
|
|
///
|
2017-10-22 07:59:09 +02:00
|
|
|
pub struct Logger {
|
|
|
|
format: Format,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Logger {
|
2017-11-10 21:29:54 +01:00
|
|
|
/// Create `Logger` middleware with the specified `format`.
|
|
|
|
pub fn new(format: &str) -> Logger {
|
|
|
|
Logger { format: Format::new(format) }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Logger {
|
|
|
|
/// Create default `Logger` middleware
|
|
|
|
fn default() -> Logger {
|
|
|
|
Logger { format: Format::default() }
|
2017-10-22 07:59:09 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct StartTime(time::Tm);
|
|
|
|
|
|
|
|
impl Logger {
|
|
|
|
|
|
|
|
fn log(&self, req: &mut HttpRequest, resp: &HttpResponse) {
|
|
|
|
let entry_time = req.extensions().get::<StartTime>().unwrap().0;
|
|
|
|
|
|
|
|
let response_time = time::now() - entry_time;
|
2017-11-10 07:08:54 +01:00
|
|
|
let response_time_ms = (response_time.num_seconds() * 1000) as f64 +
|
2017-11-10 21:29:54 +01:00
|
|
|
(response_time.num_nanoseconds().unwrap_or(0) as f64) / 1000000.0;
|
2017-10-22 07:59:09 +02:00
|
|
|
{
|
|
|
|
let render = |fmt: &mut Formatter, text: &FormatText| {
|
|
|
|
match *text {
|
|
|
|
FormatText::Str(ref string) => fmt.write_str(string),
|
2017-11-10 21:29:54 +01:00
|
|
|
FormatText::Percent => "%".fmt(fmt),
|
|
|
|
FormatText::RequestLine => {
|
2017-10-28 07:19:00 +02:00
|
|
|
if req.query_string().is_empty() {
|
2017-11-10 21:29:54 +01:00
|
|
|
fmt.write_fmt(format_args!(
|
|
|
|
"{} {} {:?}",
|
|
|
|
req.method(), req.path(), req.version()))
|
2017-10-28 07:19:00 +02:00
|
|
|
} else {
|
2017-11-10 21:29:54 +01:00
|
|
|
fmt.write_fmt(format_args!(
|
|
|
|
"{} {}?{} {:?}",
|
|
|
|
req.method(), req.path(), req.query_string(), req.version()))
|
2017-10-28 07:19:00 +02:00
|
|
|
}
|
|
|
|
},
|
2017-11-10 21:29:54 +01:00
|
|
|
FormatText::ResponseStatus => resp.status().as_u16().fmt(fmt),
|
|
|
|
FormatText::ResponseSize => resp.response_size().fmt(fmt),
|
|
|
|
FormatText::Time =>
|
|
|
|
fmt.write_fmt(format_args!("{:.6}", response_time_ms/1000.0)),
|
|
|
|
FormatText::TimeMillis =>
|
|
|
|
fmt.write_fmt(format_args!("{:.6}", response_time_ms)),
|
2017-10-22 07:59:09 +02:00
|
|
|
FormatText::RemoteAddr => Ok(()), //req.remote_addr.fmt(fmt),
|
|
|
|
FormatText::RequestTime => {
|
2017-11-10 21:29:54 +01:00
|
|
|
entry_time.strftime("[%d/%b/%Y:%H:%M:%S %z]")
|
2017-10-22 07:59:09 +02:00
|
|
|
.unwrap()
|
|
|
|
.fmt(fmt)
|
|
|
|
}
|
2017-11-10 21:29:54 +01:00
|
|
|
FormatText::RequestHeader(ref name) => {
|
|
|
|
let s = if let Some(val) = req.headers().get(name) {
|
|
|
|
if let Ok(s) = val.to_str() { s } else { "-" }
|
|
|
|
} else {
|
|
|
|
"-"
|
|
|
|
};
|
|
|
|
fmt.write_fmt(format_args!("{}", s))
|
|
|
|
}
|
|
|
|
FormatText::ResponseHeader(ref name) => {
|
|
|
|
let s = if let Some(val) = resp.headers().get(name) {
|
|
|
|
if let Ok(s) = val.to_str() { s } else { "-" }
|
|
|
|
} else {
|
|
|
|
"-"
|
|
|
|
};
|
|
|
|
fmt.write_fmt(format_args!("{}", s))
|
|
|
|
}
|
|
|
|
FormatText::EnvironHeader(ref name) => {
|
|
|
|
if let Ok(val) = env::var(name) {
|
|
|
|
fmt.write_fmt(format_args!("{}", val))
|
|
|
|
} else {
|
|
|
|
"-".fmt(fmt)
|
|
|
|
}
|
|
|
|
}
|
2017-10-22 07:59:09 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
info!("{}", self.format.display_with(&render));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Middleware for Logger {
|
2017-11-10 07:08:54 +01:00
|
|
|
|
|
|
|
fn start(&self, req: &mut HttpRequest) -> Started {
|
|
|
|
req.extensions().insert(StartTime(time::now()));
|
|
|
|
Started::Done
|
2017-10-22 07:59:09 +02:00
|
|
|
}
|
|
|
|
|
2017-11-10 07:08:54 +01:00
|
|
|
fn finish(&self, req: &mut HttpRequest, resp: &HttpResponse) -> Finished {
|
2017-10-22 07:59:09 +02:00
|
|
|
self.log(req, resp);
|
2017-11-10 07:08:54 +01:00
|
|
|
Finished::Done
|
2017-10-22 07:59:09 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// A formatting style for the `Logger`, consisting of multiple
|
|
|
|
/// `FormatText`s concatenated into one line.
|
|
|
|
#[derive(Clone)]
|
|
|
|
#[doc(hidden)]
|
2017-11-10 21:29:54 +01:00
|
|
|
struct Format(Vec<FormatText>);
|
2017-10-22 07:59:09 +02:00
|
|
|
|
|
|
|
impl Default for Format {
|
|
|
|
/// Return the default formatting style for the `Logger`:
|
|
|
|
///
|
|
|
|
/// ```ignore
|
2017-11-10 21:29:54 +01:00
|
|
|
/// %a %t "%r" %s %b "%{Referrer}i" "%{User-Agent}i %T"
|
2017-10-22 07:59:09 +02:00
|
|
|
/// ```
|
|
|
|
fn default() -> Format {
|
2017-11-10 21:29:54 +01:00
|
|
|
Format::new(r#"%a %t "%r" %s %b "%{Referrer}i" "%{User-Agent}i" %T"#)
|
2017-10-22 07:59:09 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Format {
|
2017-11-10 21:29:54 +01:00
|
|
|
/// Create a `Format` from a format string.
|
2017-10-22 07:59:09 +02:00
|
|
|
///
|
|
|
|
/// Returns `None` if the format string syntax is incorrect.
|
2017-11-10 21:29:54 +01:00
|
|
|
pub fn new(s: &str) -> Format {
|
|
|
|
trace!("Access log format: {}", s);
|
|
|
|
let fmt = Regex::new(r"%(\{([A-Za-z0-9\-_]+)\}([ioe])|[atPrsbTD]?)").unwrap();
|
2017-10-22 07:59:09 +02:00
|
|
|
|
2017-11-10 21:29:54 +01:00
|
|
|
let mut idx = 0;
|
2017-10-22 07:59:09 +02:00
|
|
|
let mut results = Vec::new();
|
2017-11-10 21:29:54 +01:00
|
|
|
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()));
|
2017-10-22 07:59:09 +02:00
|
|
|
}
|
2017-11-10 21:29:54 +01:00
|
|
|
idx = m.end();
|
|
|
|
|
|
|
|
if let Some(key) = cap.get(2) {
|
|
|
|
results.push(
|
|
|
|
match cap.get(3).unwrap().as_str() {
|
|
|
|
"i" => FormatText::RequestHeader(key.as_str().to_owned()),
|
|
|
|
"o" => FormatText::ResponseHeader(key.as_str().to_owned()),
|
|
|
|
"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,
|
|
|
|
"P" => FormatText::Percent,
|
|
|
|
"r" => FormatText::RequestLine,
|
|
|
|
"s" => FormatText::ResponseStatus,
|
|
|
|
"b" => FormatText::ResponseSize,
|
|
|
|
"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()));
|
2017-10-22 07:59:09 +02:00
|
|
|
}
|
|
|
|
|
2017-11-10 21:29:54 +01:00
|
|
|
Format(results)
|
2017-10-22 07:59:09 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) trait ContextDisplay<'a> {
|
|
|
|
type Item;
|
|
|
|
type Display: fmt::Display;
|
|
|
|
fn display_with(&'a self,
|
|
|
|
render: &'a Fn(&mut Formatter, &Self::Item) -> Result<(), fmt::Error>)
|
|
|
|
-> Self::Display;
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> ContextDisplay<'a> for Format {
|
|
|
|
type Item = FormatText;
|
|
|
|
type Display = FormatDisplay<'a>;
|
|
|
|
fn display_with(&'a self,
|
|
|
|
render: &'a Fn(&mut Formatter, &FormatText) -> Result<(), fmt::Error>)
|
|
|
|
-> FormatDisplay<'a> {
|
|
|
|
FormatDisplay {
|
|
|
|
format: self,
|
|
|
|
render: render,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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)]
|
2017-11-10 21:29:54 +01:00
|
|
|
#[derive(Debug, Clone)]
|
2017-10-22 07:59:09 +02:00
|
|
|
pub enum FormatText {
|
|
|
|
Str(String),
|
2017-11-10 21:29:54 +01:00
|
|
|
Percent,
|
|
|
|
RequestLine,
|
|
|
|
RequestTime,
|
|
|
|
ResponseStatus,
|
|
|
|
ResponseSize,
|
|
|
|
Time,
|
|
|
|
TimeMillis,
|
2017-10-22 07:59:09 +02:00
|
|
|
RemoteAddr,
|
2017-11-10 21:29:54 +01:00
|
|
|
RequestHeader(String),
|
|
|
|
ResponseHeader(String),
|
|
|
|
EnvironHeader(String),
|
2017-10-22 07:59:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) struct FormatDisplay<'a> {
|
|
|
|
format: &'a Format,
|
|
|
|
render: &'a Fn(&mut Formatter, &FormatText) -> Result<(), fmt::Error>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> fmt::Display for FormatDisplay<'a> {
|
|
|
|
fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> {
|
|
|
|
let Format(ref format) = *self.format;
|
|
|
|
for unit in format {
|
|
|
|
(self.render)(fmt, unit)?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|