fukurow_observability/
lib.rs1use 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 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 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 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 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