camel_api/
component_metrics.rs1use std::sync::Arc;
22
23use crate::metrics::MetricsCollector;
24
25pub struct ComponentMetrics {
29 collector: Arc<dyn MetricsCollector>,
30 components_enabled: bool,
31}
32
33impl ComponentMetrics {
34 pub fn new(collector: Arc<dyn MetricsCollector>, components_enabled: bool) -> Self {
38 Self {
39 collector,
40 components_enabled,
41 }
42 }
43
44 pub fn observe(&self, component: &str, operation: &str, failed: bool) {
49 if failed {
50 self.collector
51 .increment_errors(component, &format!("e:{component}:{operation}"));
52 }
53 if self.components_enabled {
54 self.collector.record_component_operation(
56 component,
57 operation,
58 if failed { "failure" } else { "success" },
59 );
60 }
61 }
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67 use crate::metrics::MetricsCollector;
68 use std::sync::{Arc, Mutex};
69 use std::time::Duration;
70
71 struct RecordingComponentMetrics {
73 errors: Mutex<Vec<(String, String)>>,
74 ops: Mutex<Vec<(String, String, String)>>,
75 }
76
77 impl RecordingComponentMetrics {
78 fn new() -> Self {
79 Self {
80 errors: Mutex::new(Vec::new()),
81 ops: Mutex::new(Vec::new()),
82 }
83 }
84 }
85
86 impl MetricsCollector for RecordingComponentMetrics {
87 fn record_exchange_duration(&self, _route_id: &str, _duration: Duration) {}
88 fn increment_errors(&self, route_id: &str, error_type: &str) {
89 self.errors
90 .lock()
91 .expect("errors lock")
92 .push((route_id.to_string(), error_type.to_string()));
93 }
94 fn increment_exchanges(&self, _route_id: &str) {}
95 fn set_queue_depth(&self, _queue: &str, _depth: usize) {}
96 fn record_circuit_breaker_change(&self, _route_id: &str, _from: &str, _to: &str) {}
97 fn record_component_operation(&self, component: &str, operation: &str, outcome: &str) {
98 self.ops.lock().expect("ops lock").push((
99 component.to_string(),
100 operation.to_string(),
101 outcome.to_string(),
102 ));
103 }
104 }
105
106 #[test]
109 fn facade_gates_components_not_errors() {
110 let off_collector = Arc::new(RecordingComponentMetrics::new());
113 let off = ComponentMetrics::new(
114 Arc::clone(&off_collector) as Arc<dyn MetricsCollector>,
115 false,
116 );
117 off.observe("redis", "command", true);
118 assert_eq!(
119 off_collector.errors.lock().expect("errors lock").clone(),
120 vec![("redis".to_string(), "e:redis:command".to_string())],
121 "failure must hit the error family with the lever off"
122 );
123 assert!(
124 off_collector.ops.lock().expect("ops lock").is_empty(),
125 "component ops must be suppressed with the lever off"
126 );
127
128 let on_collector = Arc::new(RecordingComponentMetrics::new());
131 let on =
132 ComponentMetrics::new(Arc::clone(&on_collector) as Arc<dyn MetricsCollector>, true);
133 on.observe("redis", "command", false);
134 on.observe("redis", "command", true);
135 assert_eq!(
136 on_collector.ops.lock().expect("ops lock").clone(),
137 vec![
138 (
139 "redis".to_string(),
140 "command".to_string(),
141 "success".to_string()
142 ),
143 (
144 "redis".to_string(),
145 "command".to_string(),
146 "failure".to_string()
147 ),
148 ],
149 "lever on must record both outcomes"
150 );
151 assert_eq!(
152 on_collector.errors.lock().expect("errors lock").clone(),
153 vec![("redis".to_string(), "e:redis:command".to_string())],
154 "failure must still increment errors with the lever on"
155 );
156 }
157}