1use serde::Serialize;
4use std::sync::{Arc, Mutex};
5use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
6
7#[derive(Clone, Debug, Serialize)]
8pub struct LogLine {
9 pub level: String,
10 pub target: String,
11 pub message: String,
12 pub request_id: Option<String>,
13 pub at_ms: u64,
14}
15
16#[derive(Clone, Debug, Serialize)]
17pub struct QueryLine {
18 pub sql: String,
19 pub duration_ms: Option<f64>,
20 pub rows: Option<u64>,
21}
22
23#[derive(Clone, Debug, Serialize)]
24pub struct HttpLine {
25 pub method: String,
26 pub url: String,
27 pub status: Option<u16>,
28 pub duration_ms: Option<f64>,
29 pub error: Option<String>,
30}
31
32#[derive(Clone, Debug, Serialize)]
33pub struct MailLine {
34 pub to: Vec<String>,
35 pub subject: String,
36 pub backend: String,
37}
38
39#[derive(Clone, Debug, Serialize)]
40pub struct JobLine {
41 pub name: String,
42 pub status: String,
43 pub detail: Option<String>,
44}
45
46#[derive(Clone, Debug, Default, Serialize)]
47pub struct AuthSnap {
48 pub session_id: Option<String>,
49 pub user_id: Option<String>,
50 pub session_keys: Vec<(String, String)>,
51}
52
53#[derive(Clone, Debug, Serialize)]
54pub struct RequestSnapshot {
55 pub id: String,
56 pub request_id: String,
57 pub method: String,
58 pub path: String,
59 pub status: u16,
60 pub duration_ms: f64,
61 pub at_ms: u64,
62 pub logs: Vec<LogLine>,
63 pub queries: Vec<QueryLine>,
64 pub http: Vec<HttpLine>,
65 pub mail: Vec<MailLine>,
66 pub jobs: Vec<JobLine>,
67 pub auth: AuthSnap,
68}
69
70#[derive(Clone, Debug, Serialize)]
71pub struct RequestMeta {
72 pub id: String,
73 pub request_id: String,
74 pub method: String,
75 pub path: String,
76 pub status: u16,
77 pub duration_ms: f64,
78 pub at_ms: u64,
79 pub sql_count: usize,
80 pub log_errors: usize,
81 pub http_count: usize,
82 pub mail_count: usize,
83}
84
85impl From<&RequestSnapshot> for RequestMeta {
86 fn from(s: &RequestSnapshot) -> Self {
87 Self {
88 id: s.id.clone(),
89 request_id: s.request_id.clone(),
90 method: s.method.clone(),
91 path: s.path.clone(),
92 status: s.status,
93 duration_ms: s.duration_ms,
94 at_ms: s.at_ms,
95 sql_count: s.queries.len(),
96 log_errors: s
97 .logs
98 .iter()
99 .filter(|l| l.level.eq_ignore_ascii_case("ERROR") || l.level == "ERROR")
100 .count(),
101 http_count: s.http.len(),
102 mail_count: s.mail.len(),
103 }
104 }
105}
106
107#[derive(Default)]
108struct BagInner {
109 logs: Vec<LogLine>,
110 queries: Vec<QueryLine>,
111 http: Vec<HttpLine>,
112 mail: Vec<MailLine>,
113 jobs: Vec<JobLine>,
114 auth: AuthSnap,
115}
116
117#[derive(Clone)]
119pub struct DevToolsBag {
120 pub id: String,
121 pub request_id: String,
122 pub method: String,
123 pub path: String,
124 pub started: Instant,
125 inner: Arc<Mutex<BagInner>>,
126}
127
128impl DevToolsBag {
129 pub fn new(id: String, request_id: String, method: String, path: String) -> Self {
130 Self {
131 id,
132 request_id,
133 method,
134 path,
135 started: Instant::now(),
136 inner: Arc::new(Mutex::new(BagInner::default())),
137 }
138 }
139
140 pub fn push_log(&self, line: LogLine) {
141 let mut g = self.inner.lock().unwrap();
142 if g.logs.len() < 200 {
143 g.logs.push(line);
144 }
145 }
146
147 pub fn push_query(&self, q: QueryLine) {
148 let mut g = self.inner.lock().unwrap();
149 if g.queries.len() < 200 {
150 g.queries.push(q);
151 }
152 }
153
154 pub fn push_http(&self, h: HttpLine) {
155 let mut g = self.inner.lock().unwrap();
156 if g.http.len() < 100 {
157 g.http.push(h);
158 }
159 }
160
161 pub fn push_mail(&self, m: MailLine) {
162 let mut g = self.inner.lock().unwrap();
163 if g.mail.len() < 50 {
164 g.mail.push(m);
165 }
166 }
167
168 pub fn push_job(&self, j: JobLine) {
169 let mut g = self.inner.lock().unwrap();
170 if g.jobs.len() < 50 {
171 g.jobs.push(j);
172 }
173 }
174
175 pub fn set_auth(&self, auth: AuthSnap) {
176 self.inner.lock().unwrap().auth = auth;
177 }
178
179 pub fn finish(self, status: u16) -> RequestSnapshot {
180 let duration_ms = self.started.elapsed().as_secs_f64() * 1000.0;
181 let at_ms = SystemTime::now()
182 .duration_since(UNIX_EPOCH)
183 .unwrap_or(Duration::ZERO)
184 .as_millis() as u64;
185 let inner = self.inner.lock().unwrap();
186 RequestSnapshot {
187 id: self.id,
188 request_id: self.request_id,
189 method: self.method,
190 path: self.path,
191 status,
192 duration_ms,
193 at_ms,
194 logs: inner.logs.clone(),
195 queries: inner.queries.clone(),
196 http: inner.http.clone(),
197 mail: inner.mail.clone(),
198 jobs: inner.jobs.clone(),
199 auth: inner.auth.clone(),
200 }
201 }
202}
203
204pub fn now_ms() -> u64 {
205 SystemTime::now()
206 .duration_since(UNIX_EPOCH)
207 .unwrap_or(Duration::ZERO)
208 .as_millis() as u64
209}