flare_core_runtime/health/
checker.rs1use super::HealthCheck;
6use std::sync::Arc;
7use tokio::sync::RwLock;
8use tracing::{debug, error, warn};
9
10type HealthFailureCallback = Arc<dyn Fn(&str) + Send + Sync>;
11
12#[derive(Debug, Clone)]
14pub struct HealthCheckResult {
15 pub name: String,
17 pub healthy: bool,
19 pub error: Option<String>,
21}
22
23pub struct HealthChecker {
40 checks: Vec<Arc<dyn HealthCheck>>,
41 failure_counts: Arc<RwLock<std::collections::HashMap<String, u32>>>,
42 failure_threshold: u32,
43 on_failure: Option<HealthFailureCallback>,
44}
45
46impl HealthChecker {
47 pub fn new() -> Self {
49 Self {
50 checks: Vec::new(),
51 failure_counts: Arc::new(RwLock::new(std::collections::HashMap::new())),
52 failure_threshold: 3,
53 on_failure: None,
54 }
55 }
56
57 pub fn with_failure_threshold(mut self, threshold: u32) -> Self {
59 self.failure_threshold = threshold;
60 self
61 }
62
63 pub fn with_on_failure(mut self, callback: HealthFailureCallback) -> Self {
65 self.on_failure = Some(callback);
66 self
67 }
68
69 pub fn add_check(&mut self, check: Arc<dyn HealthCheck>) {
75 let name = check.name().to_string();
76 self.checks.push(check);
77 debug!(check_name = %name, "Health check added");
78 }
79
80 pub async fn check_all(&self) -> Vec<HealthCheckResult> {
86 let mut results = Vec::new();
87
88 for check in &self.checks {
89 let result = self.check_one(check.as_ref()).await;
90 results.push(result);
91 }
92
93 results
94 }
95
96 async fn check_one(&self, check: &dyn HealthCheck) -> HealthCheckResult {
98 let name = check.name().to_string();
99
100 match check.check().await {
101 Ok(_) => {
102 let mut counts = self.failure_counts.write().await;
104 counts.insert(name.clone(), 0);
105
106 HealthCheckResult {
107 name: name.clone(),
108 healthy: true,
109 error: None,
110 }
111 }
112 Err(e) => {
113 let mut counts = self.failure_counts.write().await;
115 let count = counts.entry(name.clone()).or_insert(0);
116 *count += 1;
117
118 error!(
119 check_name = %name,
120 failure_count = *count,
121 threshold = self.failure_threshold,
122 error = %e,
123 "Health check failed"
124 );
125
126 if *count >= self.failure_threshold {
128 warn!(
129 check_name = %name,
130 "Health check failure threshold exceeded"
131 );
132
133 if let Some(callback) = &self.on_failure {
135 callback(&name);
136 }
137 }
138
139 HealthCheckResult {
140 name: name.clone(),
141 healthy: false,
142 error: Some(e.to_string()),
143 }
144 }
145 }
146 }
147
148 pub async fn is_healthy(&self) -> bool {
150 let results = self.check_all().await;
151 results.iter().all(|r| r.healthy)
152 }
153
154 pub fn check_count(&self) -> usize {
156 self.checks.len()
157 }
158}
159
160impl Default for HealthChecker {
161 fn default() -> Self {
162 Self::new()
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169 use crate::error::HealthError;
170 use std::future::Future;
171 use std::pin::Pin;
172
173 struct TestHealthCheck {
174 name: String,
175 healthy: bool,
176 }
177
178 impl HealthCheck for TestHealthCheck {
179 fn check(&self) -> Pin<Box<dyn Future<Output = Result<(), HealthError>> + Send + '_>> {
180 let healthy = self.healthy;
181 let name = self.name.clone();
182 Box::pin(async move {
183 if healthy {
184 Ok(())
185 } else {
186 Err(HealthError::CheckFailed {
187 name,
188 reason: "Test failure".to_string(),
189 })
190 }
191 })
192 }
193
194 fn name(&self) -> &str {
195 &self.name
196 }
197 }
198
199 #[tokio::test]
200 async fn test_health_checker_add() {
201 let mut checker = HealthChecker::new();
202 checker.add_check(Arc::new(TestHealthCheck {
203 name: "test-check".to_string(),
204 healthy: true,
205 }));
206
207 assert_eq!(checker.check_count(), 1);
208 }
209
210 #[tokio::test]
211 async fn test_health_checker_healthy() {
212 let mut checker = HealthChecker::new();
213 checker.add_check(Arc::new(TestHealthCheck {
214 name: "test-check".to_string(),
215 healthy: true,
216 }));
217
218 let is_healthy = checker.is_healthy().await;
219 assert!(is_healthy);
220 }
221
222 #[tokio::test]
223 async fn test_health_checker_unhealthy() {
224 let mut checker = HealthChecker::new();
225 checker.add_check(Arc::new(TestHealthCheck {
226 name: "test-check".to_string(),
227 healthy: false,
228 }));
229
230 let is_healthy = checker.is_healthy().await;
231 assert!(!is_healthy);
232 }
233}