gotham/middleware/
logger.rs

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