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#[derive(Copy, Clone)]
32pub enum LoggerType {
33 Minimal,
34 Detailed,
35 Verbose,
36}
37
38pub struct RapidLogger {
39 logger_type: LoggerType,
40}
41
42impl 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 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 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 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 RapidLogger::get_response_logs(&res, cloned_log_type);
226 Ok(res)
227 })
228 }
229}