1use std::sync::atomic::{AtomicBool, Ordering};
6use std::sync::Arc;
7use std::time::{Duration, Instant};
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub enum HealthStatus {
11 Healthy,
12 Degraded,
13 Unhealthy,
14}
15
16impl std::fmt::Display for HealthStatus {
17 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18 match self {
19 HealthStatus::Healthy => write!(f, "healthy"),
20 HealthStatus::Degraded => write!(f, "degraded"),
21 HealthStatus::Unhealthy => write!(f, "unhealthy"),
22 }
23 }
24}
25
26pub struct HealthCheck {
27 ready: Arc<AtomicBool>,
28 alive: Arc<AtomicBool>,
29 last_heartbeat: Arc<std::sync::Mutex<Instant>>,
30 checks: Arc<std::sync::Mutex<Vec<Check>>>,
31}
32
33struct Check {
34 name: String,
35 status: HealthStatus,
36 message: String,
37 last_check: Instant,
38}
39
40impl HealthCheck {
41 pub fn new() -> Self {
42 Self {
43 ready: Arc::new(AtomicBool::new(true)),
44 alive: Arc::new(AtomicBool::new(true)),
45 last_heartbeat: Arc::new(std::sync::Mutex::new(Instant::now())),
46 checks: Arc::new(std::sync::Mutex::new(Vec::new())),
47 }
48 }
49
50 pub fn set_ready(&self, ready: bool) {
52 self.ready.store(ready, Ordering::Release);
53 }
54
55 pub fn is_ready(&self) -> bool {
57 self.ready.load(Ordering::Acquire)
58 }
59
60 pub fn set_alive(&self, alive: bool) {
62 self.alive.store(alive, Ordering::Release);
63 }
64
65 pub fn is_alive(&self) -> bool {
67 self.alive.load(Ordering::Acquire)
68 }
69
70 pub fn heartbeat(&self) {
72 let mut last = self.last_heartbeat.lock().unwrap();
73 *last = Instant::now();
74 }
75
76 pub fn is_heartbeat_recent(&self, threshold: Duration) -> bool {
78 let last = self.last_heartbeat.lock().unwrap();
79 last.elapsed() < threshold
80 }
81
82 pub fn add_check(&self, name: impl Into<String>, status: HealthStatus, message: impl Into<String>) {
84 let mut checks = self.checks.lock().unwrap();
85 checks.push(Check {
86 name: name.into(),
87 status,
88 message: message.into(),
89 last_check: Instant::now(),
90 });
91 }
92
93 pub fn get_status(&self) -> HealthStatus {
95 if !self.is_alive() {
96 return HealthStatus::Unhealthy;
97 }
98
99 if !self.is_ready() {
100 return HealthStatus::Degraded;
101 }
102
103 let checks = self.checks.lock().unwrap();
104 let has_unhealthy = checks.iter().any(|c| c.status == HealthStatus::Unhealthy);
105 let has_degraded = checks.iter().any(|c| c.status == HealthStatus::Degraded);
106
107 if has_unhealthy {
108 HealthStatus::Unhealthy
109 } else if has_degraded {
110 HealthStatus::Degraded
111 } else {
112 HealthStatus::Healthy
113 }
114 }
115
116 pub fn get_report(&self) -> HealthReport {
118 let checks = self.checks.lock().unwrap();
119 let last_heartbeat = self.last_heartbeat.lock().unwrap();
120
121 HealthReport {
122 status: self.get_status(),
123 ready: self.is_ready(),
124 alive: self.is_alive(),
125 last_heartbeat: last_heartbeat.elapsed(),
126 checks: checks
127 .iter()
128 .map(|c| CheckReport {
129 name: c.name.clone(),
130 status: c.status,
131 message: c.message.clone(),
132 age: c.last_check.elapsed(),
133 })
134 .collect(),
135 }
136 }
137
138 pub fn clear_checks(&self) {
140 let mut checks = self.checks.lock().unwrap();
141 checks.clear();
142 }
143}
144
145impl Default for HealthCheck {
146 fn default() -> Self {
147 Self::new()
148 }
149}
150
151impl Clone for HealthCheck {
152 fn clone(&self) -> Self {
153 Self {
154 ready: Arc::clone(&self.ready),
155 alive: Arc::clone(&self.alive),
156 last_heartbeat: Arc::clone(&self.last_heartbeat),
157 checks: Arc::clone(&self.checks),
158 }
159 }
160}
161
162#[derive(Debug, Clone)]
163pub struct HealthReport {
164 pub status: HealthStatus,
165 pub ready: bool,
166 pub alive: bool,
167 pub last_heartbeat: Duration,
168 pub checks: Vec<CheckReport>,
169}
170
171#[derive(Debug, Clone)]
172pub struct CheckReport {
173 pub name: String,
174 pub status: HealthStatus,
175 pub message: String,
176 pub age: Duration,
177}
178
179impl std::fmt::Display for HealthReport {
180 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
181 writeln!(f, "Health Status: {}", self.status)?;
182 writeln!(f, "Ready: {} | Alive: {}", self.ready, self.alive)?;
183 writeln!(f, "Last Heartbeat: {:?} ago", self.last_heartbeat)?;
184
185 if !self.checks.is_empty() {
186 writeln!(f, "\nChecks:")?;
187 for check in &self.checks {
188 writeln!(
189 f,
190 " - {} [{}]: {} (checked {:?} ago)",
191 check.name, check.status, check.message, check.age
192 )?;
193 }
194 }
195
196 Ok(())
197 }
198}
199
200impl HealthReport {
201 pub fn to_json(&self) -> String {
203 format!(
204 r#"{{"status":"{}","ready":{},"alive":{},"last_heartbeat_ms":{},"checks":[{}]}}"#,
205 self.status,
206 self.ready,
207 self.alive,
208 self.last_heartbeat.as_millis(),
209 self.checks
210 .iter()
211 .map(|c| format!(
212 r#"{{"name":"{}","status":"{}","message":"{}"}}"#,
213 c.name, c.status, c.message
214 ))
215 .collect::<Vec<_>>()
216 .join(",")
217 )
218 }
219}
220
221
222
223
224