├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── default.rs └── formatstring.rs └── src ├── format.rs └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *~ 3 | *# 4 | *.o 5 | *.so 6 | *.swp 7 | *.dylib 8 | *.dSYM 9 | *.dll 10 | *.rlib 11 | *.dummy 12 | *.exe 13 | *-test 14 | /target/ 15 | Cargo.lock 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - nightly 4 | - stable 5 | - beta 6 | sudo: false 7 | env: 8 | global: 9 | secure: GBUs51VTpYXiQMunI1tslOpRpLweMdQG5Pnf+8k54tbhGDWjdlGssuhf7k9VknskpfBi3D2CesVsAI/rLJ6vkahCPpKj2uHZbmU1I8/z3ram+2/M1pcV5ehW3Cgf5oK/CsI3rzyDZhszmuQWqEpd6Q+rvcFqbKzM8+cBdyPpEVo= 10 | matrix: 11 | include: 12 | - rust: stable 13 | env: IRON_VERSION=0.4.0 14 | - rust: stable 15 | env: IRON_VERSION=0.5.0 16 | script: 17 | # We need to crate a Cargo.lock before updating to a precise version. 18 | - "cargo update" 19 | - '[[ -z "$IRON_VERSION" ]] || cargo update -p iron --precise "$IRON_VERSION"' 20 | - 'cargo build --verbose' 21 | - 'cargo test --verbose' 22 | 23 | after_success: 'curl https://raw.githubusercontent.com/iron-bot/build-doc/master/build-doc.sh | sh' 24 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | **logger** uses the same conventions as **[Iron](https://github.com/iron/iron)**. 4 | 5 | ### Overview 6 | 7 | * Fork logger to your own account 8 | * Create a feature branch, namespaced by. 9 | * bug/... 10 | * feat/... 11 | * test/... 12 | * doc/... 13 | * refactor/... 14 | * Make commits to your feature branch. Prefix each commit like so: 15 | * (feat) Added a new feature 16 | * (fix) Fixed inconsistent tests [Fixes #0] 17 | * (refactor) ... 18 | * (cleanup) ... 19 | * (test) ... 20 | * (doc) ... 21 | * Make a pull request with your changes directly to master. Include a 22 | description of your changes. 23 | * Wait for one of the reviewers to look at your code and either merge it or 24 | give feedback which you should adapt to. 25 | 26 | #### Thank you for contributing! 27 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "logger" 3 | version = "0.4.0" 4 | authors = ["Alexander Irbis ", "Jonathan Reem ", "Michael Reinhard "] 5 | description = "Logging middleware for the Iron framework." 6 | repository = "https://github.com/iron/logger" 7 | keywords = ["iron", "web", "logger", "log", "timer"] 8 | license = "MIT" 9 | 10 | [dependencies] 11 | iron = { version = ">=0.4,<0.8.0", default-features = false } 12 | log = "0.4.8" 13 | time = "0.1.42" 14 | 15 | [dev-dependencies] 16 | env_logger = "0.7.1" 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 iron 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Project moved to [`iron/iron`](https://github.com/iron/iron) 2 | 3 | This project has been moved to the `iron/iron` monorepo. You can find the latest version of `logger` at https://github.com/iron/iron/tree/master/logger. 4 | 5 | All pull requests and issues should be created there instead. 6 | -------------------------------------------------------------------------------- /examples/default.rs: -------------------------------------------------------------------------------- 1 | //! Example of a simple logger 2 | extern crate iron; 3 | extern crate logger; 4 | extern crate env_logger; 5 | 6 | use iron::prelude::*; 7 | use logger::Logger; 8 | 9 | // Logger has a default formatting of the strings printed 10 | // to console. 11 | fn main() { 12 | env_logger::init(); 13 | 14 | let (logger_before, logger_after) = Logger::new(None); 15 | 16 | let mut chain = Chain::new(no_op_handler); 17 | 18 | // Link logger_before as your first before middleware. 19 | chain.link_before(logger_before); 20 | 21 | // Link logger_after as your *last* after middleware. 22 | chain.link_after(logger_after); 23 | 24 | println!("Run `RUST_LOG=logger=info cargo run --example default` to see logs."); 25 | match Iron::new(chain).http("127.0.0.1:3000") { 26 | Result::Ok(listening) => println!("{:?}", listening), 27 | Result::Err(err) => panic!("{:?}", err), 28 | } 29 | } 30 | 31 | fn no_op_handler(_: &mut Request) -> IronResult { 32 | Ok(Response::with(iron::status::Ok)) 33 | } 34 | -------------------------------------------------------------------------------- /examples/formatstring.rs: -------------------------------------------------------------------------------- 1 | //! Example of logger with custom formatting 2 | extern crate iron; 3 | extern crate logger; 4 | extern crate env_logger; 5 | 6 | use iron::prelude::*; 7 | 8 | use logger::Logger; 9 | use logger::Format; 10 | 11 | static FORMAT: &'static str = 12 | "Uri: {uri}, Method: {method}, Status: {status}, Duration: {response-time}, Time: {request-time}"; 13 | 14 | // This is an example of using a format string that can specify colors and attributes 15 | // to specific words that are printed out to the console. 16 | fn main() { 17 | env_logger::init(); 18 | 19 | let mut chain = Chain::new(no_op_handler); 20 | let format = Format::new(FORMAT); 21 | chain.link(Logger::new(Some(format.unwrap()))); 22 | 23 | println!("Run `RUST_LOG=info cargo run --example formatstring` to see logs."); 24 | match Iron::new(chain).http("127.0.0.1:3000") { 25 | Result::Ok(listening) => println!("{:?}", listening), 26 | Result::Err(err) => panic!("{:?}", err), 27 | } 28 | } 29 | 30 | fn no_op_handler(_: &mut Request) -> IronResult { 31 | Ok(Response::with(iron::status::Ok)) 32 | } 33 | -------------------------------------------------------------------------------- /src/format.rs: -------------------------------------------------------------------------------- 1 | //! Formatting helpers for the logger middleware. 2 | 3 | use std::default::Default; 4 | use std::str::Chars; 5 | use std::iter::Peekable; 6 | use std::fmt::Formatter; 7 | 8 | use self::FormatText::{Method, URI, Status, ResponseTime, RemoteAddr, RequestTime}; 9 | 10 | /// A formatting style for the `Logger`, consisting of multiple 11 | /// `FormatText`s concatenated into one line. 12 | #[derive(Clone)] 13 | pub struct Format(Vec); 14 | 15 | impl Default for Format { 16 | /// Return the default formatting style for the `Logger`: 17 | /// 18 | /// ```ignore 19 | /// {method} {uri} -> {status} ({response-time}) 20 | /// // This will be written as: {method} {uri} -> {status} ({response-time}) 21 | /// ``` 22 | fn default() -> Format { 23 | Format::new("{method} {uri} {status} ({response-time})").unwrap() 24 | } 25 | } 26 | 27 | impl Format { 28 | /// Create a `Format` from a format string, which can contain the fields 29 | /// `{method}`, `{uri}`, `{status}`, `{response-time}`, `{ip-addr}` and 30 | /// `{request-time}`. 31 | /// 32 | /// Returns `None` if the format string syntax is incorrect. 33 | pub fn new(s: &str) -> Option { 34 | 35 | let parser = FormatParser::new(s.chars().peekable()); 36 | 37 | let mut results = Vec::new(); 38 | 39 | for unit in parser { 40 | match unit { 41 | Some(unit) => results.push(unit), 42 | None => return None 43 | } 44 | } 45 | 46 | Some(Format(results)) 47 | } 48 | } 49 | 50 | pub trait ContextDisplay<'a> { 51 | type Item; 52 | type Display: fmt::Display; 53 | fn display_with(&'a self, 54 | render: &'a dyn Fn(&mut Formatter, &Self::Item) -> Result<(), fmt::Error>) 55 | -> Self::Display; 56 | } 57 | 58 | 59 | impl<'a> ContextDisplay<'a> for Format { 60 | type Item = FormatText; 61 | type Display = FormatDisplay<'a>; 62 | fn display_with(&'a self, 63 | render: &'a dyn Fn(&mut Formatter, &FormatText) -> Result<(), fmt::Error>) 64 | -> FormatDisplay<'a> { 65 | FormatDisplay { 66 | format: self, 67 | render: render, 68 | } 69 | } 70 | } 71 | 72 | struct FormatParser<'a> { 73 | // The characters of the format string. 74 | chars: Peekable>, 75 | 76 | // A reusable buffer for parsing style attributes. 77 | object_buffer: String, 78 | 79 | finished: bool 80 | } 81 | 82 | impl<'a> FormatParser<'a> { 83 | fn new(chars: Peekable) -> FormatParser { 84 | FormatParser { 85 | chars: chars, 86 | 87 | // No attributes are longer than 14 characters, so we can avoid reallocating. 88 | object_buffer: String::with_capacity(14), 89 | 90 | finished: false 91 | } 92 | } 93 | } 94 | 95 | // Some(None) means there was a parse error and this FormatParser should be abandoned. 96 | impl<'a> Iterator for FormatParser<'a> { 97 | type Item = Option; 98 | 99 | fn next(&mut self) -> Option> { 100 | // If the parser has been cancelled or errored for some reason. 101 | if self.finished { return None } 102 | 103 | // Try to parse a new FormatText. 104 | match self.chars.next() { 105 | // Parse a recognized object. 106 | // 107 | // The allowed forms are: 108 | // - {method} 109 | // - {uri} 110 | // - {status} 111 | // - {response-time} 112 | // - {ip-addr} 113 | // - {request-time} 114 | Some('{') => { 115 | self.object_buffer.clear(); 116 | 117 | let mut chr = self.chars.next(); 118 | while chr != None { 119 | match chr.unwrap() { 120 | // Finished parsing, parse buffer. 121 | '}' => break, 122 | c => self.object_buffer.push(c.clone()) 123 | } 124 | 125 | chr = self.chars.next(); 126 | } 127 | 128 | let text = match self.object_buffer.as_ref() { 129 | "method" => Method, 130 | "uri" => URI, 131 | "status" => Status, 132 | "response-time" => ResponseTime, 133 | "request-time" => RequestTime, 134 | "ip-addr" => RemoteAddr, 135 | _ => { 136 | // Error, so mark as finished. 137 | self.finished = true; 138 | return Some(None); 139 | } 140 | }; 141 | 142 | Some(Some(text)) 143 | } 144 | 145 | // Parse a regular string part of the format string. 146 | Some(c) => { 147 | let mut buffer = String::new(); 148 | buffer.push(c); 149 | 150 | loop { 151 | match self.chars.peek() { 152 | // Done parsing. 153 | Some(&'{') | None => return Some(Some(FormatText::Str(buffer))), 154 | 155 | Some(_) => { 156 | buffer.push(self.chars.next().unwrap()) 157 | } 158 | } 159 | } 160 | }, 161 | 162 | // Reached end of the format string. 163 | None => None 164 | } 165 | } 166 | } 167 | 168 | /// A string of text to be logged. This is either one of the data 169 | /// fields supported by the `Logger`, or a custom `String`. 170 | #[derive(Clone)] 171 | #[doc(hidden)] 172 | pub enum FormatText { 173 | Str(String), 174 | Method, 175 | URI, 176 | Status, 177 | ResponseTime, 178 | RemoteAddr, 179 | RequestTime 180 | } 181 | 182 | 183 | pub struct FormatDisplay<'a> { 184 | format: &'a Format, 185 | render: &'a dyn Fn(&mut Formatter, &FormatText) -> Result<(), fmt::Error>, 186 | } 187 | 188 | use std::fmt; 189 | impl<'a> fmt::Display for FormatDisplay<'a> { 190 | fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> { 191 | let Format(ref format) = *self.format; 192 | for unit in format { 193 | (self.render)(fmt, unit)?; 194 | } 195 | Ok(()) 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(missing_docs, warnings)] 2 | 3 | //! Request logging middleware for Iron 4 | 5 | extern crate iron; 6 | #[macro_use] extern crate log; 7 | extern crate time; 8 | 9 | use iron::{AfterMiddleware, BeforeMiddleware, IronResult, IronError, Request, Response}; 10 | use iron::typemap::Key; 11 | 12 | use format::FormatText::{Str, Method, URI, Status, ResponseTime, RemoteAddr, RequestTime}; 13 | use format::{ContextDisplay, FormatText}; 14 | 15 | use std::fmt::{Display, Formatter}; 16 | 17 | mod format; 18 | pub use format::Format; 19 | 20 | /// `Middleware` for logging request and response info to the terminal. 21 | pub struct Logger { 22 | format: Format, 23 | } 24 | 25 | impl Logger { 26 | /// Create a pair of `Logger` middlewares with the specified `format`. If a `None` is passed in, uses the default format: 27 | /// 28 | /// ```ignore 29 | /// {method} {uri} -> {status} ({response-time} ms) 30 | /// ``` 31 | /// 32 | /// While the returned value can be passed straight to `Chain::link`, consider making the logger `BeforeMiddleware` 33 | /// the first in your chain and the logger `AfterMiddleware` the last by doing something like this: 34 | /// 35 | /// ```ignore 36 | /// let mut chain = Chain::new(handler); 37 | /// let (logger_before, logger_after) = Logger::new(None); 38 | /// chain.link_before(logger_before); 39 | /// // link other middlewares here... 40 | /// chain.link_after(logger_after); 41 | /// ``` 42 | pub fn new(format: Option) -> (Logger, Logger) { 43 | let format = format.unwrap_or_default(); 44 | (Logger { format: format.clone() }, Logger { format: format }) 45 | } 46 | } 47 | 48 | struct StartTime; 49 | impl Key for StartTime { type Value = time::Tm; } 50 | 51 | impl Logger { 52 | fn initialise(&self, req: &mut Request) { 53 | req.extensions.insert::(time::now()); 54 | } 55 | 56 | fn log(&self, req: &mut Request, res: &Response) -> IronResult<()> { 57 | let entry_time = *req.extensions.get::().unwrap(); 58 | 59 | let response_time = time::now() - entry_time; 60 | let response_time_ms = (response_time.num_nanoseconds().unwrap_or(0) as f64) / 1000000.0; 61 | 62 | { 63 | let render = |fmt: &mut Formatter, text: &FormatText| { 64 | match *text { 65 | Str(ref string) => fmt.write_str(string), 66 | Method => req.method.fmt(fmt), 67 | URI => req.url.fmt(fmt), 68 | Status => { 69 | match res.status { 70 | Some(status) => status.fmt(fmt), 71 | None => fmt.write_str(""), 72 | } 73 | } 74 | ResponseTime => fmt.write_fmt(format_args!("{} ms", response_time_ms)), 75 | RemoteAddr => req.remote_addr.fmt(fmt), 76 | RequestTime => { 77 | entry_time.strftime("%Y-%m-%dT%H:%M:%S.%fZ%z") 78 | .unwrap() 79 | .fmt(fmt) 80 | } 81 | } 82 | }; 83 | 84 | info!("{}", self.format.display_with(&render)); 85 | } 86 | 87 | Ok(()) 88 | } 89 | } 90 | 91 | impl BeforeMiddleware for Logger { 92 | fn before(&self, req: &mut Request) -> IronResult<()> { 93 | self.initialise(req); 94 | Ok(()) 95 | } 96 | 97 | fn catch(&self, req: &mut Request, err: IronError) -> IronResult<()> { 98 | self.initialise(req); 99 | Err(err) 100 | } 101 | } 102 | 103 | impl AfterMiddleware for Logger { 104 | fn after(&self, req: &mut Request, res: Response) -> IronResult { 105 | self.log(req, &res)?; 106 | Ok(res) 107 | } 108 | 109 | fn catch(&self, req: &mut Request, err: IronError) -> IronResult { 110 | self.log(req, &err.response)?; 111 | Err(err) 112 | } 113 | } 114 | --------------------------------------------------------------------------------