From e150b476c37041df55c5a5fb5602ca01237462ba Mon Sep 17 00:00:00 2001 From: Per Stark Date: Sat, 6 Sep 2025 11:14:24 +0200 Subject: [PATCH] feat: observability endpoints --- api-router/src/lib.rs | 14 +++++++++++--- api-router/src/routes/liveness.rs | 7 +++++++ api-router/src/routes/mod.rs | 2 ++ api-router/src/routes/readiness.rs | 25 +++++++++++++++++++++++++ 4 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 api-router/src/routes/liveness.rs create mode 100644 api-router/src/routes/readiness.rs diff --git a/api-router/src/lib.rs b/api-router/src/lib.rs index c3130a3..f4834bd 100644 --- a/api-router/src/lib.rs +++ b/api-router/src/lib.rs @@ -6,7 +6,7 @@ use axum::{ Router, }; use middleware_api_auth::api_auth; -use routes::{categories::get_categories, ingress::ingest_data}; +use routes::{categories::get_categories, ingress::ingest_data, liveness::live, readiness::ready}; pub mod api_state; pub mod error; @@ -19,9 +19,17 @@ where S: Clone + Send + Sync + 'static, ApiState: FromRef, { - Router::new() + // Public, unauthenticated endpoints (for k8s/systemd probes) + let public = Router::new() + .route("/ready", get(ready)) + .route("/live", get(live)); + + // Protected API endpoints (require auth) + let protected = Router::new() .route("/ingress", post(ingest_data)) .route("/categories", get(get_categories)) .layer(DefaultBodyLimit::max(1024 * 1024 * 1024)) - .route_layer(from_fn_with_state(app_state.clone(), api_auth)) + .route_layer(from_fn_with_state(app_state.clone(), api_auth)); + + public.merge(protected) } diff --git a/api-router/src/routes/liveness.rs b/api-router/src/routes/liveness.rs new file mode 100644 index 0000000..cacb245 --- /dev/null +++ b/api-router/src/routes/liveness.rs @@ -0,0 +1,7 @@ +use axum::{http::StatusCode, response::IntoResponse, Json}; +use serde_json::json; + +/// Liveness probe: always returns 200 to indicate the process is running. +pub async fn live() -> impl IntoResponse { + (StatusCode::OK, Json(json!({"status": "ok"}))) +} diff --git a/api-router/src/routes/mod.rs b/api-router/src/routes/mod.rs index 3a6e996..28ec91a 100644 --- a/api-router/src/routes/mod.rs +++ b/api-router/src/routes/mod.rs @@ -1,2 +1,4 @@ pub mod categories; pub mod ingress; +pub mod readiness; +pub mod liveness; diff --git a/api-router/src/routes/readiness.rs b/api-router/src/routes/readiness.rs new file mode 100644 index 0000000..628c208 --- /dev/null +++ b/api-router/src/routes/readiness.rs @@ -0,0 +1,25 @@ +use axum::{extract::State, http::StatusCode, response::IntoResponse, Json}; +use serde_json::json; + +use crate::api_state::ApiState; + +/// Readiness probe: returns 200 if core dependencies are ready, else 503. +pub async fn ready(State(state): State) -> impl IntoResponse { + match state.db.client.query("RETURN true").await { + Ok(_) => ( + StatusCode::OK, + Json(json!({ + "status": "ok", + "checks": { "db": "ok" } + })), + ), + Err(e) => ( + StatusCode::SERVICE_UNAVAILABLE, + Json(json!({ + "status": "error", + "checks": { "db": "fail" }, + "reason": e.to_string() + })), + ), + } +}