mod metrics;
pub mod tracing;
pub mod propagation;
#[cfg(feature = "observability")]
pub mod prometheus;
#[cfg(feature = "system_info")]
mod metrics_collector;
pub mod grafana;
use std::sync::Arc;
use serde::{Serialize, Deserialize};
pub use tracing::{RiTracer, RiTraceId, RiSpanId, RiSpan, RiSpanKind, RiSpanStatus, RiTracingContext, RiSamplingStrategy};
pub use metrics::{RiMetricsRegistry, RiMetric, RiMetricConfig, RiMetricType, RiWindowStats, RiMetricSample};
pub use propagation::{RiTraceContext, RiBaggage, RiContextCarrier, W3CTracePropagator};
#[cfg(feature = "system_info")]
pub use metrics_collector::{RiSystemMetricsCollector, RiSystemMetrics, RiCPUMetrics, RiMemoryMetrics, RiDiskMetrics, RiNetworkMetrics};
use crate::core::{RiResult, RiServiceContext};
#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
pub struct RiObservabilityModule {
tracer: Option<Arc<RiTracer>>,
metrics_registry: Option<Arc<RiMetricsRegistry>>,
config: RiObservabilityConfig,
}
#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RiObservabilityConfig {
pub tracing_enabled: bool,
pub metrics_enabled: bool,
pub tracing_sampling_rate: f64,
pub tracing_sampling_strategy: String,
pub metrics_window_size_secs: u64,
pub metrics_bucket_size_secs: u64,
}
impl Default for RiObservabilityConfig {
fn default() -> Self {
Self {
tracing_enabled: true,
metrics_enabled: true,
tracing_sampling_rate: 0.1, tracing_sampling_strategy: "rate".to_string(), metrics_window_size_secs: 300, metrics_bucket_size_secs: 10, }
}
}
#[cfg(feature = "pyo3")]
#[pyo3::prelude::pymethods]
impl RiObservabilityConfig {
#[new]
fn py_new() -> Self {
Self::default()
}
fn set_tracing_enabled(&mut self, tracing_enabled: bool) {
self.tracing_enabled = tracing_enabled;
}
fn get_tracing_enabled(&self) -> bool {
self.tracing_enabled
}
fn set_metrics_enabled(&mut self, metrics_enabled: bool) {
self.metrics_enabled = metrics_enabled;
}
fn get_metrics_enabled(&self) -> bool {
self.metrics_enabled
}
fn set_tracing_sampling_rate(&mut self, tracing_sampling_rate: f64) -> pyo3::PyResult<()>
{
if tracing_sampling_rate < 0.0 || tracing_sampling_rate > 1.0 {
return Err(pyo3::exceptions::PyValueError::new_err("Tracing sampling rate must be between 0.0 and 1.0"));
}
self.tracing_sampling_rate = tracing_sampling_rate;
Ok(())
}
fn get_tracing_sampling_rate(&self) -> f64 {
self.tracing_sampling_rate
}
fn set_tracing_sampling_strategy(&mut self, tracing_sampling_strategy: String) {
self.tracing_sampling_strategy = tracing_sampling_strategy;
}
fn get_tracing_sampling_strategy(&self) -> String {
self.tracing_sampling_strategy.clone()
}
fn set_metrics_window_size_secs(&mut self, metrics_window_size_secs: u64) {
self.metrics_window_size_secs = metrics_window_size_secs;
}
fn get_metrics_window_size_secs(&self) -> u64 {
self.metrics_window_size_secs
}
fn set_metrics_bucket_size_secs(&mut self, metrics_bucket_size_secs: u64) {
self.metrics_bucket_size_secs = metrics_bucket_size_secs;
}
fn get_metrics_bucket_size_secs(&self) -> u64 {
self.metrics_bucket_size_secs
}
}
impl Default for RiObservabilityModule {
fn default() -> Self {
Self::new()
}
}
impl RiObservabilityModule {
pub fn new() -> Self {
Self {
tracer: None,
metrics_registry: None,
config: RiObservabilityConfig::default(),
}
}
pub fn with_config(mut self, config: RiObservabilityConfig) -> Self {
self.config = config;
self
}
fn init_tracing(&mut self) {
if self.config.tracing_enabled {
tracing::init_tracer(self.config.tracing_sampling_rate);
}
}
fn init_metrics(&mut self) {
if self.config.metrics_enabled {
let registry = Arc::new(RiMetricsRegistry::new());
self.metrics_registry = Some(registry);
}
}
fn create_service_metrics(&self) -> RiResult<()> {
if let Some(registry) = &self.metrics_registry {
let request_duration_config = RiMetricConfig {
metric_type: RiMetricType::Histogram,
name: "dms_request_duration_seconds".to_string(),
help: "Request duration in seconds".to_string(),
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],
max_age: std::time::Duration::from_secs(300),
age_buckets: 5,
};
let request_duration_metric = Arc::new(RiMetric::new(request_duration_config));
registry.register(request_duration_metric)?;
let request_counter_config = RiMetricConfig {
metric_type: RiMetricType::Counter,
name: "dms_requests_total".to_string(),
help: "Total number of requests".to_string(),
buckets: vec![],
quantiles: vec![],
max_age: std::time::Duration::from_secs(0),
age_buckets: 0,
};
let request_counter_metric = Arc::new(RiMetric::new(request_counter_config));
registry.register(request_counter_metric)?;
let error_counter_config = RiMetricConfig {
metric_type: RiMetricType::Counter,
name: "dms_errors_total".to_string(),
help: "Total number of errors".to_string(),
buckets: vec![],
quantiles: vec![],
max_age: std::time::Duration::from_secs(0),
age_buckets: 0,
};
let error_counter_metric = Arc::new(RiMetric::new(error_counter_config));
registry.register(error_counter_metric)?;
let connections_gauge_config = RiMetricConfig {
metric_type: RiMetricType::Gauge,
name: "dms_active_connections".to_string(),
help: "Number of active connections".to_string(),
buckets: vec![],
quantiles: vec![],
max_age: std::time::Duration::from_secs(0),
age_buckets: 0,
};
let connections_gauge_metric = Arc::new(RiMetric::new(connections_gauge_config));
registry.register(connections_gauge_metric)?;
let startup_time_config = RiMetricConfig {
metric_type: RiMetricType::Gauge,
name: "dms_service_startup_time_seconds".to_string(),
help: "Service startup time in seconds".to_string(),
buckets: vec![],
quantiles: vec![],
max_age: std::time::Duration::from_secs(0),
age_buckets: 0,
};
let startup_time_metric = Arc::new(RiMetric::new(startup_time_config));
registry.register(startup_time_metric)?;
let module_init_config = RiMetricConfig {
metric_type: RiMetricType::Histogram,
name: "dms_module_init_time_seconds".to_string(),
help: "Module initialization time in seconds".to_string(),
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],
max_age: std::time::Duration::from_secs(300),
age_buckets: 5,
};
let module_init_metric = Arc::new(RiMetric::new(module_init_config));
registry.register(module_init_metric)?;
let queue_length_config = RiMetricConfig {
metric_type: RiMetricType::Gauge,
name: "dms_request_queue_length".to_string(),
help: "Request queue length".to_string(),
buckets: vec![],
quantiles: vec![],
max_age: std::time::Duration::from_secs(0),
age_buckets: 0,
};
let queue_length_metric = Arc::new(RiMetric::new(queue_length_config));
registry.register(queue_length_metric)?;
let middleware_time_config = RiMetricConfig {
metric_type: RiMetricType::Histogram,
name: "dms_middleware_duration_seconds".to_string(),
help: "Middleware execution time in seconds".to_string(),
buckets: vec![0.001, 0.005, 0.01, 0.05, 0.1, 0.5],
quantiles: vec![0.5, 0.9, 0.95, 0.99],
max_age: std::time::Duration::from_secs(300),
age_buckets: 5,
};
let middleware_time_metric = Arc::new(RiMetric::new(middleware_time_config));
registry.register(middleware_time_metric)?;
let cache_hit_config = RiMetricConfig {
metric_type: RiMetricType::Counter,
name: "dms_cache_hits_total".to_string(),
help: "Total number of cache hits".to_string(),
buckets: vec![],
quantiles: vec![],
max_age: std::time::Duration::from_secs(0),
age_buckets: 0,
};
let cache_hit_metric = Arc::new(RiMetric::new(cache_hit_config));
registry.register(cache_hit_metric)?;
let cache_miss_config = RiMetricConfig {
metric_type: RiMetricType::Counter,
name: "dms_cache_misses_total".to_string(),
help: "Total number of cache misses".to_string(),
buckets: vec![],
quantiles: vec![],
max_age: std::time::Duration::from_secs(0),
age_buckets: 0,
};
let cache_miss_metric = Arc::new(RiMetric::new(cache_miss_config));
registry.register(cache_miss_metric)?;
let cache_entries_config = RiMetricConfig {
metric_type: RiMetricType::Gauge,
name: "dms_cache_entries".to_string(),
help: "Number of cache entries".to_string(),
buckets: vec![],
quantiles: vec![],
max_age: std::time::Duration::from_secs(0),
age_buckets: 0,
};
let cache_entries_metric = Arc::new(RiMetric::new(cache_entries_config));
registry.register(cache_entries_metric)?;
let cache_memory_config = RiMetricConfig {
metric_type: RiMetricType::Gauge,
name: "dms_cache_memory_usage_bytes".to_string(),
help: "Cache memory usage in bytes".to_string(),
buckets: vec![],
quantiles: vec![],
max_age: std::time::Duration::from_secs(0),
age_buckets: 0,
};
let cache_memory_metric = Arc::new(RiMetric::new(cache_memory_config));
registry.register(cache_memory_metric)?;
let cache_eviction_config = RiMetricConfig {
metric_type: RiMetricType::Counter,
name: "dms_cache_evictions_total".to_string(),
help: "Total number of cache evictions".to_string(),
buckets: vec![],
quantiles: vec![],
max_age: std::time::Duration::from_secs(0),
age_buckets: 0,
};
let cache_eviction_metric = Arc::new(RiMetric::new(cache_eviction_config));
registry.register(cache_eviction_metric)?;
let db_query_config = RiMetricConfig {
metric_type: RiMetricType::Histogram,
name: "dms_db_query_duration_seconds".to_string(),
help: "Database query execution time in seconds".to_string(),
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],
max_age: std::time::Duration::from_secs(300),
age_buckets: 5,
};
let db_query_metric = Arc::new(RiMetric::new(db_query_config));
registry.register(db_query_metric)?;
let db_active_connections_config = RiMetricConfig {
metric_type: RiMetricType::Gauge,
name: "dms_db_active_connections".to_string(),
help: "Number of active database connections".to_string(),
buckets: vec![],
quantiles: vec![],
max_age: std::time::Duration::from_secs(0),
age_buckets: 0,
};
let db_active_connections_metric = Arc::new(RiMetric::new(db_active_connections_config));
registry.register(db_active_connections_metric)?;
let db_idle_connections_config = RiMetricConfig {
metric_type: RiMetricType::Gauge,
name: "dms_db_idle_connections".to_string(),
help: "Number of idle database connections".to_string(),
buckets: vec![],
quantiles: vec![],
max_age: std::time::Duration::from_secs(0),
age_buckets: 0,
};
let db_idle_connections_metric = Arc::new(RiMetric::new(db_idle_connections_config));
registry.register(db_idle_connections_metric)?;
let db_timeout_config = RiMetricConfig {
metric_type: RiMetricType::Counter,
name: "dms_db_connection_timeouts_total".to_string(),
help: "Total number of database connection timeouts".to_string(),
buckets: vec![],
quantiles: vec![],
max_age: std::time::Duration::from_secs(0),
age_buckets: 0,
};
let db_timeout_metric = Arc::new(RiMetric::new(db_timeout_config));
registry.register(db_timeout_metric)?;
let db_errors_config = RiMetricConfig {
metric_type: RiMetricType::Counter,
name: "dms_db_errors_total".to_string(),
help: "Total number of database errors".to_string(),
buckets: vec![],
quantiles: vec![],
max_age: std::time::Duration::from_secs(0),
age_buckets: 0,
};
let db_errors_metric = Arc::new(RiMetric::new(db_errors_config));
registry.register(db_errors_metric)?;
let db_transactions_config = RiMetricConfig {
metric_type: RiMetricType::Counter,
name: "dms_db_transactions_total".to_string(),
help: "Total number of database transactions".to_string(),
buckets: vec![],
quantiles: vec![],
max_age: std::time::Duration::from_secs(0),
age_buckets: 0,
};
let db_transactions_metric = Arc::new(RiMetric::new(db_transactions_config));
registry.register(db_transactions_metric)?;
let db_commits_config = RiMetricConfig {
metric_type: RiMetricType::Counter,
name: "dms_db_transaction_commits_total".to_string(),
help: "Total number of database transaction commits".to_string(),
buckets: vec![],
quantiles: vec![],
max_age: std::time::Duration::from_secs(0),
age_buckets: 0,
};
let db_commits_metric = Arc::new(RiMetric::new(db_commits_config));
registry.register(db_commits_metric)?;
let db_rollbacks_config = RiMetricConfig {
metric_type: RiMetricType::Counter,
name: "dms_db_transaction_rollbacks_total".to_string(),
help: "Total number of database transaction rollbacks".to_string(),
buckets: vec![],
quantiles: vec![],
max_age: std::time::Duration::from_secs(0),
age_buckets: 0,
};
let db_rollbacks_metric = Arc::new(RiMetric::new(db_rollbacks_config));
registry.register(db_rollbacks_metric)?;
}
Ok(())
}
pub fn export_data(&self) -> RiObservabilityData {
RiObservabilityData {
metrics: {
#[cfg(feature = "observability")]
{
self.metrics_registry.as_ref().map(|r| r.export_prometheus()).unwrap_or_default()
}
#[cfg(not(feature = "observability"))]
{
String::default()
}
},
active_traces: self.tracer.as_ref().map(|_| tracing::tracer().map(|t| t.active_trace_count()).unwrap_or(0)).unwrap_or(0),
active_spans: self.tracer.as_ref().map(|_| tracing::tracer().map(|t| t.active_span_count()).unwrap_or(0)).unwrap_or(0),
}
}
}
#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RiObservabilityData {
pub metrics: String,
pub active_traces: usize,
pub active_spans: usize,
}
#[cfg(feature = "pyo3")]
#[pyo3::prelude::pymethods]
impl RiObservabilityData {
#[new]
fn py_new(metrics: String, active_traces: usize, active_spans: usize) -> Self {
Self {
metrics,
active_traces,
active_spans,
}
}
#[pyo3(name = "get_metrics")]
fn get_metrics_impl(&self) -> String {
self.metrics.clone()
}
#[pyo3(name = "get_active_traces")]
fn get_active_traces_impl(&self) -> usize {
self.active_traces
}
#[pyo3(name = "get_active_spans")]
fn get_active_spans_impl(&self) -> usize {
self.active_spans
}
}
#[cfg(feature = "pyo3")]
#[pyo3::prelude::pymethods]
impl RiObservabilityModule {
fn get_metrics(&self) -> String {
format!("ObservabilityModule with config: {:?}", self.config)
}
}
#[async_trait::async_trait]
impl crate::core::RiModule for RiObservabilityModule {
fn name(&self) -> &str {
"Ri.Observability"
}
fn is_critical(&self) -> bool {
false }
async fn init(&mut self, ctx: &mut RiServiceContext) -> RiResult<()> {
let binding = ctx.config();
let cfg = binding.config();
self.config = RiObservabilityConfig {
tracing_enabled: cfg.get_bool("observability.tracing_enabled").unwrap_or(true),
metrics_enabled: cfg.get_bool("observability.metrics_enabled").unwrap_or(true),
tracing_sampling_rate: cfg.get_f32("observability.tracing_sampling_rate")
.unwrap_or(0.1)
.max(0.0)
.min(1.0) as f64,
tracing_sampling_strategy: cfg.get_str("observability.tracing_sampling_strategy")
.unwrap_or("rate")
.to_string(),
metrics_window_size_secs: cfg.get_u64("observability.metrics_window_size_secs")
.unwrap_or(300)
.max(1),
metrics_bucket_size_secs: cfg.get_u64("observability.metrics_bucket_size_secs")
.unwrap_or(10)
.max(1),
};
self.init_tracing();
self.init_metrics();
self.create_service_metrics()?;
let hooks: &mut crate::hooks::RiHookBus = ctx.hooks_mut();
hooks.register(
crate::hooks::RiHookKind::Startup,
"dms.observability.lifecycle".to_string(),
|_ctx, _event: &crate::hooks::RiHookEvent| {
Ok(())
},
);
let logger = ctx.logger();
logger.info("Ri.Observability", "Observability module initialized")?;
Ok(())
}
async fn after_shutdown(&mut self, ctx: &mut RiServiceContext) -> RiResult<()> {
let data = self.export_data();
let logger = ctx.logger();
logger.info("Ri.Observability", format!("Final observability data: {} active traces, {} active spans",
data.active_traces, data.active_spans))?;
Ok(())
}
}