diff options
| author | Your Name <you@example.com> | 2026-05-26 18:01:30 +0530 |
|---|---|---|
| committer | Your Name <you@example.com> | 2026-05-26 18:01:30 +0530 |
| commit | e435f7d7b4ad4e4b1d3c21c35df5f41ffd642376 (patch) | |
| tree | ccb0587d8aff2d0513f2cef359349b6e0b1b947f /src/http_health.rs | |
| parent | 8816a192c95cf539b65975469a2d61aed46f0414 (diff) | |
Add HTTP health endpoint on /health and /api/mirror-health
- New axum-based health server on port 7335 (configurable via health_port)
- Reports status, uptime, cycle_count, last_cycle_ok as JSON
- Status is 'ok' on startup and after successful cycles, 'degraded' after failures
- Config: storage.health_port defaults to 7335
- Spawned alongside daemon loop, independent of mirror cycles
Diffstat (limited to 'src/http_health.rs')
| -rw-r--r-- | src/http_health.rs | 40 |
1 files changed, 40 insertions, 0 deletions
diff --git a/src/http_health.rs b/src/http_health.rs new file mode 100644 index 0000000..0cdfeb5 --- /dev/null +++ b/src/http_health.rs | |||
| @@ -0,0 +1,40 @@ | |||
| 1 | use axum::extract::State; | ||
| 2 | use axum::response::Json; | ||
| 3 | use axum::routing::get; | ||
| 4 | use axum::Router; | ||
| 5 | use serde_json::{json, Value}; | ||
| 6 | use std::sync::Arc; | ||
| 7 | use std::time::Instant; | ||
| 8 | use tokio::sync::watch; | ||
| 9 | |||
| 10 | pub struct HealthState { | ||
| 11 | pub started_at: Instant, | ||
| 12 | pub cycle_count: watch::Receiver<u64>, | ||
| 13 | pub last_cycle_ok: watch::Receiver<bool>, | ||
| 14 | pub db_path: String, | ||
| 15 | } | ||
| 16 | |||
| 17 | pub async fn start_health_server(port: u16, state: Arc<HealthState>) -> anyhow::Result<()> { | ||
| 18 | let app = Router::new() | ||
| 19 | .route("/health", get(health_handler)) | ||
| 20 | .route("/api/mirror-health", get(health_handler)) | ||
| 21 | .with_state(state); | ||
| 22 | |||
| 23 | let listener = tokio::net::TcpListener::bind(format!("127.0.0.1:{}", port)).await?; | ||
| 24 | tracing::info!(port, "health server listening"); | ||
| 25 | axum::serve(listener, app).await?; | ||
| 26 | Ok(()) | ||
| 27 | } | ||
| 28 | |||
| 29 | async fn health_handler(State(state): State<Arc<HealthState>>) -> Json<Value> { | ||
| 30 | let uptime = state.started_at.elapsed(); | ||
| 31 | let cycle_count = *state.cycle_count.borrow(); | ||
| 32 | let last_ok = *state.last_cycle_ok.borrow(); | ||
| 33 | |||
| 34 | Json(json!({ | ||
| 35 | "status": if last_ok || cycle_count == 0 { "ok" } else { "degraded" }, | ||
| 36 | "uptime_secs": uptime.as_secs(), | ||
| 37 | "cycle_count": cycle_count, | ||
| 38 | "last_cycle_ok": last_ok, | ||
| 39 | })) | ||
| 40 | } | ||