//! Request logging middleware use std::fmt; use std::str::Chars; use std::iter::Peekable; use std::fmt::{Display, Formatter}; use time; use application::Middleware; use httprequest::HttpRequest; use httpresponse::HttpResponse; /// `Middleware` for logging request and response info to the terminal. pub struct Logger { format: Format, } impl Logger { /// Create `Logger` middlewares with the specified `format`. /// If a `None` is passed in, uses the default format: /// /// ```ignore /// {method} {uri} -> {status} ({response-time} ms) /// ``` /// /// ```rust,ignore /// let app = Application::default("/") /// .middleware(Logger::new(None)) /// .finish() /// ``` pub fn new(format: Option) -> Logger { let format = format.unwrap_or_default(); Logger { format: format.clone() } } } struct StartTime(time::Tm); impl Logger { fn initialise(&self, req: &mut HttpRequest) { req.extensions().insert(StartTime(time::now())); } fn log(&self, req: &mut HttpRequest, resp: &HttpResponse) { let entry_time = req.extensions().get::().unwrap().0; let response_time = time::now() - entry_time; let response_time_ms = (response_time.num_seconds() * 1000) as f64 + (response_time.num_nanoseconds().unwrap_or(0) as f64) / 1000000.0; { let render = |fmt: &mut Formatter, text: &FormatText| { match *text { FormatText::Str(ref string) => fmt.write_str(string), FormatText::Method => req.method().fmt(fmt), FormatText::URI => { if req.query_string().is_empty() { fmt.write_fmt(format_args!("{}", req.path())) } else { fmt.write_fmt(format_args!("{}?{}", req.path(), req.query_string())) } }, FormatText::Status => resp.status().fmt(fmt), FormatText::ResponseTime => fmt.write_fmt(format_args!("{} ms", response_time_ms)), FormatText::RemoteAddr => Ok(()), //req.remote_addr.fmt(fmt), FormatText::RequestTime => { entry_time.strftime("%Y-%m-%dT%H:%M:%S.%fZ%z") .unwrap() .fmt(fmt) } } }; info!("{}", self.format.display_with(&render)); } } } impl Middleware for Logger { fn start(&self, req: &mut HttpRequest) -> Result<(), HttpResponse> { self.initialise(req); Ok(()) } fn finish(&self, req: &mut HttpRequest, resp: &HttpResponse) { self.log(req, resp); } } use self::FormatText::{Method, URI, Status, ResponseTime, RemoteAddr, RequestTime}; /// A formatting style for the `Logger`, consisting of multiple /// `FormatText`s concatenated into one line. #[derive(Clone)] #[doc(hidden)] pub struct Format(Vec); impl Default for Format { /// Return the default formatting style for the `Logger`: /// /// ```ignore /// {method} {uri} -> {status} ({response-time}) /// // This will be written as: {method} {uri} -> {status} ({response-time}) /// ``` fn default() -> Format { Format::new("{method} {uri} {status} ({response-time})").unwrap() } } impl Format { /// Create a `Format` from a format string, which can contain the fields /// `{method}`, `{uri}`, `{status}`, `{response-time}`, `{ip-addr}` and /// `{request-time}`. /// /// Returns `None` if the format string syntax is incorrect. pub fn new(s: &str) -> Option { let parser = FormatParser::new(s.chars().peekable()); let mut results = Vec::new(); for unit in parser { match unit { Some(unit) => results.push(unit), None => return None } } Some(Format(results)) } } 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, } } } struct FormatParser<'a> { // The characters of the format string. chars: Peekable>, // A reusable buffer for parsing style attributes. object_buffer: String, finished: bool } impl<'a> FormatParser<'a> { fn new(chars: Peekable) -> FormatParser { FormatParser { chars: chars, // No attributes are longer than 14 characters, so we can avoid reallocating. object_buffer: String::with_capacity(14), finished: false } } } // Some(None) means there was a parse error and this FormatParser should be abandoned. impl<'a> Iterator for FormatParser<'a> { type Item = Option; fn next(&mut self) -> Option> { // If the parser has been cancelled or errored for some reason. if self.finished { return None } // Try to parse a new FormatText. match self.chars.next() { // Parse a recognized object. // // The allowed forms are: // - {method} // - {uri} // - {status} // - {response-time} // - {ip-addr} // - {request-time} Some('{') => { self.object_buffer.clear(); let mut chr = self.chars.next(); while chr != None { match chr.unwrap() { // Finished parsing, parse buffer. '}' => break, c => self.object_buffer.push(c) } chr = self.chars.next(); } let text = match self.object_buffer.as_ref() { "method" => Method, "uri" => URI, "status" => Status, "response-time" => ResponseTime, "request-time" => RequestTime, "ip-addr" => RemoteAddr, _ => { // Error, so mark as finished. self.finished = true; return Some(None); } }; Some(Some(text)) } // Parse a regular string part of the format string. Some(c) => { let mut buffer = String::new(); buffer.push(c); loop { match self.chars.peek() { // Done parsing. Some(&'{') | None => return Some(Some(FormatText::Str(buffer))), Some(_) => { buffer.push(self.chars.next().unwrap()) } } } }, // Reached end of the format string. None => None } } } /// A string of text to be logged. This is either one of the data /// fields supported by the `Logger`, or a custom `String`. #[derive(Clone)] #[doc(hidden)] pub enum FormatText { Str(String), Method, URI, Status, ResponseTime, RemoteAddr, RequestTime } 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(()) } }