rustkernel_core/observability/
mod.rs1pub mod alerting;
32pub mod logging;
33pub mod metrics;
34pub mod tracing;
35
36pub use alerting::{AlertConfig, AlertRule, AlertSeverity, AlertState};
37pub use logging::{LogConfig, LogLevel, StructuredLogger};
38pub use metrics::{KernelMetrics, MetricsConfig, MetricsExporter};
39pub use tracing::{KernelSpan, SpanContext, TracingConfig};
40
41pub use ringkernel_core::alerting as ring_alerting;
43pub use ringkernel_core::logging as ring_logging;
44pub use ringkernel_core::observability as ring_observability;
45pub use ringkernel_core::telemetry as ring_telemetry;
46pub use ringkernel_core::telemetry_pipeline as ring_telemetry_pipeline;
47
48use serde::{Deserialize, Serialize};
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct ObservabilityConfig {
53 pub metrics: Option<MetricsConfig>,
55 pub tracing: Option<TracingConfig>,
57 pub logging: LogConfig,
59 pub alerting: Option<AlertConfig>,
61 pub service_name: String,
63 pub service_version: String,
65 pub environment: String,
67}
68
69impl Default for ObservabilityConfig {
70 fn default() -> Self {
71 Self {
72 metrics: None,
73 tracing: None,
74 logging: LogConfig::default(),
75 alerting: None,
76 service_name: "rustkernels".to_string(),
77 service_version: env!("CARGO_PKG_VERSION").to_string(),
78 environment: "development".to_string(),
79 }
80 }
81}
82
83impl ObservabilityConfig {
84 pub fn builder() -> ObservabilityConfigBuilder {
86 ObservabilityConfigBuilder::default()
87 }
88
89 pub fn development() -> Self {
91 Self {
92 logging: LogConfig {
93 level: LogLevel::Debug,
94 structured: false,
95 ..Default::default()
96 },
97 environment: "development".to_string(),
98 ..Default::default()
99 }
100 }
101
102 pub fn production() -> Self {
104 Self {
105 metrics: Some(MetricsConfig::default()),
106 tracing: Some(TracingConfig::default()),
107 logging: LogConfig {
108 level: LogLevel::Info,
109 structured: true,
110 ..Default::default()
111 },
112 alerting: Some(AlertConfig::default()),
113 environment: "production".to_string(),
114 ..Default::default()
115 }
116 }
117
118 #[cfg(feature = "metrics")]
120 pub async fn init(&self) -> crate::error::Result<()> {
121 self.logging.init()?;
123
124 if let Some(ref metrics) = self.metrics {
126 metrics.init().await?;
127 }
128
129 if let Some(ref tracing) = self.tracing {
131 tracing.init().await?;
132 }
133
134 Ok(())
135 }
136
137 #[cfg(not(feature = "metrics"))]
139 pub async fn init(&self) -> crate::error::Result<()> {
140 self.logging.init()?;
141 Ok(())
142 }
143}
144
145#[derive(Default)]
147pub struct ObservabilityConfigBuilder {
148 config: ObservabilityConfig,
149}
150
151impl ObservabilityConfigBuilder {
152 pub fn with_metrics(mut self, config: MetricsConfig) -> Self {
154 self.config.metrics = Some(config);
155 self
156 }
157
158 pub fn with_tracing(mut self, config: TracingConfig) -> Self {
160 self.config.tracing = Some(config);
161 self
162 }
163
164 pub fn with_logging(mut self, config: LogConfig) -> Self {
166 self.config.logging = config;
167 self
168 }
169
170 pub fn with_alerting(mut self, config: AlertConfig) -> Self {
172 self.config.alerting = Some(config);
173 self
174 }
175
176 pub fn service_name(mut self, name: impl Into<String>) -> Self {
178 self.config.service_name = name.into();
179 self
180 }
181
182 pub fn service_version(mut self, version: impl Into<String>) -> Self {
184 self.config.service_version = version.into();
185 self
186 }
187
188 pub fn environment(mut self, env: impl Into<String>) -> Self {
190 self.config.environment = env.into();
191 self
192 }
193
194 pub fn build(self) -> ObservabilityConfig {
196 self.config
197 }
198}
199
200#[derive(Debug, Clone, Default)]
202pub struct MetricLabels {
203 pub kernel_id: Option<String>,
205 pub domain: Option<String>,
207 pub tenant_id: Option<String>,
209 pub extra: std::collections::HashMap<String, String>,
211}
212
213impl MetricLabels {
214 pub fn new() -> Self {
216 Self::default()
217 }
218
219 pub fn with_kernel(mut self, id: impl Into<String>) -> Self {
221 self.kernel_id = Some(id.into());
222 self
223 }
224
225 pub fn with_domain(mut self, domain: impl Into<String>) -> Self {
227 self.domain = Some(domain.into());
228 self
229 }
230
231 pub fn with_tenant(mut self, tenant: impl Into<String>) -> Self {
233 self.tenant_id = Some(tenant.into());
234 self
235 }
236
237 pub fn with_label(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
239 self.extra.insert(key.into(), value.into());
240 self
241 }
242
243 pub fn to_pairs(&self) -> Vec<(&str, String)> {
245 let mut pairs = Vec::new();
246 if let Some(ref id) = self.kernel_id {
247 pairs.push(("kernel_id", id.clone()));
248 }
249 if let Some(ref domain) = self.domain {
250 pairs.push(("domain", domain.clone()));
251 }
252 if let Some(ref tenant) = self.tenant_id {
253 pairs.push(("tenant_id", tenant.clone()));
254 }
255 for (k, v) in &self.extra {
256 pairs.push((k.as_str(), v.clone()));
257 }
258 pairs
259 }
260}
261
262#[cfg(test)]
263mod tests {
264 use super::*;
265
266 #[test]
267 fn test_default_config() {
268 let config = ObservabilityConfig::default();
269 assert_eq!(config.service_name, "rustkernels");
270 assert_eq!(config.environment, "development");
271 assert!(config.metrics.is_none());
272 }
273
274 #[test]
275 fn test_production_config() {
276 let config = ObservabilityConfig::production();
277 assert_eq!(config.environment, "production");
278 assert!(config.metrics.is_some());
279 assert!(config.tracing.is_some());
280 }
281
282 #[test]
283 fn test_builder() {
284 let config = ObservabilityConfig::builder()
285 .service_name("test-service")
286 .environment("testing")
287 .build();
288
289 assert_eq!(config.service_name, "test-service");
290 assert_eq!(config.environment, "testing");
291 }
292
293 #[test]
294 fn test_metric_labels() {
295 let labels = MetricLabels::new()
296 .with_kernel("graph/pagerank")
297 .with_domain("GraphAnalytics")
298 .with_tenant("tenant-123");
299
300 let pairs = labels.to_pairs();
301 assert_eq!(pairs.len(), 3);
302 }
303}