fraiseql_core/federation/
logging.rs1use std::time::Instant;
9
10use serde::Serialize;
11
12#[derive(Debug, Clone, Copy, Serialize)]
14pub enum FederationOperationType {
15 #[serde(rename = "entity_resolution")]
17 EntityResolution,
18 #[serde(rename = "service_schema")]
20 ServiceSchema,
21 #[serde(rename = "resolve_db")]
23 ResolveDb,
24 #[serde(rename = "resolve_http")]
26 ResolveHttp,
27 #[serde(rename = "mutation_execute")]
29 MutationExecute,
30}
31
32#[derive(Debug, Clone, Copy, Serialize)]
34pub enum ResolutionStrategy {
35 #[serde(rename = "local")]
37 Local,
38 #[serde(rename = "db")]
40 Db,
41 #[serde(rename = "http")]
43 Http,
44}
45
46#[derive(Debug, Clone, Serialize)]
48pub struct FederationLogContext {
49 pub operation_type: FederationOperationType,
51
52 pub query_id: String,
54
55 pub entity_count: usize,
57
58 pub entity_count_unique: Option<usize>,
60
61 pub strategy: Option<ResolutionStrategy>,
63
64 pub typename: Option<String>,
66
67 pub subgraph_name: Option<String>,
69
70 pub duration_ms: f64,
72
73 pub status: OperationStatus,
75
76 pub error_message: Option<String>,
78
79 pub http_status: Option<u16>,
81
82 pub resolved_count: Option<usize>,
84
85 pub trace_id: Option<String>,
87
88 pub request_id: Option<String>,
90}
91
92#[derive(Debug, Clone, Copy, Serialize)]
94pub enum OperationStatus {
95 #[serde(rename = "started")]
97 Started,
98 #[serde(rename = "success")]
100 Success,
101 #[serde(rename = "error")]
103 Error,
104 #[serde(rename = "timeout")]
106 Timeout,
107}
108
109impl FederationLogContext {
110 pub fn new(
112 operation_type: FederationOperationType,
113 query_id: String,
114 entity_count: usize,
115 ) -> Self {
116 Self {
117 operation_type,
118 query_id,
119 entity_count,
120 entity_count_unique: None,
121 strategy: None,
122 typename: None,
123 subgraph_name: None,
124 duration_ms: 0.0,
125 status: OperationStatus::Started,
126 error_message: None,
127 http_status: None,
128 resolved_count: None,
129 trace_id: None,
130 request_id: None,
131 }
132 }
133
134 pub fn with_strategy(mut self, strategy: ResolutionStrategy) -> Self {
136 self.strategy = Some(strategy);
137 self
138 }
139
140 pub fn with_typename(mut self, typename: String) -> Self {
142 self.typename = Some(typename);
143 self
144 }
145
146 pub fn with_subgraph_name(mut self, subgraph_name: String) -> Self {
148 self.subgraph_name = Some(subgraph_name);
149 self
150 }
151
152 pub fn with_entity_count_unique(mut self, count: usize) -> Self {
154 self.entity_count_unique = Some(count);
155 self
156 }
157
158 pub fn with_resolved_count(mut self, count: usize) -> Self {
160 self.resolved_count = Some(count);
161 self
162 }
163
164 pub fn with_http_status(mut self, status: u16) -> Self {
166 self.http_status = Some(status);
167 self
168 }
169
170 pub fn with_trace_id(mut self, trace_id: String) -> Self {
172 self.trace_id = Some(trace_id);
173 self
174 }
175
176 pub fn with_request_id(mut self, request_id: String) -> Self {
178 self.request_id = Some(request_id);
179 self
180 }
181
182 pub fn complete(mut self, duration_ms: f64) -> Self {
184 self.status = OperationStatus::Success;
185 self.duration_ms = duration_ms;
186 self
187 }
188
189 pub fn fail(mut self, duration_ms: f64, error_message: String) -> Self {
191 self.status = OperationStatus::Error;
192 self.duration_ms = duration_ms;
193 self.error_message = Some(error_message);
194 self
195 }
196
197 pub fn timeout(mut self, duration_ms: f64) -> Self {
199 self.status = OperationStatus::Timeout;
200 self.duration_ms = duration_ms;
201 self
202 }
203}
204
205pub struct LogTimer {
207 start: Instant,
208}
209
210impl LogTimer {
211 pub fn new() -> Self {
213 Self {
214 start: Instant::now(),
215 }
216 }
217
218 pub fn elapsed_ms(&self) -> f64 {
220 self.start.elapsed().as_secs_f64() * 1000.0
221 }
222}
223
224impl Default for LogTimer {
225 fn default() -> Self {
226 Self::new()
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233
234 #[test]
235 fn test_federation_log_context_creation() {
236 let ctx = FederationLogContext::new(
237 FederationOperationType::EntityResolution,
238 "query-123".to_string(),
239 10,
240 );
241
242 assert_eq!(ctx.entity_count, 10);
243 assert_eq!(ctx.query_id, "query-123");
244 assert!(ctx.typename.is_none());
245 assert!(ctx.error_message.is_none());
246 }
247
248 #[test]
249 fn test_federation_log_context_builder() {
250 let ctx = FederationLogContext::new(
251 FederationOperationType::ResolveDb,
252 "query-456".to_string(),
253 20,
254 )
255 .with_strategy(ResolutionStrategy::Db)
256 .with_typename("User".to_string())
257 .with_entity_count_unique(15)
258 .with_resolved_count(15)
259 .complete(25.5);
260
261 assert_eq!(ctx.entity_count, 20);
262 assert_eq!(ctx.entity_count_unique, Some(15));
263 assert_eq!(ctx.resolved_count, Some(15));
264 assert_eq!(ctx.duration_ms, 25.5);
265 assert!(matches!(ctx.status, OperationStatus::Success));
266 }
267
268 #[test]
269 fn test_federation_log_context_error() {
270 let ctx = FederationLogContext::new(
271 FederationOperationType::ResolveHttp,
272 "query-789".to_string(),
273 5,
274 )
275 .fail(15.2, "Connection refused".to_string());
276
277 assert!(matches!(ctx.status, OperationStatus::Error));
278 assert_eq!(ctx.error_message, Some("Connection refused".to_string()));
279 assert_eq!(ctx.duration_ms, 15.2);
280 }
281
282 #[test]
283 fn test_log_timer_elapsed() {
284 let timer = LogTimer::new();
285 std::thread::sleep(std::time::Duration::from_millis(10));
286 let elapsed = timer.elapsed_ms();
287 assert!(elapsed >= 10.0);
288 assert!(elapsed < 100.0); }
290
291 #[test]
292 fn test_federation_log_context_serialization() {
293 let ctx = FederationLogContext::new(
294 FederationOperationType::EntityResolution,
295 "query-123".to_string(),
296 10,
297 )
298 .with_strategy(ResolutionStrategy::Db)
299 .with_typename("User".to_string())
300 .complete(25.5);
301
302 let json = serde_json::to_string(&ctx).expect("JSON serialization failed");
303 assert!(json.contains("\"entity_count\":10"));
304 assert!(json.contains("\"duration_ms\":25.5"));
305 assert!(json.contains("\"status\":\"success\""));
306 }
307}