bws_web_server/monitoring/
health.rs1use crate::config::SiteConfig;
2use pingora::http::ResponseHeader;
3use pingora::prelude::*;
4
5pub struct HealthHandler {
6 start_time: std::time::Instant,
7}
8
9impl HealthHandler {
10 pub fn new() -> Self {
11 Self {
12 start_time: std::time::Instant::now(),
13 }
14 }
15
16 pub async fn handle(&self, session: &mut Session, _site: Option<&SiteConfig>) -> Result<()> {
17 let path = session.req_header().uri.path();
18
19 match path {
20 "/api/health" => self.handle_basic_health(session).await,
21 "/api/health/detailed" => self.handle_detailed_health(session).await,
22 "/api/health/ready" => self.handle_readiness(session).await,
23 "/api/health/live" => self.handle_liveness(session).await,
24 _ => self.handle_basic_health(session).await,
25 }
26 }
27
28 async fn handle_basic_health(&self, session: &mut Session) -> Result<()> {
29 let response = serde_json::json!({
30 "status": "ok",
31 "timestamp": chrono::Utc::now().to_rfc3339(),
32 "service": "bws-web-server",
33 "version": env!("CARGO_PKG_VERSION"),
34 "uptime_seconds": self.start_time.elapsed().as_secs()
35 });
36
37 self.send_json_response(session, 200, &response).await
38 }
39
40 async fn handle_detailed_health(&self, session: &mut Session) -> Result<()> {
41 let uptime = self.start_time.elapsed();
42 let memory_info = self.get_memory_info();
43 let system_info = self.get_system_info();
44
45 let response = serde_json::json!({
46 "status": "ok",
47 "timestamp": chrono::Utc::now().to_rfc3339(),
48 "service": {
49 "name": "bws-web-server",
50 "version": env!("CARGO_PKG_VERSION"),
51 "description": env!("CARGO_PKG_DESCRIPTION")
52 },
53 "uptime": {
54 "seconds": uptime.as_secs(),
55 "human_readable": self.format_duration(uptime)
56 },
57 "memory": memory_info,
58 "system": system_info,
59 "features": {
60 "ssl_support": true,
61 "auto_cert": true,
62 "multi_site": true,
63 "compression": true,
64 "caching": true
65 }
66 });
67
68 self.send_json_response(session, 200, &response).await
69 }
70
71 async fn handle_readiness(&self, session: &mut Session) -> Result<()> {
72 let is_ready = self.check_readiness();
74
75 let status_code = if is_ready { 200 } else { 503 };
76 let response = serde_json::json!({
77 "status": if is_ready { "ready" } else { "not_ready" },
78 "timestamp": chrono::Utc::now().to_rfc3339(),
79 "checks": {
80 "configuration_loaded": true,
81 "ssl_manager_initialized": true,
82 "handlers_ready": true
83 }
84 });
85
86 self.send_json_response(session, status_code, &response)
87 .await
88 }
89
90 async fn handle_liveness(&self, session: &mut Session) -> Result<()> {
91 let is_alive = self.check_liveness();
93
94 let status_code = if is_alive { 200 } else { 503 };
95 let response = serde_json::json!({
96 "status": if is_alive { "alive" } else { "dead" },
97 "timestamp": chrono::Utc::now().to_rfc3339(),
98 "uptime_seconds": self.start_time.elapsed().as_secs()
99 });
100
101 self.send_json_response(session, status_code, &response)
102 .await
103 }
104
105 fn check_readiness(&self) -> bool {
106 true
109 }
110
111 fn check_liveness(&self) -> bool {
112 true
115 }
116
117 fn get_memory_info(&self) -> serde_json::Value {
118 serde_json::json!({
121 "note": "Memory information not available in this implementation"
122 })
123 }
124
125 fn get_system_info(&self) -> serde_json::Value {
126 serde_json::json!({
127 "rust_version": std::env::var("RUSTC_VERSION").unwrap_or_else(|_| "unknown".to_string()),
128 "target": std::env::var("TARGET").unwrap_or_else(|_| std::env::consts::ARCH.to_string()),
129 "build_timestamp": std::env::var("BUILD_TIMESTAMP").unwrap_or_else(|_| "unknown".to_string()),
130 "os": std::env::consts::OS,
131 "arch": std::env::consts::ARCH
132 })
133 }
134
135 fn format_duration(&self, duration: std::time::Duration) -> String {
136 let total_seconds = duration.as_secs();
137 let days = total_seconds / 86400;
138 let hours = (total_seconds % 86400) / 3600;
139 let minutes = (total_seconds % 3600) / 60;
140 let seconds = total_seconds % 60;
141
142 if days > 0 {
143 format!("{days}d {hours}h {minutes}m {seconds}s")
144 } else if hours > 0 {
145 format!("{hours}h {minutes}m {seconds}s")
146 } else if minutes > 0 {
147 format!("{minutes}m {seconds}s")
148 } else {
149 format!("{seconds}s")
150 }
151 }
152
153 async fn send_json_response(
154 &self,
155 session: &mut Session,
156 status: u16,
157 data: &serde_json::Value,
158 ) -> Result<()> {
159 let response_body = serde_json::to_string_pretty(data)
160 .unwrap_or_else(|_| r#"{"error": "Failed to serialize response"}"#.to_string());
161 let response_bytes = response_body.into_bytes();
162
163 let mut header = ResponseHeader::build(status, Some(4))?;
164 header.insert_header("Content-Type", "application/json; charset=utf-8")?;
165 header.insert_header("Content-Length", response_bytes.len().to_string())?;
166 header.insert_header("Cache-Control", "no-cache, no-store, must-revalidate")?;
167 header.insert_header("Pragma", "no-cache")?;
168
169 session
170 .write_response_header(Box::new(header), false)
171 .await?;
172 session
173 .write_response_body(Some(response_bytes.into()), true)
174 .await?;
175
176 Ok(())
177 }
178}
179
180impl Default for HealthHandler {
181 fn default() -> Self {
182 Self::new()
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189
190 #[test]
191 fn test_health_handler_creation() {
192 let handler = HealthHandler::new();
193 assert!(handler.start_time.elapsed().as_millis() < 100); }
195
196 #[test]
197 fn test_duration_formatting() {
198 let handler = HealthHandler::new();
199
200 assert_eq!(
201 handler.format_duration(std::time::Duration::from_secs(30)),
202 "30s"
203 );
204 assert_eq!(
205 handler.format_duration(std::time::Duration::from_secs(90)),
206 "1m 30s"
207 );
208 assert_eq!(
209 handler.format_duration(std::time::Duration::from_secs(3661)),
210 "1h 1m 1s"
211 );
212 assert_eq!(
213 handler.format_duration(std::time::Duration::from_secs(90061)),
214 "1d 1h 1m 1s"
215 );
216 }
217
218 #[test]
219 fn test_readiness_check() {
220 let handler = HealthHandler::new();
221 assert!(handler.check_readiness());
222 }
223
224 #[test]
225 fn test_liveness_check() {
226 let handler = HealthHandler::new();
227 assert!(handler.check_liveness());
228 }
229}