1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
//! Middlewares for the Gotham framework to log on requests made to the server.
//!
//! This module contains several logging implementations, with varying degrees
//! of complexity. The default `RequestLogger` will log out using the standard
//! [Common Log Format](https://en.wikipedia.org/wiki/Common_Log_Format) (CLF).
//!
//! There is also a `SimpleLogger` which emits only basic request logs.
use futures_util::future::{self, FutureExt, TryFutureExt};
use hyper::header::CONTENT_LENGTH;
use hyper::{Method, Uri, Version};
use log::{log, log_enabled, Level};
use std::pin::Pin;
use crate::handler::HandlerFuture;
use crate::helpers::timing::Timer;
use crate::middleware::{Middleware, NewMiddleware};
use crate::state::{client_addr, request_id, FromState, State};
/// A struct that can act as a logging middleware for Gotham.
///
/// We implement `NewMiddleware` here for Gotham to allow us to work with the request
/// lifecycle correctly. This trait requires `Clone`, so that is also included.
#[derive(Copy, Clone)]
pub struct RequestLogger {
level: Level,
}
impl RequestLogger {
/// Constructs a new `RequestLogger` instance.
pub fn new(level: Level) -> Self {
RequestLogger { level }
}
}
/// Implementation of `NewMiddleware` is required for Gotham middleware.
///
/// This will simply dereference the internal state, rather than deriving `NewMiddleware`
/// which will clone the structure - should be cheaper for repeated calls.
impl NewMiddleware for RequestLogger {
type Instance = Self;
/// Returns a new middleware to be used to serve a request.
fn new_middleware(&self) -> anyhow::Result<Self::Instance> {
Ok(*self)
}
}
/// Implementing `gotham::middleware::Middleware` allows us to hook into the request chain
/// in order to correctly log out after a request has executed.
impl Middleware for RequestLogger {
fn call<Chain>(self, state: State, chain: Chain) -> Pin<Box<HandlerFuture>>
where
Chain: FnOnce(State) -> Pin<Box<HandlerFuture>>,
{
// skip everything if logging is disabled
if !log_enabled!(self.level) {
return chain(state);
}
// extract the current time
let timer = Timer::new();
// hook onto the end of the request to log the access
let f = chain(state).and_then(move |(state, response)| {
// format the start time to the CLF formats
let datetime = {
use time::format_description::FormatItem;
use time::macros::format_description;
const DT_FORMAT: &[FormatItem<'static>]
= format_description!("[day]/[month repr:short]/[year]:[hour repr:24]:[minute]:[second] [offset_hour][offset_minute]");
timer.start_time().format(&DT_FORMAT).expect("Failed to format time")
};
// grab the ip address from the state
let ip = client_addr(&state).unwrap().ip();
{
// borrows from the state
let path = Uri::borrow_from(&state);
let method = Method::borrow_from(&state);
let version = Version::borrow_from(&state);
// take references based on the response
let status = response.status().as_u16();
let length = response
.headers()
.get(CONTENT_LENGTH)
.map(|len| len.to_str().unwrap())
.unwrap_or("0");
// log out
log!(
self.level,
"{} - - [{}] \"{} {} {:?}\" {} {} - {}",
ip,
datetime,
method,
path,
version,
status,
length,
timer.elapsed()
);
}
// continue the response chain
future::ok((state, response))
});
// box it up
f.boxed()
}
}
/// A struct that can act as a simple logging middleware for Gotham.
///
/// We implement `NewMiddleware` here for Gotham to allow us to work with the request
/// lifecycle correctly. This trait requires `Clone`, so that is also included.
#[derive(Copy, Clone)]
pub struct SimpleLogger {
level: Level,
}
impl SimpleLogger {
/// Constructs a new `SimpleLogger` instance.
pub fn new(level: Level) -> Self {
SimpleLogger { level }
}
}
/// Implementation of `NewMiddleware` is required for Gotham middleware.
///
/// This will simply dereference the internal state, rather than deriving `NewMiddleware`
/// which will clone the structure - should be cheaper for repeated calls.
impl NewMiddleware for SimpleLogger {
type Instance = Self;
/// Returns a new middleware to be used to serve a request.
fn new_middleware(&self) -> anyhow::Result<Self::Instance> {
Ok(*self)
}
}
/// Implementing `gotham::middleware::Middleware` allows us to hook into the request chain
/// in order to correctly log out after a request has executed.
impl Middleware for SimpleLogger {
fn call<Chain>(self, state: State, chain: Chain) -> Pin<Box<HandlerFuture>>
where
Chain: FnOnce(State) -> Pin<Box<HandlerFuture>>,
{
// skip everything if logging is disabled
if !log_enabled!(self.level) {
return chain(state);
}
// extract the current time
let timer = Timer::new();
// execute the request and chain the logging call
let f = chain(state).and_then(move |(state, response)| {
log!(
self.level,
"[RESPONSE][{}][{:?}][{}][{}]",
request_id(&state),
response.version(),
response.status(),
timer.elapsed()
);
future::ok((state, response))
});
f.boxed()
}
}