1mod metrics;
75pub mod tracing;
76pub mod propagation;
77#[cfg(feature = "observability")]
78pub mod prometheus;
79#[cfg(feature = "system_info")]
80mod metrics_collector;
81pub mod grafana;
82
83use std::sync::Arc;
84use serde::{Serialize, Deserialize};
85
86pub use tracing::{RiTracer, RiTraceId, RiSpanId, RiSpan, RiSpanKind, RiSpanStatus, RiTracingContext, RiSamplingStrategy};
87pub use metrics::{RiMetricsRegistry, RiMetric, RiMetricConfig, RiMetricType, RiWindowStats, RiMetricSample};
88pub use propagation::{RiTraceContext, RiBaggage, RiContextCarrier, W3CTracePropagator};
89#[cfg(feature = "system_info")]
90pub use metrics_collector::{RiSystemMetricsCollector, RiSystemMetrics, RiCPUMetrics, RiMemoryMetrics, RiDiskMetrics, RiNetworkMetrics};
91pub use metrics_collector::{RiSlidingWindow, RiQuantileCalculator, RiPerformanceCollector};
92
93use crate::core::{RiResult, RiServiceContext};
94
95
96#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
102pub struct RiObservabilityModule {
103 tracer: Option<Arc<RiTracer>>,
105 metrics_registry: Option<Arc<RiMetricsRegistry>>,
107 config: RiObservabilityConfig,
109}
110
111#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
115#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct RiObservabilityConfig {
117 pub tracing_enabled: bool,
119 pub metrics_enabled: bool,
121 pub tracing_sampling_rate: f64,
123 pub tracing_sampling_strategy: String,
125 pub metrics_window_size_secs: u64,
127 pub metrics_bucket_size_secs: u64,
129}
130
131impl Default for RiObservabilityConfig {
132 fn default() -> Self {
142 Self {
143 tracing_enabled: true,
144 metrics_enabled: true,
145 tracing_sampling_rate: 0.1, tracing_sampling_strategy: "rate".to_string(), metrics_window_size_secs: 300, metrics_bucket_size_secs: 10, }
150 }
151}
152
153#[cfg(feature = "pyo3")]
154#[pyo3::prelude::pymethods]
156impl RiObservabilityConfig {
157 #[new]
158 fn py_new() -> Self {
159 Self::default()
160 }
161
162 fn set_tracing_enabled(&mut self, tracing_enabled: bool) {
164 self.tracing_enabled = tracing_enabled;
165 }
166
167 fn get_tracing_enabled(&self) -> bool {
169 self.tracing_enabled
170 }
171
172 fn set_metrics_enabled(&mut self, metrics_enabled: bool) {
174 self.metrics_enabled = metrics_enabled;
175 }
176
177 fn get_metrics_enabled(&self) -> bool {
179 self.metrics_enabled
180 }
181
182 fn set_tracing_sampling_rate(&mut self, tracing_sampling_rate: f64) -> pyo3::PyResult<()>
184 {
185 if tracing_sampling_rate < 0.0 || tracing_sampling_rate > 1.0 {
186 return Err(pyo3::exceptions::PyValueError::new_err("Tracing sampling rate must be between 0.0 and 1.0"));
187 }
188 self.tracing_sampling_rate = tracing_sampling_rate;
189 Ok(())
190 }
191
192 fn get_tracing_sampling_rate(&self) -> f64 {
194 self.tracing_sampling_rate
195 }
196
197 fn set_tracing_sampling_strategy(&mut self, tracing_sampling_strategy: String) {
199 self.tracing_sampling_strategy = tracing_sampling_strategy;
200 }
201
202 fn get_tracing_sampling_strategy(&self) -> String {
204 self.tracing_sampling_strategy.clone()
205 }
206
207 fn set_metrics_window_size_secs(&mut self, metrics_window_size_secs: u64) {
209 self.metrics_window_size_secs = metrics_window_size_secs;
210 }
211
212 fn get_metrics_window_size_secs(&self) -> u64 {
214 self.metrics_window_size_secs
215 }
216
217 fn set_metrics_bucket_size_secs(&mut self, metrics_bucket_size_secs: u64) {
219 self.metrics_bucket_size_secs = metrics_bucket_size_secs;
220 }
221
222 fn get_metrics_bucket_size_secs(&self) -> u64 {
224 self.metrics_bucket_size_secs
225 }
226}
227
228impl Default for RiObservabilityModule {
229 fn default() -> Self {
230 Self::new()
231 }
232}
233
234impl RiObservabilityModule {
235 pub fn new() -> Self {
241 Self {
242 tracer: None,
243 metrics_registry: None,
244 config: RiObservabilityConfig::default(),
245 }
246 }
247
248 pub fn with_config(mut self, config: RiObservabilityConfig) -> Self {
258 self.config = config;
259 self
260 }
261
262 fn init_tracing(&mut self) {
266 if self.config.tracing_enabled {
267 tracing::init_tracer(self.config.tracing_sampling_rate);
270 }
271 }
272
273 fn init_metrics(&mut self) {
277 if self.config.metrics_enabled {
278 let registry = Arc::new(RiMetricsRegistry::new());
279 self.metrics_registry = Some(registry);
280 }
281 }
282
283 fn create_service_metrics(&self) -> RiResult<()> {
301 if let Some(registry) = &self.metrics_registry {
302 let request_duration_config = RiMetricConfig {
304 metric_type: RiMetricType::Histogram,
305 name: "dms_request_duration_seconds".to_string(),
306 help: "Request duration in seconds".to_string(),
307 buckets: vec![0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1.0, 5.0], quantiles: vec![0.5, 0.9, 0.95, 0.99],
309 max_age: std::time::Duration::from_secs(300),
310 age_buckets: 5,
311 };
312
313 let request_duration_metric = Arc::new(RiMetric::new(request_duration_config));
314 registry.register(request_duration_metric)?;
315
316 let request_counter_config = RiMetricConfig {
318 metric_type: RiMetricType::Counter,
319 name: "dms_requests_total".to_string(),
320 help: "Total number of requests".to_string(),
321 buckets: vec![],
322 quantiles: vec![],
323 max_age: std::time::Duration::from_secs(0),
324 age_buckets: 0,
325 };
326
327 let request_counter_metric = Arc::new(RiMetric::new(request_counter_config));
328 registry.register(request_counter_metric)?;
329
330 let error_counter_config = RiMetricConfig {
332 metric_type: RiMetricType::Counter,
333 name: "dms_errors_total".to_string(),
334 help: "Total number of errors".to_string(),
335 buckets: vec![],
336 quantiles: vec![],
337 max_age: std::time::Duration::from_secs(0),
338 age_buckets: 0,
339 };
340
341 let error_counter_metric = Arc::new(RiMetric::new(error_counter_config));
342 registry.register(error_counter_metric)?;
343
344 let connections_gauge_config = RiMetricConfig {
346 metric_type: RiMetricType::Gauge,
347 name: "dms_active_connections".to_string(),
348 help: "Number of active connections".to_string(),
349 buckets: vec![],
350 quantiles: vec![],
351 max_age: std::time::Duration::from_secs(0),
352 age_buckets: 0,
353 };
354
355 let connections_gauge_metric = Arc::new(RiMetric::new(connections_gauge_config));
356 registry.register(connections_gauge_metric)?;
357
358 let startup_time_config = RiMetricConfig {
360 metric_type: RiMetricType::Gauge,
361 name: "dms_service_startup_time_seconds".to_string(),
362 help: "Service startup time in seconds".to_string(),
363 buckets: vec![],
364 quantiles: vec![],
365 max_age: std::time::Duration::from_secs(0),
366 age_buckets: 0,
367 };
368
369 let startup_time_metric = Arc::new(RiMetric::new(startup_time_config));
370 registry.register(startup_time_metric)?;
371
372 let module_init_config = RiMetricConfig {
374 metric_type: RiMetricType::Histogram,
375 name: "dms_module_init_time_seconds".to_string(),
376 help: "Module initialization time in seconds".to_string(),
377 buckets: vec![0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1.0, 5.0],
378 quantiles: vec![0.5, 0.9, 0.95, 0.99],
379 max_age: std::time::Duration::from_secs(300),
380 age_buckets: 5,
381 };
382
383 let module_init_metric = Arc::new(RiMetric::new(module_init_config));
384 registry.register(module_init_metric)?;
385
386 let queue_length_config = RiMetricConfig {
388 metric_type: RiMetricType::Gauge,
389 name: "dms_request_queue_length".to_string(),
390 help: "Request queue length".to_string(),
391 buckets: vec![],
392 quantiles: vec![],
393 max_age: std::time::Duration::from_secs(0),
394 age_buckets: 0,
395 };
396
397 let queue_length_metric = Arc::new(RiMetric::new(queue_length_config));
398 registry.register(queue_length_metric)?;
399
400 let middleware_time_config = RiMetricConfig {
402 metric_type: RiMetricType::Histogram,
403 name: "dms_middleware_duration_seconds".to_string(),
404 help: "Middleware execution time in seconds".to_string(),
405 buckets: vec![0.001, 0.005, 0.01, 0.05, 0.1, 0.5],
406 quantiles: vec![0.5, 0.9, 0.95, 0.99],
407 max_age: std::time::Duration::from_secs(300),
408 age_buckets: 5,
409 };
410
411 let middleware_time_metric = Arc::new(RiMetric::new(middleware_time_config));
412 registry.register(middleware_time_metric)?;
413
414 let cache_hit_config = RiMetricConfig {
417 metric_type: RiMetricType::Counter,
418 name: "dms_cache_hits_total".to_string(),
419 help: "Total number of cache hits".to_string(),
420 buckets: vec![],
421 quantiles: vec![],
422 max_age: std::time::Duration::from_secs(0),
423 age_buckets: 0,
424 };
425
426 let cache_hit_metric = Arc::new(RiMetric::new(cache_hit_config));
427 registry.register(cache_hit_metric)?;
428
429 let cache_miss_config = RiMetricConfig {
431 metric_type: RiMetricType::Counter,
432 name: "dms_cache_misses_total".to_string(),
433 help: "Total number of cache misses".to_string(),
434 buckets: vec![],
435 quantiles: vec![],
436 max_age: std::time::Duration::from_secs(0),
437 age_buckets: 0,
438 };
439
440 let cache_miss_metric = Arc::new(RiMetric::new(cache_miss_config));
441 registry.register(cache_miss_metric)?;
442
443 let cache_entries_config = RiMetricConfig {
445 metric_type: RiMetricType::Gauge,
446 name: "dms_cache_entries".to_string(),
447 help: "Number of cache entries".to_string(),
448 buckets: vec![],
449 quantiles: vec![],
450 max_age: std::time::Duration::from_secs(0),
451 age_buckets: 0,
452 };
453
454 let cache_entries_metric = Arc::new(RiMetric::new(cache_entries_config));
455 registry.register(cache_entries_metric)?;
456
457 let cache_memory_config = RiMetricConfig {
459 metric_type: RiMetricType::Gauge,
460 name: "dms_cache_memory_usage_bytes".to_string(),
461 help: "Cache memory usage in bytes".to_string(),
462 buckets: vec![],
463 quantiles: vec![],
464 max_age: std::time::Duration::from_secs(0),
465 age_buckets: 0,
466 };
467
468 let cache_memory_metric = Arc::new(RiMetric::new(cache_memory_config));
469 registry.register(cache_memory_metric)?;
470
471 let cache_eviction_config = RiMetricConfig {
473 metric_type: RiMetricType::Counter,
474 name: "dms_cache_evictions_total".to_string(),
475 help: "Total number of cache evictions".to_string(),
476 buckets: vec![],
477 quantiles: vec![],
478 max_age: std::time::Duration::from_secs(0),
479 age_buckets: 0,
480 };
481
482 let cache_eviction_metric = Arc::new(RiMetric::new(cache_eviction_config));
483 registry.register(cache_eviction_metric)?;
484
485 let db_query_config = RiMetricConfig {
488 metric_type: RiMetricType::Histogram,
489 name: "dms_db_query_duration_seconds".to_string(),
490 help: "Database query execution time in seconds".to_string(),
491 buckets: vec![0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1.0, 5.0],
492 quantiles: vec![0.5, 0.9, 0.95, 0.99],
493 max_age: std::time::Duration::from_secs(300),
494 age_buckets: 5,
495 };
496
497 let db_query_metric = Arc::new(RiMetric::new(db_query_config));
498 registry.register(db_query_metric)?;
499
500 let db_active_connections_config = RiMetricConfig {
502 metric_type: RiMetricType::Gauge,
503 name: "dms_db_active_connections".to_string(),
504 help: "Number of active database connections".to_string(),
505 buckets: vec![],
506 quantiles: vec![],
507 max_age: std::time::Duration::from_secs(0),
508 age_buckets: 0,
509 };
510
511 let db_active_connections_metric = Arc::new(RiMetric::new(db_active_connections_config));
512 registry.register(db_active_connections_metric)?;
513
514 let db_idle_connections_config = RiMetricConfig {
516 metric_type: RiMetricType::Gauge,
517 name: "dms_db_idle_connections".to_string(),
518 help: "Number of idle database connections".to_string(),
519 buckets: vec![],
520 quantiles: vec![],
521 max_age: std::time::Duration::from_secs(0),
522 age_buckets: 0,
523 };
524
525 let db_idle_connections_metric = Arc::new(RiMetric::new(db_idle_connections_config));
526 registry.register(db_idle_connections_metric)?;
527
528 let db_timeout_config = RiMetricConfig {
530 metric_type: RiMetricType::Counter,
531 name: "dms_db_connection_timeouts_total".to_string(),
532 help: "Total number of database connection timeouts".to_string(),
533 buckets: vec![],
534 quantiles: vec![],
535 max_age: std::time::Duration::from_secs(0),
536 age_buckets: 0,
537 };
538
539 let db_timeout_metric = Arc::new(RiMetric::new(db_timeout_config));
540 registry.register(db_timeout_metric)?;
541
542 let db_errors_config = RiMetricConfig {
544 metric_type: RiMetricType::Counter,
545 name: "dms_db_errors_total".to_string(),
546 help: "Total number of database errors".to_string(),
547 buckets: vec![],
548 quantiles: vec![],
549 max_age: std::time::Duration::from_secs(0),
550 age_buckets: 0,
551 };
552
553 let db_errors_metric = Arc::new(RiMetric::new(db_errors_config));
554 registry.register(db_errors_metric)?;
555
556 let db_transactions_config = RiMetricConfig {
558 metric_type: RiMetricType::Counter,
559 name: "dms_db_transactions_total".to_string(),
560 help: "Total number of database transactions".to_string(),
561 buckets: vec![],
562 quantiles: vec![],
563 max_age: std::time::Duration::from_secs(0),
564 age_buckets: 0,
565 };
566
567 let db_transactions_metric = Arc::new(RiMetric::new(db_transactions_config));
568 registry.register(db_transactions_metric)?;
569
570 let db_commits_config = RiMetricConfig {
572 metric_type: RiMetricType::Counter,
573 name: "dms_db_transaction_commits_total".to_string(),
574 help: "Total number of database transaction commits".to_string(),
575 buckets: vec![],
576 quantiles: vec![],
577 max_age: std::time::Duration::from_secs(0),
578 age_buckets: 0,
579 };
580
581 let db_commits_metric = Arc::new(RiMetric::new(db_commits_config));
582 registry.register(db_commits_metric)?;
583
584 let db_rollbacks_config = RiMetricConfig {
586 metric_type: RiMetricType::Counter,
587 name: "dms_db_transaction_rollbacks_total".to_string(),
588 help: "Total number of database transaction rollbacks".to_string(),
589 buckets: vec![],
590 quantiles: vec![],
591 max_age: std::time::Duration::from_secs(0),
592 age_buckets: 0,
593 };
594
595 let db_rollbacks_metric = Arc::new(RiMetric::new(db_rollbacks_config));
596 registry.register(db_rollbacks_metric)?;
597 }
598
599 Ok(())
600 }
601
602 pub fn export_data(&self) -> RiObservabilityData {
611 RiObservabilityData {
612 metrics: {
613 #[cfg(feature = "observability")]
614 {
615 self.metrics_registry.as_ref().map(|r| r.export_prometheus()).unwrap_or_default()
616 }
617 #[cfg(not(feature = "observability"))]
618 {
619 String::default()
620 }
621 },
622 active_traces: self.tracer.as_ref().map(|_| tracing::tracer().map(|t| t.active_trace_count()).unwrap_or(0)).unwrap_or(0),
623 active_spans: self.tracer.as_ref().map(|_| tracing::tracer().map(|t| t.active_span_count()).unwrap_or(0)).unwrap_or(0),
624 }
625 }
626}
627
628#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
633#[derive(Debug, Clone, Serialize, Deserialize)]
634pub struct RiObservabilityData {
635 pub metrics: String,
637 pub active_traces: usize,
639 pub active_spans: usize,
641}
642
643#[cfg(feature = "pyo3")]
644#[pyo3::prelude::pymethods]
646impl RiObservabilityData {
647 #[new]
649 fn py_new(metrics: String, active_traces: usize, active_spans: usize) -> Self {
650 Self {
651 metrics,
652 active_traces,
653 active_spans,
654 }
655 }
656
657 #[pyo3(name = "get_metrics")]
659 fn get_metrics_impl(&self) -> String {
660 self.metrics.clone()
661 }
662
663 #[pyo3(name = "get_active_traces")]
665 fn get_active_traces_impl(&self) -> usize {
666 self.active_traces
667 }
668
669 #[pyo3(name = "get_active_spans")]
671 fn get_active_spans_impl(&self) -> usize {
672 self.active_spans
673 }
674}
675
676#[cfg(feature = "pyo3")]
677#[pyo3::prelude::pymethods]
678impl RiObservabilityModule {
679 fn get_metrics(&self) -> String {
680 format!("ObservabilityModule with config: {:?}", self.config)
681 }
682}
683
684#[async_trait::async_trait]
685impl crate::core::RiModule for RiObservabilityModule {
686 fn name(&self) -> &str {
692 "Ri.Observability"
693 }
694
695 fn is_critical(&self) -> bool {
705 false }
707
708 async fn init(&mut self, ctx: &mut RiServiceContext) -> RiResult<()> {
726 let binding = ctx.config();
728 let cfg = binding.config();
729
730 self.config = RiObservabilityConfig {
731 tracing_enabled: cfg.get_bool("observability.tracing_enabled").unwrap_or(true),
732 metrics_enabled: cfg.get_bool("observability.metrics_enabled").unwrap_or(true),
733 tracing_sampling_rate: cfg.get_f32("observability.tracing_sampling_rate")
734 .unwrap_or(0.1)
735 .max(0.0)
736 .min(1.0) as f64,
737 tracing_sampling_strategy: cfg.get_str("observability.tracing_sampling_strategy")
738 .unwrap_or("rate")
739 .to_string(),
740 metrics_window_size_secs: cfg.get_u64("observability.metrics_window_size_secs")
741 .unwrap_or(300)
742 .max(1),
743 metrics_bucket_size_secs: cfg.get_u64("observability.metrics_bucket_size_secs")
744 .unwrap_or(10)
745 .max(1),
746 };
747
748 self.init_tracing();
750 self.init_metrics();
751 self.create_service_metrics()?;
752
753 let hooks: &mut crate::hooks::RiHookBus = ctx.hooks_mut();
755
756 hooks.register(
758 crate::hooks::RiHookKind::Startup,
759 "dms.observability.lifecycle".to_string(),
760 |_ctx, _event: &crate::hooks::RiHookEvent| {
761 Ok(())
763 },
764 );
765
766 let logger = ctx.logger();
767 logger.info("Ri.Observability", "Observability module initialized")?;
768
769 Ok(())
770 }
771
772 async fn after_shutdown(&mut self, ctx: &mut RiServiceContext) -> RiResult<()> {
785 let data = self.export_data();
787
788 let logger = ctx.logger();
789 logger.info("Ri.Observability", format!("Final observability data: {} active traces, {} active spans",
790 data.active_traces, data.active_spans))?;
791
792 Ok(())
793 }
794}