1use std::collections::HashMap;
26
27#[derive(Debug, Clone)]
29pub struct DaemonMetric {
30 pub name: String,
31 pub state: String,
32 pub event_count: u64,
33 pub restart_count: u32,
34}
35
36#[derive(Debug, Clone)]
38pub struct ClientRateLimitMetric {
39 pub client_key: String,
40 pub total_requests: u64,
41 pub rejected: u64,
42}
43
44#[derive(Debug, Clone)]
46pub struct TopicMetric {
47 pub topic: String,
48 pub published: u64,
49}
50
51#[derive(Debug, Clone)]
53pub struct FlowMetric {
54 pub flow_name: String,
55 pub executions: u64,
56 pub errors: u64,
57 pub avg_latency_ms: u64,
58}
59
60#[derive(Debug, Clone)]
62pub struct ServerSnapshot {
63 pub uptime_secs: u64,
64 pub server_start_timestamp: u64,
65 pub total_requests: u64,
66 pub total_deployments: u64,
67 pub total_errors: u64,
68 pub active_daemons: u32,
69 pub daemon_states: HashMap<String, u32>,
70 pub daemon_metrics: Vec<DaemonMetric>,
71 pub daemon_total_restarts: u64,
72 pub daemon_total_events: u64,
73 pub bus_events_published: u64,
74 pub bus_events_delivered: u64,
75 pub bus_events_dropped: u64,
76 pub bus_topics_seen: usize,
77 pub bus_active_subscribers: usize,
78 pub bus_topic_metrics: Vec<TopicMetric>,
79 pub flows_tracked: usize,
80 pub versions_total: usize,
81 pub session_memory_count: usize,
82 pub session_store_count: usize,
83 pub deploy_count: u64,
84 pub rate_limiter_enabled: bool,
86 pub rate_limiter_clients: usize,
87 pub rate_limiter_max_requests: u32,
88 pub rate_limiter_window_secs: u64,
89 pub rate_limiter_client_metrics: Vec<ClientRateLimitMetric>,
90 pub request_log_enabled: bool,
92 pub request_log_buffered: usize,
93 pub request_log_capacity: usize,
94 pub request_log_total: u64,
95 pub request_log_errors: u64,
96 pub api_keys_enabled: bool,
98 pub api_keys_active: usize,
99 pub api_keys_total: usize,
100 pub webhooks_total: usize,
102 pub webhooks_active: usize,
103 pub webhooks_deliveries_total: u64,
104 pub webhooks_failures_total: u64,
105 pub audit_buffered: usize,
107 pub audit_total_recorded: u64,
108 pub middleware_enabled: bool,
110 pub middleware_requests_total: u64,
111 pub middleware_slow_threshold_ms: u64,
112 pub cors_enabled: bool,
114 pub cors_permissive: bool,
115 pub trace_enabled: bool,
117 pub trace_buffered: usize,
118 pub trace_capacity: usize,
119 pub trace_total_recorded: u64,
120 pub trace_total_executions: u64,
121 pub trace_total_errors: u64,
122 pub flow_metrics: Vec<FlowMetric>,
123 pub schedules_total: usize,
125 pub schedules_enabled: usize,
126 pub schedules_total_runs: u64,
127 pub schedules_total_errors: u64,
128 pub schedules_avg_interval_secs: u64,
129 pub shutdown_initiated: bool,
131}
132
133pub fn to_prometheus(snap: &ServerSnapshot) -> String {
135 let mut out = String::new();
136
137 prom_gauge(&mut out, "axon_server_uptime_seconds", "Server uptime in seconds.", snap.uptime_secs);
139 prom_gauge(&mut out, "axon_server_start_timestamp", "Server start time (Unix seconds).", snap.server_start_timestamp);
140
141 prom_counter(&mut out, "axon_server_requests_total", "Total API requests handled.", snap.total_requests);
143
144 prom_counter(&mut out, "axon_server_deployments_total", "Total flow deployments.", snap.total_deployments);
146 prom_counter(&mut out, "axon_server_deploy_count", "Total deploy operations.", snap.deploy_count);
147
148 prom_counter(&mut out, "axon_server_errors_total", "Total errors encountered.", snap.total_errors);
150
151 prom_gauge(&mut out, "axon_server_daemons_active", "Number of active daemons.", snap.active_daemons as u64);
153
154 if !snap.daemon_states.is_empty() {
156 out.push_str("# HELP axon_server_daemons_by_state Daemons by lifecycle state.\n");
157 out.push_str("# TYPE axon_server_daemons_by_state gauge\n");
158 let mut states: Vec<_> = snap.daemon_states.iter().collect();
159 states.sort_by_key(|(k, _)| (*k).clone());
160 for (state, count) in states {
161 out.push_str(&format!("axon_server_daemons_by_state{{state=\"{}\"}} {}\n", state, count));
162 }
163 out.push('\n');
164 }
165
166 prom_counter(&mut out, "axon_server_daemon_total_restarts", "Total daemon restarts across all daemons.", snap.daemon_total_restarts);
168 prom_counter(&mut out, "axon_server_daemon_total_events", "Total events processed across all daemons.", snap.daemon_total_events);
169
170 if !snap.daemon_metrics.is_empty() {
172 out.push_str("# HELP axon_server_daemon_event_count Events processed by daemon.\n");
173 out.push_str("# TYPE axon_server_daemon_event_count counter\n");
174 let mut sorted: Vec<_> = snap.daemon_metrics.iter().collect();
175 sorted.sort_by(|a, b| a.name.cmp(&b.name));
176 for dm in &sorted {
177 out.push_str(&format!(
178 "axon_server_daemon_event_count{{daemon=\"{}\",state=\"{}\"}} {}\n",
179 dm.name, dm.state, dm.event_count
180 ));
181 }
182 out.push('\n');
183
184 out.push_str("# HELP axon_server_daemon_restart_count Restart count by daemon.\n");
185 out.push_str("# TYPE axon_server_daemon_restart_count counter\n");
186 for dm in &sorted {
187 out.push_str(&format!(
188 "axon_server_daemon_restart_count{{daemon=\"{}\",state=\"{}\"}} {}\n",
189 dm.name, dm.state, dm.restart_count
190 ));
191 }
192 out.push('\n');
193 }
194
195 prom_counter(&mut out, "axon_server_bus_events_published", "Total events published on the bus.", snap.bus_events_published);
197 prom_counter(&mut out, "axon_server_bus_events_delivered", "Total events delivered to subscribers.", snap.bus_events_delivered);
198 prom_counter(&mut out, "axon_server_bus_events_dropped", "Total events dropped (no subscriber).", snap.bus_events_dropped);
199 prom_gauge(&mut out, "axon_server_bus_topics_seen", "Unique event topics seen.", snap.bus_topics_seen as u64);
200 prom_gauge(&mut out, "axon_server_bus_active_subscribers", "Active event bus subscribers.", snap.bus_active_subscribers as u64);
201
202 if !snap.bus_topic_metrics.is_empty() {
204 out.push_str("# HELP axon_server_bus_topic_published Events published per topic.\n");
205 out.push_str("# TYPE axon_server_bus_topic_published counter\n");
206 let mut sorted: Vec<_> = snap.bus_topic_metrics.iter().collect();
207 sorted.sort_by(|a, b| a.topic.cmp(&b.topic));
208 for tm in &sorted {
209 out.push_str(&format!(
210 "axon_server_bus_topic_published{{topic=\"{}\"}} {}\n",
211 tm.topic, tm.published
212 ));
213 }
214 out.push('\n');
215 }
216
217 prom_gauge(&mut out, "axon_server_flows_tracked", "Number of tracked flows.", snap.flows_tracked as u64);
219 prom_gauge(&mut out, "axon_server_versions_total", "Total flow versions across all flows.", snap.versions_total as u64);
220
221 prom_gauge(&mut out, "axon_server_session_memory_count", "Ephemeral session memory entries.", snap.session_memory_count as u64);
223 prom_gauge(&mut out, "axon_server_session_store_count", "Persistent session store entries.", snap.session_store_count as u64);
224
225 prom_gauge(&mut out, "axon_server_rate_limiter_enabled", "Whether rate limiting is enabled.", snap.rate_limiter_enabled as u64);
227 prom_gauge(&mut out, "axon_server_rate_limiter_clients", "Number of tracked rate-limit clients.", snap.rate_limiter_clients as u64);
228 prom_gauge(&mut out, "axon_server_rate_limiter_max_requests", "Max requests per window.", snap.rate_limiter_max_requests as u64);
229 prom_gauge(&mut out, "axon_server_rate_limiter_window_secs", "Rate limit window in seconds.", snap.rate_limiter_window_secs);
230
231 if !snap.rate_limiter_client_metrics.is_empty() {
233 out.push_str("# HELP axon_server_rate_limiter_client_requests Total requests per client.\n");
234 out.push_str("# TYPE axon_server_rate_limiter_client_requests counter\n");
235 let mut sorted: Vec<_> = snap.rate_limiter_client_metrics.iter().collect();
236 sorted.sort_by(|a, b| a.client_key.cmp(&b.client_key));
237 for cm in &sorted {
238 out.push_str(&format!(
239 "axon_server_rate_limiter_client_requests{{client=\"{}\"}} {}\n",
240 cm.client_key, cm.total_requests
241 ));
242 }
243 out.push('\n');
244
245 out.push_str("# HELP axon_server_rate_limiter_client_rejected Rejected requests per client.\n");
246 out.push_str("# TYPE axon_server_rate_limiter_client_rejected counter\n");
247 for cm in &sorted {
248 out.push_str(&format!(
249 "axon_server_rate_limiter_client_rejected{{client=\"{}\"}} {}\n",
250 cm.client_key, cm.rejected
251 ));
252 }
253 out.push('\n');
254 }
255
256 prom_gauge(&mut out, "axon_server_request_log_enabled", "Whether request logging is enabled.", snap.request_log_enabled as u64);
258 prom_gauge(&mut out, "axon_server_request_log_buffered", "Entries currently in request log buffer.", snap.request_log_buffered as u64);
259 prom_gauge(&mut out, "axon_server_request_log_capacity", "Max capacity of request log buffer.", snap.request_log_capacity as u64);
260 prom_counter(&mut out, "axon_server_request_log_total", "Total requests recorded by request log.", snap.request_log_total);
261 prom_counter(&mut out, "axon_server_request_log_errors", "Total error responses recorded.", snap.request_log_errors);
262
263 prom_gauge(&mut out, "axon_server_api_keys_enabled", "Whether API key auth is enabled.", snap.api_keys_enabled as u64);
265 prom_gauge(&mut out, "axon_server_api_keys_active", "Number of active (non-revoked) API keys.", snap.api_keys_active as u64);
266 prom_gauge(&mut out, "axon_server_api_keys_total", "Total API keys (including revoked).", snap.api_keys_total as u64);
267
268 prom_gauge(&mut out, "axon_server_webhooks_total", "Total registered webhooks.", snap.webhooks_total as u64);
270 prom_gauge(&mut out, "axon_server_webhooks_active", "Active (enabled) webhooks.", snap.webhooks_active as u64);
271 prom_counter(&mut out, "axon_server_webhooks_deliveries_total", "Total webhook deliveries attempted.", snap.webhooks_deliveries_total);
272 prom_counter(&mut out, "axon_server_webhooks_failures_total", "Total webhook delivery failures.", snap.webhooks_failures_total);
273
274 prom_gauge(&mut out, "axon_server_audit_buffered", "Entries currently in audit log buffer.", snap.audit_buffered as u64);
276 prom_counter(&mut out, "axon_server_audit_total_recorded", "Total audit entries recorded.", snap.audit_total_recorded);
277
278 prom_gauge(&mut out, "axon_server_middleware_enabled", "Whether request middleware is enabled.", snap.middleware_enabled as u64);
280 prom_counter(&mut out, "axon_server_middleware_requests_total", "Total requests processed by middleware.", snap.middleware_requests_total);
281 prom_gauge(&mut out, "axon_server_middleware_slow_threshold_ms", "Slow request threshold in milliseconds.", snap.middleware_slow_threshold_ms);
282
283 prom_gauge(&mut out, "axon_server_cors_enabled", "Whether CORS is enabled.", snap.cors_enabled as u64);
285 prom_gauge(&mut out, "axon_server_cors_permissive", "Whether CORS is in permissive (wildcard) mode.", snap.cors_permissive as u64);
286
287 prom_gauge(&mut out, "axon_server_trace_enabled", "Whether trace recording is enabled.", snap.trace_enabled as u64);
289 prom_gauge(&mut out, "axon_server_trace_buffered", "Number of traces currently in buffer.", snap.trace_buffered as u64);
290 prom_gauge(&mut out, "axon_server_trace_capacity", "Maximum trace buffer capacity.", snap.trace_capacity as u64);
291 prom_counter(&mut out, "axon_server_trace_total_recorded", "Total traces recorded (including evicted).", snap.trace_total_recorded);
292 prom_counter(&mut out, "axon_server_trace_total_executions", "Total flow executions via server.", snap.trace_total_executions);
293 prom_counter(&mut out, "axon_server_trace_total_errors", "Total execution errors recorded in traces.", snap.trace_total_errors);
294
295 if !snap.flow_metrics.is_empty() {
297 out.push_str("# HELP axon_server_flow_executions Total executions per flow.\n");
298 out.push_str("# TYPE axon_server_flow_executions counter\n");
299 let mut sorted: Vec<_> = snap.flow_metrics.iter().collect();
300 sorted.sort_by(|a, b| a.flow_name.cmp(&b.flow_name));
301 for fm in &sorted {
302 out.push_str(&format!("axon_server_flow_executions{{flow=\"{}\"}} {}\n", fm.flow_name, fm.executions));
303 }
304 out.push('\n');
305
306 out.push_str("# HELP axon_server_flow_errors Total errors per flow.\n");
307 out.push_str("# TYPE axon_server_flow_errors counter\n");
308 for fm in &sorted {
309 out.push_str(&format!("axon_server_flow_errors{{flow=\"{}\"}} {}\n", fm.flow_name, fm.errors));
310 }
311 out.push('\n');
312
313 out.push_str("# HELP axon_server_flow_avg_latency_ms Average latency per flow in milliseconds.\n");
314 out.push_str("# TYPE axon_server_flow_avg_latency_ms gauge\n");
315 for fm in &sorted {
316 out.push_str(&format!("axon_server_flow_avg_latency_ms{{flow=\"{}\"}} {}\n", fm.flow_name, fm.avg_latency_ms));
317 }
318 out.push('\n');
319 }
320
321 prom_gauge(&mut out, "axon_server_schedules_total", "Total registered schedules.", snap.schedules_total as u64);
323 prom_gauge(&mut out, "axon_server_schedules_enabled", "Number of enabled schedules.", snap.schedules_enabled as u64);
324 prom_counter(&mut out, "axon_server_schedules_total_runs", "Total scheduled flow executions.", snap.schedules_total_runs);
325 prom_counter(&mut out, "axon_server_schedules_total_errors", "Total errors from scheduled executions.", snap.schedules_total_errors);
326 prom_gauge(&mut out, "axon_server_schedules_avg_interval_secs", "Average schedule interval in seconds.", snap.schedules_avg_interval_secs);
327
328 prom_gauge(&mut out, "axon_server_shutdown_initiated", "Whether graceful shutdown has been initiated.", snap.shutdown_initiated as u64);
330
331 out
332}
333
334fn prom_gauge(out: &mut String, name: &str, help: &str, value: u64) {
335 out.push_str(&format!("# HELP {} {}\n", name, help));
336 out.push_str(&format!("# TYPE {} gauge\n", name));
337 out.push_str(&format!("{} {}\n\n", name, value));
338}
339
340fn prom_counter(out: &mut String, name: &str, help: &str, value: u64) {
341 out.push_str(&format!("# HELP {} {}\n", name, help));
342 out.push_str(&format!("# TYPE {} counter\n", name));
343 out.push_str(&format!("{} {}\n\n", name, value));
344}
345
346#[cfg(test)]
349mod tests {
350 use super::*;
351
352 fn sample_snapshot() -> ServerSnapshot {
353 let mut daemon_states = HashMap::new();
354 daemon_states.insert("idle".to_string(), 2);
355 daemon_states.insert("running".to_string(), 1);
356
357 ServerSnapshot {
358 uptime_secs: 3600,
359 server_start_timestamp: 1700000000,
360 total_requests: 150,
361 total_deployments: 10,
362 total_errors: 3,
363 active_daemons: 3,
364 daemon_states,
365 daemon_metrics: vec![
366 DaemonMetric { name: "worker-1".into(), state: "running".into(), event_count: 42, restart_count: 1 },
367 DaemonMetric { name: "worker-2".into(), state: "idle".into(), event_count: 10, restart_count: 0 },
368 ],
369 daemon_total_restarts: 1,
370 daemon_total_events: 52,
371 bus_events_published: 50,
372 bus_events_delivered: 45,
373 bus_events_dropped: 5,
374 bus_topics_seen: 8,
375 bus_active_subscribers: 2,
376 bus_topic_metrics: vec![
377 TopicMetric { topic: "deploy".into(), published: 10 },
378 TopicMetric { topic: "daemon.started".into(), published: 5 },
379 ],
380 flows_tracked: 4,
381 versions_total: 12,
382 session_memory_count: 5,
383 session_store_count: 3,
384 deploy_count: 10,
385 rate_limiter_enabled: true,
386 rate_limiter_clients: 3,
387 rate_limiter_max_requests: 100,
388 rate_limiter_window_secs: 60,
389 rate_limiter_client_metrics: vec![
390 ClientRateLimitMetric { client_key: "user-1".into(), total_requests: 50, rejected: 2 },
391 ],
392 request_log_enabled: true,
393 request_log_buffered: 42,
394 request_log_capacity: 1000,
395 request_log_total: 150,
396 request_log_errors: 5,
397 api_keys_enabled: true,
398 api_keys_active: 3,
399 api_keys_total: 5,
400 webhooks_total: 4,
401 webhooks_active: 3,
402 webhooks_deliveries_total: 20,
403 webhooks_failures_total: 2,
404 audit_buffered: 100,
405 audit_total_recorded: 250,
406 middleware_enabled: true,
407 middleware_requests_total: 150,
408 middleware_slow_threshold_ms: 5000,
409 cors_enabled: true,
410 cors_permissive: true,
411 trace_enabled: true,
412 trace_buffered: 25,
413 trace_capacity: 500,
414 trace_total_recorded: 42,
415 trace_total_executions: 42,
416 trace_total_errors: 3,
417 flow_metrics: vec![
418 FlowMetric { flow_name: "Pipeline".into(), executions: 50, errors: 3, avg_latency_ms: 120 },
419 ],
420 schedules_total: 3,
421 schedules_enabled: 2,
422 schedules_total_runs: 15,
423 schedules_total_errors: 1,
424 schedules_avg_interval_secs: 120,
425 shutdown_initiated: false,
426 }
427 }
428
429 #[test]
430 fn prometheus_contains_uptime() {
431 let prom = to_prometheus(&sample_snapshot());
432 assert!(prom.contains("axon_server_uptime_seconds 3600"));
433 assert!(prom.contains("# TYPE axon_server_uptime_seconds gauge"));
434 }
435
436 #[test]
437 fn prometheus_contains_requests() {
438 let prom = to_prometheus(&sample_snapshot());
439 assert!(prom.contains("axon_server_requests_total 150"));
440 assert!(prom.contains("# TYPE axon_server_requests_total counter"));
441 }
442
443 #[test]
444 fn prometheus_contains_deployments() {
445 let prom = to_prometheus(&sample_snapshot());
446 assert!(prom.contains("axon_server_deployments_total 10"));
447 }
448
449 #[test]
450 fn prometheus_contains_errors() {
451 let prom = to_prometheus(&sample_snapshot());
452 assert!(prom.contains("axon_server_errors_total 3"));
453 }
454
455 #[test]
456 fn prometheus_contains_daemons() {
457 let prom = to_prometheus(&sample_snapshot());
458 assert!(prom.contains("axon_server_daemons_active 3"));
459 assert!(prom.contains("axon_server_daemons_by_state{state=\"idle\"} 2"));
460 assert!(prom.contains("axon_server_daemons_by_state{state=\"running\"} 1"));
461 }
462
463 #[test]
464 fn prometheus_contains_bus_metrics() {
465 let prom = to_prometheus(&sample_snapshot());
466 assert!(prom.contains("axon_server_bus_events_published 50"));
467 assert!(prom.contains("axon_server_bus_events_delivered 45"));
468 assert!(prom.contains("axon_server_bus_events_dropped 5"));
469 assert!(prom.contains("axon_server_bus_topics_seen 8"));
470 assert!(prom.contains("axon_server_bus_active_subscribers 2"));
471 }
472
473 #[test]
474 fn prometheus_contains_versions() {
475 let prom = to_prometheus(&sample_snapshot());
476 assert!(prom.contains("axon_server_flows_tracked 4"));
477 assert!(prom.contains("axon_server_versions_total 12"));
478 }
479
480 #[test]
481 fn prometheus_contains_session() {
482 let prom = to_prometheus(&sample_snapshot());
483 assert!(prom.contains("axon_server_session_memory_count 5"));
484 assert!(prom.contains("axon_server_session_store_count 3"));
485 }
486
487 #[test]
488 fn prometheus_has_help_and_type_for_all() {
489 let prom = to_prometheus(&sample_snapshot());
490 let help_count = prom.lines().filter(|l| l.starts_with("# HELP")).count();
492 let type_count = prom.lines().filter(|l| l.starts_with("# TYPE")).count();
493 assert!(help_count >= 56);
494 assert_eq!(help_count, type_count);
495 }
496
497 #[test]
498 fn prometheus_empty_daemon_states() {
499 let mut snap = sample_snapshot();
500 snap.daemon_states.clear();
501 let prom = to_prometheus(&snap);
502 assert!(!prom.contains("axon_server_daemons_by_state"));
503 }
504
505 #[test]
506 fn prometheus_zero_snapshot() {
507 let snap = ServerSnapshot {
508 uptime_secs: 0,
509 server_start_timestamp: 0,
510 total_requests: 0,
511 total_deployments: 0,
512 total_errors: 0,
513 active_daemons: 0,
514 daemon_states: HashMap::new(),
515 daemon_metrics: Vec::new(),
516 daemon_total_restarts: 0,
517 daemon_total_events: 0,
518 bus_events_published: 0,
519 bus_events_delivered: 0,
520 bus_events_dropped: 0,
521 bus_topics_seen: 0,
522 bus_active_subscribers: 0,
523 bus_topic_metrics: Vec::new(),
524 flows_tracked: 0,
525 versions_total: 0,
526 session_memory_count: 0,
527 session_store_count: 0,
528 deploy_count: 0,
529 rate_limiter_enabled: false,
530 rate_limiter_clients: 0,
531 rate_limiter_max_requests: 0,
532 rate_limiter_window_secs: 0,
533 rate_limiter_client_metrics: Vec::new(),
534 request_log_enabled: false,
535 request_log_buffered: 0,
536 request_log_capacity: 0,
537 request_log_total: 0,
538 request_log_errors: 0,
539 api_keys_enabled: false,
540 api_keys_active: 0,
541 api_keys_total: 0,
542 webhooks_total: 0,
543 webhooks_active: 0,
544 webhooks_deliveries_total: 0,
545 webhooks_failures_total: 0,
546 audit_buffered: 0,
547 audit_total_recorded: 0,
548 middleware_enabled: false,
549 middleware_requests_total: 0,
550 middleware_slow_threshold_ms: 0,
551 cors_enabled: false,
552 cors_permissive: false,
553 trace_enabled: false,
554 trace_buffered: 0,
555 trace_capacity: 0,
556 trace_total_recorded: 0,
557 trace_total_executions: 0,
558 trace_total_errors: 0,
559 flow_metrics: Vec::new(),
560 schedules_total: 0,
561 schedules_enabled: 0,
562 schedules_total_runs: 0,
563 schedules_total_errors: 0,
564 schedules_avg_interval_secs: 0,
565 shutdown_initiated: false,
566 };
567 let prom = to_prometheus(&snap);
568 assert!(prom.contains("axon_server_uptime_seconds 0"));
569 assert!(prom.contains("axon_server_requests_total 0"));
570 }
571
572 #[test]
573 fn prometheus_contains_rate_limiter() {
574 let prom = to_prometheus(&sample_snapshot());
575 assert!(prom.contains("axon_server_rate_limiter_enabled 1"));
576 assert!(prom.contains("axon_server_rate_limiter_clients 3"));
577 assert!(prom.contains("axon_server_rate_limiter_max_requests 100"));
578 assert!(prom.contains("axon_server_rate_limiter_window_secs 60"));
579 }
580
581 #[test]
582 fn prometheus_contains_request_log() {
583 let prom = to_prometheus(&sample_snapshot());
584 assert!(prom.contains("axon_server_request_log_enabled 1"));
585 assert!(prom.contains("axon_server_request_log_buffered 42"));
586 assert!(prom.contains("axon_server_request_log_capacity 1000"));
587 assert!(prom.contains("axon_server_request_log_total 150"));
588 assert!(prom.contains("axon_server_request_log_errors 5"));
589 }
590
591 #[test]
592 fn prometheus_contains_api_keys() {
593 let prom = to_prometheus(&sample_snapshot());
594 assert!(prom.contains("axon_server_api_keys_enabled 1"));
595 assert!(prom.contains("axon_server_api_keys_active 3"));
596 assert!(prom.contains("axon_server_api_keys_total 5"));
597 }
598
599 #[test]
600 fn prometheus_contains_webhooks() {
601 let prom = to_prometheus(&sample_snapshot());
602 assert!(prom.contains("axon_server_webhooks_total 4"));
603 assert!(prom.contains("axon_server_webhooks_active 3"));
604 assert!(prom.contains("axon_server_webhooks_deliveries_total 20"));
605 assert!(prom.contains("axon_server_webhooks_failures_total 2"));
606 }
607
608 #[test]
609 fn prometheus_contains_audit() {
610 let prom = to_prometheus(&sample_snapshot());
611 assert!(prom.contains("axon_server_audit_buffered 100"));
612 assert!(prom.contains("axon_server_audit_total_recorded 250"));
613 }
614
615 #[test]
616 fn prometheus_contains_middleware() {
617 let prom = to_prometheus(&sample_snapshot());
618 assert!(prom.contains("axon_server_middleware_enabled 1"));
619 assert!(prom.contains("axon_server_middleware_requests_total 150"));
620 assert!(prom.contains("axon_server_middleware_slow_threshold_ms 5000"));
621 }
622
623 #[test]
624 fn prometheus_contains_cors() {
625 let prom = to_prometheus(&sample_snapshot());
626 assert!(prom.contains("axon_server_cors_enabled 1"));
627 assert!(prom.contains("axon_server_cors_permissive 1"));
628 }
629
630 #[test]
631 fn prometheus_contains_shutdown() {
632 let prom = to_prometheus(&sample_snapshot());
633 assert!(prom.contains("axon_server_shutdown_initiated 0"));
634 }
635
636 #[test]
637 fn prometheus_contains_trace_store() {
638 let prom = to_prometheus(&sample_snapshot());
639 assert!(prom.contains("axon_server_trace_enabled 1"));
640 assert!(prom.contains("axon_server_trace_buffered 25"));
641 assert!(prom.contains("axon_server_trace_capacity 500"));
642 assert!(prom.contains("axon_server_trace_total_recorded 42"));
643 assert!(prom.contains("axon_server_trace_total_executions 42"));
644 assert!(prom.contains("axon_server_trace_total_errors 3"));
645 }
646
647 #[test]
648 fn prometheus_valid_exposition_format() {
649 let prom = to_prometheus(&sample_snapshot());
650 for line in prom.lines() {
652 if line.is_empty() || line.starts_with('#') {
653 continue;
654 }
655 assert!(line.contains(' '), "Invalid line: {}", line);
657 }
658 }
659}