Skip to main content

schema_registry_observability/
lib.rs

1//! Observability: Prometheus metrics, OpenTelemetry tracing, structured logging
2//!
3//! This crate provides comprehensive observability for the LLM Schema Registry:
4//! - 40+ Prometheus metrics (RED + USE + Business metrics)
5//! - Distributed tracing with OpenTelemetry and Jaeger
6//! - Structured JSON logging with correlation IDs
7//! - HTTP and gRPC middleware for automatic instrumentation
8//! - SLI/SLO monitoring support
9
10pub mod logging;
11pub mod metrics;
12pub mod middleware;
13pub mod tracing_setup;
14
15pub use metrics::MetricsCollector;
16pub use tracing_setup::{
17    init_tracing, setup_tracing, shutdown_tracing, TracingConfig,
18    context as trace_context, correlation,
19};
20pub use logging::{LogContext, LogSamplingConfig, ModuleLogLevels};
21pub use middleware::{
22    metrics_middleware, tracing_middleware, observability_middleware,
23};
24
25use std::sync::Arc;
26
27/// Observability manager that coordinates all observability components
28pub struct ObservabilityManager {
29    pub metrics: Arc<MetricsCollector>,
30    pub tracing_config: TracingConfig,
31}
32
33impl ObservabilityManager {
34    /// Creates a new observability manager
35    pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
36        let metrics = MetricsCollector::new()?;
37        let tracing_config = TracingConfig::default();
38
39        Ok(Self {
40            metrics,
41            tracing_config,
42        })
43    }
44
45    /// Initializes all observability components
46    pub fn initialize(&self) -> Result<(), Box<dyn std::error::Error>> {
47        // Initialize tracing
48        init_tracing(self.tracing_config.clone())?;
49
50        tracing::info!(
51            metrics_count = self.metrics.metric_count(),
52            "Observability initialized successfully"
53        );
54
55        Ok(())
56    }
57
58    /// Gets metrics in Prometheus format
59    pub fn export_metrics(&self) -> Result<String, prometheus::Error> {
60        self.metrics.export()
61    }
62
63    /// Shuts down observability gracefully
64    pub fn shutdown(&self) {
65        shutdown_tracing();
66    }
67}
68
69impl Default for ObservabilityManager {
70    fn default() -> Self {
71        Self::new().expect("Failed to create ObservabilityManager")
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    #[test]
80    fn test_observability_manager_creation() {
81        let manager = ObservabilityManager::new().unwrap();
82        assert!(manager.metrics.metric_count() > 0);
83    }
84
85    #[test]
86    fn test_observability_manager_default() {
87        let manager = ObservabilityManager::default();
88        assert!(manager.metrics.metric_count() > 0);
89    }
90
91    #[test]
92    fn test_observability_manager_metrics() {
93        let manager = ObservabilityManager::new().unwrap();
94        assert!(Arc::strong_count(&manager.metrics) >= 1);
95    }
96
97    #[test]
98    fn test_observability_manager_tracing_config() {
99        let manager = ObservabilityManager::new().unwrap();
100        assert!(manager.tracing_config.service_name.len() > 0);
101    }
102
103    #[test]
104    fn test_export_metrics_format() {
105        let manager = ObservabilityManager::new().unwrap();
106        let exported = manager.export_metrics();
107        assert!(exported.is_ok());
108    }
109
110    #[test]
111    fn test_multiple_managers() {
112        let manager1 = ObservabilityManager::new().unwrap();
113        let manager2 = ObservabilityManager::new().unwrap();
114
115        assert!(!Arc::ptr_eq(&manager1.metrics, &manager2.metrics));
116    }
117
118    #[test]
119    fn test_metrics_collector_count() {
120        let manager = ObservabilityManager::new().unwrap();
121        let count = manager.metrics.metric_count();
122        assert!(count > 0);
123    }
124
125    #[test]
126    fn test_tracing_config_has_service_name() {
127        let manager = ObservabilityManager::new().unwrap();
128        assert!(!manager.tracing_config.service_name.is_empty());
129    }
130
131    #[test]
132    fn test_observability_manager_shutdown() {
133        let manager = ObservabilityManager::new().unwrap();
134        manager.shutdown();
135        // Should not panic
136    }
137
138    #[test]
139    fn test_metrics_export_not_empty() {
140        let manager = ObservabilityManager::new().unwrap();
141        let metrics = manager.export_metrics().unwrap();
142        assert!(!metrics.is_empty());
143    }
144
145    #[test]
146    fn test_observability_manager_arc_metrics() {
147        let manager = ObservabilityManager::new().unwrap();
148        let metrics_clone = Arc::clone(&manager.metrics);
149        assert!(Arc::ptr_eq(&metrics_clone, &manager.metrics));
150    }
151
152    #[test]
153    fn test_tracing_config_default() {
154        let config = TracingConfig::default();
155        assert!(!config.service_name.is_empty());
156    }
157
158    #[test]
159    fn test_metrics_collector_creation() {
160        let collector = MetricsCollector::new().unwrap();
161        assert!(collector.metric_count() > 0);
162    }
163
164    #[test]
165    fn test_multiple_metrics_exports() {
166        let manager = ObservabilityManager::new().unwrap();
167        let export1 = manager.export_metrics();
168        let export2 = manager.export_metrics();
169        assert!(export1.is_ok());
170        assert!(export2.is_ok());
171    }
172
173    #[test]
174    fn test_observability_manager_metrics_accessible() {
175        let manager = ObservabilityManager::new().unwrap();
176        let _count = manager.metrics.metric_count();
177        // Should be accessible without issues
178    }
179
180    #[test]
181    fn test_tracing_config_clone() {
182        let manager = ObservabilityManager::new().unwrap();
183        let config = manager.tracing_config.clone();
184        assert_eq!(config.service_name, manager.tracing_config.service_name);
185    }
186
187    #[test]
188    fn test_observability_components_independent() {
189        let manager1 = ObservabilityManager::new().unwrap();
190        let manager2 = ObservabilityManager::new().unwrap();
191
192        let metrics1_count = manager1.metrics.metric_count();
193        let metrics2_count = manager2.metrics.metric_count();
194
195        assert_eq!(metrics1_count, metrics2_count);
196    }
197
198    #[test]
199    fn test_metrics_export_is_prometheus_format() {
200        let manager = ObservabilityManager::new().unwrap();
201        let exported = manager.export_metrics().unwrap();
202        // Prometheus format typically contains HELP and TYPE lines
203        assert!(exported.contains("# HELP") || exported.len() > 0);
204    }
205
206    #[test]
207    fn test_observability_manager_creation_success() {
208        let result = ObservabilityManager::new();
209        assert!(result.is_ok());
210    }
211
212    #[test]
213    fn test_shutdown_can_be_called_multiple_times() {
214        let manager = ObservabilityManager::new().unwrap();
215        manager.shutdown();
216        manager.shutdown();
217        // Should not panic
218    }
219
220    #[test]
221    fn test_metrics_count_positive() {
222        let manager = ObservabilityManager::new().unwrap();
223        assert!(manager.metrics.metric_count() > 0);
224    }
225
226    #[test]
227    fn test_tracing_config_service_name_not_empty() {
228        let manager = ObservabilityManager::new().unwrap();
229        assert!(!manager.tracing_config.service_name.is_empty());
230    }
231
232    #[test]
233    fn test_export_metrics_returns_string() {
234        let manager = ObservabilityManager::new().unwrap();
235        let exported = manager.export_metrics();
236        assert!(exported.is_ok());
237        assert!(exported.unwrap().is_ascii());
238    }
239
240    #[test]
241    fn test_metrics_collector_metric_count() {
242        let collector = MetricsCollector::new().unwrap();
243        let count = collector.metric_count();
244        assert!(count > 0);
245    }
246
247    #[test]
248    fn test_observability_manager_default_creation() {
249        let _manager = ObservabilityManager::default();
250        // Should create successfully
251    }
252
253    #[test]
254    fn test_metrics_collector_export() {
255        let collector = MetricsCollector::new().unwrap();
256        let exported = collector.export();
257        assert!(exported.is_ok());
258    }
259
260    #[test]
261    fn test_observability_manager_fields() {
262        let manager = ObservabilityManager::new().unwrap();
263        assert!(Arc::strong_count(&manager.metrics) >= 1);
264        assert!(!manager.tracing_config.service_name.is_empty());
265    }
266
267    #[test]
268    fn test_metrics_arc_reference_counting() {
269        let manager = ObservabilityManager::new().unwrap();
270        let original_count = Arc::strong_count(&manager.metrics);
271        let _clone = Arc::clone(&manager.metrics);
272        assert_eq!(Arc::strong_count(&manager.metrics), original_count + 1);
273    }
274
275    #[test]
276    fn test_observability_manager_lifecycle() {
277        let manager = ObservabilityManager::new().unwrap();
278        let _export = manager.export_metrics();
279        manager.shutdown();
280        // Full lifecycle should work
281    }
282
283    #[test]
284    fn test_tracing_config_fields() {
285        let config = TracingConfig::default();
286        assert!(!config.service_name.is_empty());
287    }
288
289    #[test]
290    fn test_metrics_collector_independent_instances() {
291        let collector1 = MetricsCollector::new().unwrap();
292        let collector2 = MetricsCollector::new().unwrap();
293
294        assert_eq!(collector1.metric_count(), collector2.metric_count());
295    }
296
297    #[test]
298    fn test_observability_manager_error_handling() {
299        // Should handle creation gracefully
300        let result = ObservabilityManager::new();
301        assert!(result.is_ok());
302    }
303}