Skip to main content

ri/observability/
mod.rs

1//! Copyright © 2025-2026 Wenze Wei. All Rights Reserved.
2//!
3//! This file is part of Ri.
4//! The Ri project belongs to the Dunimd Team.
5//!
6//! Licensed under the Apache License, Version 2.0 (the "License");
7//! You may not use this file except in compliance with the License.
8//! You may obtain a copy of the License at
9//!
10//!     http://www.apache.org/licenses/LICENSE-2.0
11//!
12//! Unless required by applicable law or agreed to in writing, software
13//! distributed under the License is distributed on an "AS IS" BASIS,
14//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15//! See the License for the specific language governing permissions and
16//! limitations under the License.
17
18//! # Observability Module
19//! 
20//! This module provides comprehensive observability capabilities for Ri, including distributed tracing
21//! and metrics collection. It follows modern observability best practices to help monitor, debug, and
22//! optimize Ri applications.
23//! 
24//! ## Key Components
25//! 
26//! - **RiObservabilityModule**: Main observability module
27//! - **RiTracer**: Distributed tracing implementation
28//! - **RiMetricsRegistry**: Metrics collection and aggregation
29//! - **RiObservabilityConfig**: Configuration for observability features
30//! - **RiObservabilityData**: Exported observability data structure
31//! 
32//! ## Design Principles
33//! 
34//! 1. **Separation of Concerns**: Tracing and metrics are separate but integrated components
35//! 2. **Configurable**: All features can be enabled/disabled and configured at runtime
36//! 3. **Non-intrusive**: Designed to be easy to integrate without disrupting application logic
37//! 4. **Performance-focused**: Optimized for low overhead in production environments
38//! 5. **Standard-compliant**: Follows W3C Trace Context standard for distributed tracing
39//! 6. **Prometheus-compatible**: Metrics are exported in Prometheus format
40//! 7. **Service Module Integration**: Implements the `ServiceModule` trait for seamless integration
41//! 
42//! ## Usage
43//! 
44//! ```rust
45//! use ri::prelude::*;
46//! 
47//! fn example() -> RiResult<()> {
48//!     // Create a Ri app builder
49//!     let mut builder = RiAppBuilder::new();
50//!     
51//!     // Configure observability
52//!     let observability_config = RiObservabilityConfig {
53//!         tracing_enabled: true,
54//!         metrics_enabled: true,
55//!         tracing_sampling_rate: 0.5, // 50% sampling rate
56//!         metrics_window_size_secs: 300,
57//!         metrics_bucket_size_secs: 10,
58//!     };
59//!     
60//!     // Add observability module to the app
61//!     let observability_module = RiObservabilityModule::new()
62//!         .with_config(observability_config);
63//!     
64//!     builder.add_module(Box::new(observability_module));
65//!     
66//!     // Build and run the app
67//!     let mut app = builder.build()?;
68//!     app.run()?;
69//!     
70//!     Ok(())
71//! }
72//! ```
73
74mod 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/// Main observability module for Ri.
97/// 
98/// This module provides distributed tracing and metrics collection capabilities, following modern
99/// observability best practices. It implements the `ServiceModule` trait for seamless integration
100/// with the Ri application lifecycle.
101#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
102pub struct RiObservabilityModule {
103    /// Distributed tracer instance
104    tracer: Option<Arc<RiTracer>>,
105    /// Metrics registry for collecting and aggregating metrics
106    metrics_registry: Option<Arc<RiMetricsRegistry>>,
107    /// Configuration for observability features
108    config: RiObservabilityConfig,
109}
110
111/// Configuration for the observability module.
112/// 
113/// This struct defines the configuration options for tracing and metrics collection in Ri.
114#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
115#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct RiObservabilityConfig {
117    /// Whether distributed tracing is enabled
118    pub tracing_enabled: bool,
119    /// Whether metrics collection is enabled
120    pub metrics_enabled: bool,
121    /// Sampling rate for distributed tracing (0.0 to 1.0)
122    pub tracing_sampling_rate: f64,
123    /// Sampling strategy for distributed tracing
124    pub tracing_sampling_strategy: String,
125    /// Window size for metrics aggregation in seconds
126    pub metrics_window_size_secs: u64,
127    /// Bucket size for metrics aggregation in seconds
128    pub metrics_bucket_size_secs: u64,
129}
130
131impl Default for RiObservabilityConfig {
132    /// Returns the default configuration for observability.
133    /// 
134    /// Default values:
135    /// - tracing_enabled: true
136    /// - metrics_enabled: true
137    /// - tracing_sampling_rate: 0.1 (10% sampling)
138    /// - tracing_sampling_strategy: "rate" (fixed rate sampling)
139    /// - metrics_window_size_secs: 300 (5 minutes)
140    /// - metrics_bucket_size_secs: 10 (10 seconds)
141    fn default() -> Self {
142        Self {
143            tracing_enabled: true,
144            metrics_enabled: true,
145            tracing_sampling_rate: 0.1, // 10% sampling by default
146            tracing_sampling_strategy: "rate".to_string(), // fixed rate sampling by default
147            metrics_window_size_secs: 300, // 5 minutes
148            metrics_bucket_size_secs: 10,  // 10 seconds
149        }
150    }
151}
152
153#[cfg(feature = "pyo3")]
154/// Python methods for RiObservabilityConfig
155#[pyo3::prelude::pymethods]
156impl RiObservabilityConfig {
157    #[new]
158    fn py_new() -> Self {
159        Self::default()
160    }
161    
162    /// Set tracing enabled flag from Python
163    fn set_tracing_enabled(&mut self, tracing_enabled: bool) {
164        self.tracing_enabled = tracing_enabled;
165    }
166    
167    /// Get tracing enabled flag from Python
168    fn get_tracing_enabled(&self) -> bool {
169        self.tracing_enabled
170    }
171    
172    /// Set metrics enabled flag from Python
173    fn set_metrics_enabled(&mut self, metrics_enabled: bool) {
174        self.metrics_enabled = metrics_enabled;
175    }
176    
177    /// Get metrics enabled flag from Python
178    fn get_metrics_enabled(&self) -> bool {
179        self.metrics_enabled
180    }
181    
182    /// Set tracing sampling rate from Python
183    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    /// Get tracing sampling rate from Python
193    fn get_tracing_sampling_rate(&self) -> f64 {
194        self.tracing_sampling_rate
195    }
196    
197    /// Set tracing sampling strategy from Python
198    fn set_tracing_sampling_strategy(&mut self, tracing_sampling_strategy: String) {
199        self.tracing_sampling_strategy = tracing_sampling_strategy;
200    }
201    
202    /// Get tracing sampling strategy from Python
203    fn get_tracing_sampling_strategy(&self) -> String {
204        self.tracing_sampling_strategy.clone()
205    }
206    
207    /// Set metrics window size in seconds from Python
208    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    /// Get metrics window size in seconds from Python
213    fn get_metrics_window_size_secs(&self) -> u64 {
214        self.metrics_window_size_secs
215    }
216    
217    /// Set metrics bucket size in seconds from Python
218    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    /// Get metrics bucket size in seconds from Python
223    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    /// Creates a new observability module with default configuration.
236    /// 
237    /// # Returns
238    /// 
239    /// A new `RiObservabilityModule` instance with default configuration
240    pub fn new() -> Self {
241        Self {
242            tracer: None,
243            metrics_registry: None,
244            config: RiObservabilityConfig::default(),
245        }
246    }
247    
248    /// Configures the observability module with custom settings.
249    /// 
250    /// # Parameters
251    /// 
252    /// - `config`: The custom configuration to apply
253    /// 
254    /// # Returns
255    /// 
256    /// The updated `RiObservabilityModule` instance
257    pub fn with_config(mut self, config: RiObservabilityConfig) -> Self {
258        self.config = config;
259        self
260    }
261    
262    /// Initializes tracing with the configured sampling strategy.
263    /// 
264    /// This method sets up the distributed tracer with the specified sampling strategy.
265    fn init_tracing(&mut self) {
266        if self.config.tracing_enabled {
267            // Initialize tracer with the configured rate
268            // Note: In a real implementation, we'd use the appropriate strategy
269            tracing::init_tracer(self.config.tracing_sampling_rate);
270        }
271    }
272    
273    /// Initializes the metrics registry.
274    /// 
275    /// This method creates and configures the metrics registry for collecting and aggregating metrics.
276    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    /// Creates common service metrics.
284    /// 
285    /// This method registers standard service metrics including:
286    /// - Request duration histogram
287    /// - Request counter
288    /// - Error counter
289    /// - Active connections gauge
290    /// - Service startup time
291    /// - Module initialization time
292    /// - Request queue length
293    /// - Middleware execution time
294    /// - Cache metrics (hits, misses, entries, memory usage)
295    /// - Database query time
296    /// 
297    /// # Returns
298    /// 
299    /// A `RiResult<()>` indicating success or failure
300    fn create_service_metrics(&self) -> RiResult<()> {
301        if let Some(registry) = &self.metrics_registry {
302            // Request duration histogram
303            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], // seconds
308                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            // Request counter
317            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            // Error counter
331            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            // Active connections gauge
345            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            // Service startup time gauge
359            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            // Module initialization time histogram
373            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            // Request queue length gauge
387            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            // Middleware execution time histogram
401            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            // Cache metrics
415            // Cache hit counter
416            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            // Cache miss counter
430            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            // Cache entries gauge
444            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            // Cache memory usage gauge
458            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            // Cache eviction counter
472            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            // Database metrics
486            // Database query time histogram
487            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            // Database active connections gauge
501            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            // Database idle connections gauge
515            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            // Database connection timeouts counter
529            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            // Database errors counter
543            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            // Database transactions counter
557            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            // Database transaction commits counter
571            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            // Database transaction rollbacks counter
585            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    /// Exports observability data.
603    /// 
604    /// This method collects and returns the current observability data, including metrics in Prometheus
605    /// format and information about active traces and spans.
606    /// 
607    /// # Returns
608    /// 
609    /// A `RiObservabilityData` struct containing the exported observability data
610    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/// Exported observability data structure.
629/// 
630/// This struct represents the observability data that can be exported from the Ri system,
631/// including metrics in Prometheus format and information about active traces and spans.
632#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
633#[derive(Debug, Clone, Serialize, Deserialize)]
634pub struct RiObservabilityData {
635    /// Metrics data in Prometheus format
636    pub metrics: String,
637    /// Number of active traces
638    pub active_traces: usize,
639    /// Number of active spans
640    pub active_spans: usize,
641}
642
643#[cfg(feature = "pyo3")]
644/// Python methods for RiObservabilityData
645#[pyo3::prelude::pymethods]
646impl RiObservabilityData {
647    /// Create new observability data from Python
648    #[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    /// Get metrics data from Python
658    #[pyo3(name = "get_metrics")]
659    fn get_metrics_impl(&self) -> String {
660        self.metrics.clone()
661    }
662    
663    /// Get active traces count from Python
664    #[pyo3(name = "get_active_traces")]
665    fn get_active_traces_impl(&self) -> usize {
666        self.active_traces
667    }
668    
669    /// Get active spans count from Python
670    #[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    /// Returns the name of the observability module.
687    /// 
688    /// # Returns
689    /// 
690    /// The module name as a string
691    fn name(&self) -> &str {
692        "Ri.Observability"
693    }
694    
695    /// Indicates whether the observability module is critical.
696    /// 
697    /// The observability module is non-critical, meaning that if it fails to initialize or operate,
698    /// it should not break the entire application. This allows the core functionality to continue
699    /// even if observability features are unavailable.
700    /// 
701    /// # Returns
702    /// 
703    /// `false` since observability is non-critical
704    fn is_critical(&self) -> bool {
705        false // Non-critical, should not break the app if observability fails
706    }
707    
708    /// Initializes the observability module.
709    /// 
710    /// This method performs the following steps:
711    /// 1. Loads configuration from the service context
712    /// 2. Initializes tracing with the configured sampling rate
713    /// 3. Initializes the metrics registry
714    /// 4. Creates common service metrics
715    /// 5. Registers lifecycle hooks for automatic metrics collection
716    /// 6. Logs initialization completion
717    /// 
718    /// # Parameters
719    /// 
720    /// - `ctx`: The service context containing configuration and other services
721    /// 
722    /// # Returns
723    /// 
724    /// A `RiResult<()>` indicating success or failure
725    async fn init(&mut self, ctx: &mut RiServiceContext) -> RiResult<()> {
726        // Load configuration
727        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        // Initialize components
749        self.init_tracing();
750        self.init_metrics();
751        self.create_service_metrics()?;
752        
753        // Register lifecycle hooks
754        let hooks: &mut crate::hooks::RiHookBus = ctx.hooks_mut();
755        
756        // Hook into request lifecycle for automatic metrics collection
757        hooks.register(
758            crate::hooks::RiHookKind::Startup,
759            "dms.observability.lifecycle".to_string(),
760            |_ctx, _event: &crate::hooks::RiHookEvent| {
761                // Could add automatic span creation here
762                Ok(())
763            },
764        );
765        
766        let logger = ctx.logger();
767        logger.info("Ri.Observability", "Observability module initialized")?;
768        
769        Ok(())
770    }
771    
772    /// Performs cleanup after the application has shut down.
773    /// 
774    /// This method exports the final observability data and logs information about active traces
775    /// and spans at the time of shutdown.
776    /// 
777    /// # Parameters
778    /// 
779    /// - `ctx`: The service context containing the logger service
780    /// 
781    /// # Returns
782    /// 
783    /// A `RiResult<()>` indicating success or failure
784    async fn after_shutdown(&mut self, ctx: &mut RiServiceContext) -> RiResult<()> {
785        // Export final observability data
786        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}