rapid_web/
logger.rs

1use actix_web::{
2	dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
3	http::header::HeaderValue,
4	Error,
5};
6use colorful::{Color, Colorful};
7use futures_util::future::LocalBoxFuture;
8use log::info;
9use std::future::{ready, Ready};
10use std::io::Write;
11
12pub fn init_logger() {
13	pretty_env_logger::formatted_builder()
14		.filter_level(log::LevelFilter::Info)
15		.filter(Some("actix_server"), log::LevelFilter::Error)
16		.format(|buf, record| writeln!(buf, "{}: {}", record.level().to_string().color(Color::Cyan), record.args()))
17		.try_init()
18		.ok();
19}
20
21/// An enum for selecting a RapidLogger output type
22///
23/// # The RapidLogger has 3 different logging formats/variants
24/// 1. `RapidLogger::minimal`
25/// 2. `RapidLogger::detailed`
26/// 3. `RapidLogger::verbose`
27///
28/// # Example output
29/// INFO  rapid_web::logger > REQUEST GET /hello HTTP/1.1
30/// INFO  rapid_web::logger > RESPONSE 200 OK
31#[derive(Copy, Clone)]
32pub enum LoggerType {
33	Minimal,
34	Detailed,
35	Verbose,
36}
37
38pub struct RapidLogger {
39	logger_type: LoggerType,
40}
41
42/// Simple Middleware for logging request and response summaries to the console/terminal.
43///
44/// This middleware uses the `log` crate to output its logs and is enabled by default but can optionally be turned off as needed
45///
46/// # The RapidLogger has 3 different logging formats/variants
47/// 1. `RapidLogger::minimal`
48/// 2. `RapidLogger::detailed`
49/// 3. `RapidLogger::verbose`
50///
51/// # Example output
52/// INFO  rapid_web::logger > REQUEST GET /hello HTTP/1.1
53/// INFO  rapid_web::logger > RESPONSE 200 OK
54impl RapidLogger {
55	pub fn minimal() -> Self {
56		Self {
57			logger_type: LoggerType::Minimal,
58		}
59	}
60
61	pub fn detailed() -> Self {
62		Self {
63			logger_type: LoggerType::Detailed,
64		}
65	}
66
67	pub fn verbose() -> Self {
68		Self {
69			logger_type: LoggerType::Verbose,
70		}
71	}
72
73	fn minimal_request_logs(req: &ServiceRequest) {
74		let request_method = req.method().to_string().color(Color::LightBlue);
75		let request_path = req.path();
76		let request_http = req.version();
77
78		let logs = format!("REQUEST {} {} {:?}", request_method, request_path, request_http);
79		info!("{}", logs);
80	}
81
82	fn minimal_response_logs<B>(res: &ServiceResponse<B>) {
83		let response_status = res.status().to_string().color(Color::LightCyan);
84
85		info!("RESPONSE {}", response_status);
86	}
87
88	fn detailed_request_logs(req: &ServiceRequest) {
89		let request_method = req.method().to_string().color(Color::LightBlue);
90		let request_path = req.path();
91		let request_http = req.version();
92		let request_headers = req
93			.headers()
94			.keys()
95			.map(|key| (key.to_string(), req.headers().get(key.to_string()).unwrap()))
96			.collect::<Vec<(String, &HeaderValue)>>();
97		let is_secure = req.app_config().secure();
98		let request_connection_info = req.connection_info();
99		let request_uri = format!("{}{}", request_connection_info.host(), request_path);
100		let agent = match req.headers().get("user-agent") {
101			Some(agent) => agent.to_str().unwrap(),
102			None => "Not Found",
103		};
104
105		let logs = format!(
106			"REQUEST {} {} {} {:?} {} {} {}",
107			request_method,
108			request_path,
109			request_uri,
110			request_http,
111			format!("{}{:?}", "headers=".color(Color::LightBlue), request_headers),
112			format!("{}{}", "is_secure=".color(Color::LightBlue), is_secure),
113			format!("{}{}", "agent=".color(Color::LightBlue), agent)
114		);
115		info!("{}", logs);
116	}
117
118	fn detailed_response_logs<B>(res: &ServiceResponse<B>) {
119		let response_status = res.status().to_string().color(Color::LightCyan);
120		let response_headers = {
121			let headers = res
122				.headers()
123				.keys()
124				.map(|key| (key.to_string(), res.headers().get(key.to_string()).unwrap()))
125				.collect::<Vec<(String, &HeaderValue)>>();
126
127			if headers.len() == 0 {
128				"No Headers".to_string()
129			} else {
130				headers
131					.iter()
132					.map(|(key, value)| format!("{}: {}", key, value.to_str().unwrap()))
133					.collect::<Vec<String>>()
134					.join(", ")
135			}
136		};
137
138		let logs = format!(
139			"RESPONSE {} {}",
140			response_status,
141			format!("{}{:?}", "headers=".color(Color::LightCyan), response_headers)
142		);
143		info!("{}", logs);
144	}
145
146	// TODO: verbose will likely not be used becuase it is basically the same as detailed logs
147	// Currently, it exists for users that don't want formatted logs with the default rapid gradient colors (and for us to experiment with other logging techniques)
148	fn verbose_request_logs(req: &ServiceRequest) {
149		let is_secure = req.app_config().secure();
150		info!("{} {} {:?} is_secure={}", "[rapid-web::logger]", "REQUEST", req.request(), is_secure);
151	}
152
153	fn verbose_response_logs<B>(res: &ServiceResponse<B>) {
154		let response_status = res.status();
155		let response_headers = res.headers();
156
157		// TODO: eventually it would be nice to log response logs here as well (currently actix-web does not support this very well)
158		info!("{} {} {:?} {:?}", "[rapid-web::logger]", "RESPONSE", response_status, response_headers);
159	}
160
161	fn get_request_logs(req: &ServiceRequest, logger_type: LoggerType) {
162		match logger_type {
163			LoggerType::Minimal => Self::minimal_request_logs(req),
164			LoggerType::Detailed => Self::detailed_request_logs(req),
165			LoggerType::Verbose => Self::verbose_request_logs(req),
166		}
167	}
168
169	fn get_response_logs<B>(res: &ServiceResponse<B>, logger_type: LoggerType) {
170		match logger_type {
171			LoggerType::Minimal => Self::minimal_response_logs(res),
172			LoggerType::Detailed => Self::detailed_response_logs(res),
173			LoggerType::Verbose => Self::verbose_response_logs(res),
174		}
175	}
176}
177
178impl<S, B> Transform<S, ServiceRequest> for RapidLogger
179where
180	S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
181	S::Future: 'static,
182	B: 'static + actix_web::body::MessageBody,
183{
184	type Response = ServiceResponse<B>;
185	type Error = Error;
186	type InitError = ();
187	type Transform = RapidLoggerMiddleware<S>;
188	type Future = Ready<Result<Self::Transform, Self::InitError>>;
189
190	fn new_transform(&self, service: S) -> Self::Future {
191		ready(Ok(RapidLoggerMiddleware {
192			service,
193			log_type: self.logger_type,
194		}))
195	}
196}
197
198pub struct RapidLoggerMiddleware<S> {
199	service: S,
200	log_type: LoggerType,
201}
202
203impl<S, B> Service<ServiceRequest> for RapidLoggerMiddleware<S>
204where
205	S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
206	S::Future: 'static,
207	B: 'static + actix_web::body::MessageBody,
208{
209	type Response = ServiceResponse<B>;
210	type Error = Error;
211	type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
212
213	forward_ready!(service);
214
215	fn call(&self, req: ServiceRequest) -> Self::Future {
216		let cloned_log_type = self.log_type.clone();
217
218		// Log all of the request logs to the users console
219		RapidLogger::get_request_logs(&req, cloned_log_type);
220		let fut = self.service.call(req);
221
222		Box::pin(async move {
223			let res = fut.await?;
224			// Log all of the response logs to the users console
225			RapidLogger::get_response_logs(&res, cloned_log_type);
226			Ok(res)
227		})
228	}
229}