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}