1use actix_rt::spawn;
2use actix_web::{
3 dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
4 http::header::{HeaderValue, HOST, USER_AGENT},
5 Error,
6};
7use chrono::Utc;
8use futures::future::LocalBoxFuture;
9use lazy_static::lazy_static;
10use reqwest::Client;
11use serde::Serialize;
12use std::sync::{Arc, Mutex};
13use std::{
14 future::{ready, Ready},
15 time::Instant,
16};
17
18#[derive(Debug, Clone, Serialize)]
19struct RequestData {
20 hostname: String,
21 ip_address: String,
22 path: String,
23 user_agent: String,
24 method: String,
25 response_time: u32,
26 status: u16,
27 user_id: String,
28 created_at: String,
29}
30impl RequestData {
31 pub fn new(
32 hostname: String,
33 ip_address: String,
34 path: String,
35 user_agent: String,
36 method: String,
37 response_time: u32,
38 status: u16,
39 user_id: String,
40 created_at: String,
41 ) -> Self {
42 Self {
43 hostname,
44 ip_address,
45 path,
46 user_agent,
47 method,
48 response_time,
49 status,
50 user_id,
51 created_at,
52 }
53 }
54}
55
56type StringMapper = dyn for<'a> Fn(&ServiceRequest) -> String + Send + Sync;
57
58#[derive(Clone)]
59struct Config {
60 privacy_level: i32,
61 server_url: String,
62 get_hostname: Arc<StringMapper>,
63 get_ip_address: Arc<StringMapper>,
64 get_path: Arc<StringMapper>,
65 get_user_agent: Arc<StringMapper>,
66 get_user_id: Arc<StringMapper>,
67}
68
69impl Default for Config {
70 fn default() -> Self {
71 Self {
72 privacy_level: 0,
73 server_url: String::from("https://www.apianalytics-server.com/"),
74 get_hostname: Arc::new(get_hostname),
75 get_ip_address: Arc::new(get_ip_address),
76 get_path: Arc::new(get_path),
77 get_user_agent: Arc::new(get_user_agent),
78 get_user_id: Arc::new(get_user_id),
79 }
80 }
81}
82
83fn get_hostname(req: &ServiceRequest) -> String {
84 req.headers()
85 .get(HOST)
86 .map(|x| x.to_string())
87 .unwrap_or_default()
88}
89
90fn get_ip_address(req: &ServiceRequest) -> String {
91 if let Some(val) = req.peer_addr() {
92 return val.ip().to_string();
93 };
94 String::new()
95}
96
97fn get_path(req: &ServiceRequest) -> String {
98 req.path().to_string()
99}
100
101fn get_user_agent(req: &ServiceRequest) -> String {
102 req
103 .headers()
104 .get(USER_AGENT)
105 .map(|x| x.to_string())
106 .unwrap_or_default()
107}
108
109fn get_user_id(_req: &ServiceRequest) -> String {
110 String::new()
111}
112
113pub struct Analytics {
114 api_key: String,
115 config: Config,
116}
117
118impl Analytics {
119 pub fn new(api_key: String) -> Self {
120 Self {
121 api_key,
122 config: Config::default(),
123 }
124 }
125
126 pub fn with_privacy_level(mut self, privacy_level: i32) -> Self {
127 self.config.privacy_level = privacy_level;
128 self
129 }
130
131 pub fn with_server_url(mut self, server_url: String) -> Self {
132 if server_url.ends_with("/") {
133 self.config.server_url = server_url;
134 } else {
135 self.config.server_url = server_url + "/";
136 }
137 self
138 }
139
140 pub fn with_hostname_mapper<F>(mut self, mapper: F) -> Self
141 where
142 F: Fn(&ServiceRequest) -> String + Send + Sync + 'static,
143 {
144 self.config.get_hostname = Arc::new(mapper);
145 self
146 }
147
148 pub fn with_ip_address_mapper<F>(mut self, mapper: F) -> Self
149 where
150 F: Fn(&ServiceRequest) -> String + Send + Sync + 'static,
151 {
152 self.config.get_ip_address = Arc::new(mapper);
153 self
154 }
155
156 pub fn with_path_mapper<F>(mut self, mapper: F) -> Self
157 where
158 F: Fn(&ServiceRequest) -> String + Send + Sync + 'static,
159 {
160 self.config.get_path = Arc::new(mapper);
161 self
162 }
163
164 pub fn with_user_agent_mapper<F>(mut self, mapper: F) -> Self
165 where
166 F: Fn(&ServiceRequest) -> String + Send + Sync + 'static,
167 {
168 self.config.get_user_agent = Arc::new(mapper);
169 self
170 }
171}
172
173impl<S, B> Transform<S, ServiceRequest> for Analytics
174where
175 S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
176 S::Future: 'static,
177 B: 'static,
178{
179 type Response = ServiceResponse<B>;
180 type Error = Error;
181 type InitError = ();
182 type Transform = AnalyticsMiddleware<S>;
183 type Future = Ready<Result<Self::Transform, Self::InitError>>;
184
185 fn new_transform(&self, service: S) -> Self::Future {
186 ready(Ok(AnalyticsMiddleware {
187 api_key: Arc::new(self.api_key.clone()),
188 config: Arc::new(self.config.clone()),
189 service,
190 }))
191 }
192}
193
194pub struct AnalyticsMiddleware<S> {
195 api_key: Arc<String>,
196 config: Arc<Config>,
197 service: S,
198}
199
200pub trait HeaderValueExt {
201 fn to_string(&self) -> String;
202}
203
204impl HeaderValueExt for HeaderValue {
205 fn to_string(&self) -> String {
206 self.to_str().unwrap_or_default().to_string()
207 }
208}
209
210lazy_static! {
211 static ref REQUESTS: Mutex<Vec<RequestData>> = Mutex::new(vec![]);
212 static ref LAST_POSTED: Mutex<Instant> = Mutex::new(Instant::now());
213}
214
215#[derive(Debug, Clone, Serialize)]
216struct Payload {
217 api_key: String,
218 requests: Vec<RequestData>,
219 framework: String,
220 privacy_level: i32,
221}
222
223impl Payload {
224 pub fn new(api_key: String, requests: Vec<RequestData>, privacy_level: i32) -> Self {
225 Self {
226 api_key,
227 requests,
228 framework: String::from("Actix"),
229 privacy_level,
230 }
231 }
232}
233
234async fn post_requests(data: Payload, server_url: String) {
235 let _ = Client::new()
236 .post(server_url + "api/log-request")
237 .json(&data)
238 .send()
239 .await;
240}
241
242async fn log_request(api_key: &str, request_data: RequestData, config: &Config) {
243 REQUESTS.lock().unwrap().push(request_data);
244 if LAST_POSTED.lock().unwrap().elapsed().as_secs_f64() > 60.0 {
245 let payload = Payload::new(
246 api_key.to_owned(),
247 REQUESTS.lock().unwrap().to_vec(),
248 config.privacy_level,
249 );
250 let server_url = config.server_url.to_owned();
251 REQUESTS.lock().unwrap().clear();
252 post_requests(payload, server_url).await;
253 *LAST_POSTED.lock().unwrap() = Instant::now();
254 }
255}
256
257impl<S, B> Service<ServiceRequest> for AnalyticsMiddleware<S>
258where
259 S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
260 S::Future: 'static,
261 B: 'static,
262{
263 type Response = ServiceResponse<B>;
264 type Error = Error;
265 type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
266
267 forward_ready!(service);
268
269 fn call(&self, req: ServiceRequest) -> Self::Future {
270 let start = Instant::now();
271
272 let api_key = Arc::clone(&self.api_key);
273 let config = Arc::clone(&self.config);
274 let hostname = (self.config.get_hostname)(&req);
275 let ip_address = (self.config.get_ip_address)(&req);
276 let path = (self.config.get_path)(&req);
277 let method = req.method().to_string();
278 let user_agent = (self.config.get_hostname)(&req);
279 let user_id = (self.config.get_user_id)(&req);
280
281 let future = self.service.call(req);
282
283 Box::pin(async move {
284 let res = future.await?;
285 let elapsed = start.elapsed().as_millis();
286
287 let request_data = RequestData::new(
288 hostname,
289 ip_address,
290 path,
291 user_agent,
292 method,
293 elapsed.try_into().unwrap(),
294 res.status().as_u16(),
295 user_id,
296 Utc::now().to_rfc3339(),
297 );
298
299 spawn(async move { log_request(&api_key, request_data, &config).await });
300
301 Ok(res)
302 })
303 }
304}