Skip to main content

fukurow_observability/
lib.rs

1//! Observability (health/metrics) abstractions and Axum routes
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
7#[serde(rename_all = "lowercase")]
8pub enum HealthStatus {
9    Up,
10    Down,
11    Degraded,
12}
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct HealthCheck {
16    pub name: String,
17    pub status: HealthStatus,
18    pub timestamp: DateTime<Utc>,
19    pub duration_ms: u64,
20    pub message: Option<String>,
21    pub details: Option<serde_json::Value>,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct SystemMetrics {
26    pub timestamp: DateTime<Utc>,
27    pub memory_usage_mb: u64,
28    pub cpu_usage_percent: f64,
29    pub active_connections: u32,
30    pub total_requests: u64,
31    pub error_rate_percent: f64,
32    pub uptime_seconds: u64,
33}
34
35#[async_trait::async_trait]
36pub trait HealthMonitor: Send + Sync + 'static {
37    async fn get_overall_health(&self) -> HealthStatus;
38    async fn run_health_checks(&self) -> Vec<HealthCheck>;
39    async fn get_metrics(&self) -> SystemMetrics;
40}
41
42pub mod metrics {
43    /// Standard metric names for Fukurow components
44    pub mod names {
45        pub const REQUEST_TOTAL: &str = "fukurow_requests_total";
46        pub const REQUEST_DURATION: &str = "fukurow_request_duration_seconds";
47        pub const ACTIVE_CONNECTIONS: &str = "fukurow_active_connections";
48        pub const TRIPLE_COUNT: &str = "fukurow_triple_count";
49        pub const INFERENCE_TIME: &str = "fukurow_inference_duration_seconds";
50        pub const REASONING_ERRORS: &str = "fukurow_reasoning_errors_total";
51        pub const STREAM_EVENTS: &str = "fukurow_stream_events_total";
52        pub const OPERATOR_RECONCILES: &str = "fukurow_operator_reconciles_total";
53        pub const OPERATOR_RECONCILE_DURATION: &str = "fukurow_operator_reconcile_duration_seconds";
54    }
55
56    /// Standard labels for metrics
57    pub mod labels {
58        pub const SERVICE: &str = "service";
59        pub const COMPONENT: &str = "component";
60        pub const OPERATION: &str = "operation";
61        pub const STATUS: &str = "status";
62        pub const METHOD: &str = "method";
63        pub const ENDPOINT: &str = "endpoint";
64        pub const CLUSTER: &str = "cluster";
65        pub const NAMESPACE: &str = "namespace";
66        pub const EVENT_TYPE: &str = "event_type";
67        pub const STREAM_TYPE: &str = "stream_type";
68    }
69}
70
71pub mod tracing {
72    /// Standard span names for Fukurow operations
73    pub mod spans {
74        pub const API_REQUEST: &str = "api.request";
75        pub const REASONING_EXECUTE: &str = "reasoning.execute";
76        pub const STORE_QUERY: &str = "store.query";
77        pub const STREAM_SEND: &str = "stream.send";
78        pub const OPERATOR_RECONCILE: &str = "operator.reconcile";
79        pub const HEALTH_CHECK: &str = "health.check";
80    }
81
82    /// Standard span attributes
83    pub mod attributes {
84        pub const SERVICE_NAME: &str = "service.name";
85        pub const OPERATION: &str = "operation";
86        pub const COMPONENT: &str = "component";
87        pub const USER_ID: &str = "user.id";
88        pub const REQUEST_ID: &str = "request.id";
89        pub const CLUSTER_ID: &str = "cluster.id";
90        pub const NAMESPACE: &str = "namespace";
91        pub const ENDPOINT: &str = "endpoint";
92        pub const METHOD: &str = "method";
93        pub const STATUS_CODE: &str = "status.code";
94        pub const ERROR_TYPE: &str = "error.type";
95        pub const DURATION_MS: &str = "duration.ms";
96    }
97}
98
99pub mod routes {
100    use super::*;
101    use axum::{
102        extract::State,
103        http::StatusCode,
104        response::{IntoResponse, Json},
105        routing::get,
106        Router,
107    };
108    use std::sync::Arc;
109
110    pub fn monitoring_routes(monitor: Arc<dyn HealthMonitor>) -> Router {
111        Router::new()
112            .route("/health", get(health))
113            .route("/health/detailed", get(health_detailed))
114            .route("/metrics", get(metrics))
115            .with_state(monitor)
116    }
117
118    async fn health(State(m): State<Arc<dyn HealthMonitor>>) -> impl IntoResponse {
119        let status = m.get_overall_health().await;
120        let status_code = match status {
121            HealthStatus::Up => StatusCode::OK,
122            HealthStatus::Degraded => StatusCode::OK,
123            HealthStatus::Down => StatusCode::SERVICE_UNAVAILABLE,
124        };
125        (status_code, Json(status))
126    }
127
128    async fn health_detailed(State(m): State<Arc<dyn HealthMonitor>>) -> impl IntoResponse {
129        let checks = m.run_health_checks().await;
130        Json(checks)
131    }
132
133    async fn metrics(State(m): State<Arc<dyn HealthMonitor>>) -> impl IntoResponse {
134        let s = m.get_metrics().await;
135        Json(s)
136    }
137
138}
139
140