Skip to main content

kindly_guard_server/telemetry/
standard.rs

1// Copyright 2025 Kindly Software Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//! Standard telemetry implementation using OpenTelemetry
15
16use super::{
17    async_trait, Result, TelemetryConfig, TelemetryMetric, TelemetryProvider,
18    TelemetryProviderFactory, TelemetrySpan,
19};
20use std::sync::{Arc, Mutex};
21use tracing::{debug, info};
22
23/// Standard telemetry provider using in-memory storage
24pub struct StandardTelemetryProvider {
25    config: TelemetryConfig,
26    spans: Arc<Mutex<Vec<TelemetrySpan>>>,
27    metrics: Arc<Mutex<Vec<TelemetryMetric>>>,
28    events: Arc<Mutex<Vec<(String, Vec<(String, String)>)>>>,
29}
30
31impl StandardTelemetryProvider {
32    pub fn new(config: TelemetryConfig) -> Self {
33        Self {
34            config,
35            spans: Arc::new(Mutex::new(Vec::new())),
36            metrics: Arc::new(Mutex::new(Vec::new())),
37            events: Arc::new(Mutex::new(Vec::new())),
38        }
39    }
40
41    /// Get collected metrics (for testing/debugging)
42    pub fn get_metrics(&self) -> Vec<TelemetryMetric> {
43        self.metrics.lock().unwrap().clone()
44    }
45}
46
47#[async_trait]
48impl TelemetryProvider for StandardTelemetryProvider {
49    fn start_span(&self, name: &str) -> TelemetrySpan {
50        let span = TelemetrySpan {
51            name: name.to_string(),
52            start_time: std::time::Instant::now(),
53            attributes: vec![
54                ("service.name".to_string(), self.config.service_name.clone()),
55                (
56                    "service.version".to_string(),
57                    self.config.service_version.clone(),
58                ),
59            ],
60        };
61
62        if self.config.tracing_enabled {
63            debug!("Starting span: {}", name);
64        }
65
66        span
67    }
68
69    fn end_span(&self, span: TelemetrySpan) {
70        if !self.config.tracing_enabled {
71            return;
72        }
73
74        let duration = span.start_time.elapsed();
75        debug!("Ending span: {} (duration: {:?})", span.name, duration);
76
77        if let Ok(mut spans) = self.spans.lock() {
78            spans.push(span);
79
80            // Keep only last 1000 spans to prevent memory growth
81            if spans.len() > 1000 {
82                spans.drain(0..100);
83            }
84        }
85    }
86
87    fn record_metric(&self, metric: TelemetryMetric) {
88        if !self.config.metrics_enabled {
89            return;
90        }
91
92        debug!("Recording metric: {} = {:?}", metric.name, metric.value);
93
94        if let Ok(mut metrics) = self.metrics.lock() {
95            metrics.push(metric);
96
97            // Keep only last 10000 metrics
98            if metrics.len() > 10000 {
99                metrics.drain(0..1000);
100            }
101        }
102    }
103
104    fn add_event(&self, name: &str, attributes: Vec<(&str, &str)>) {
105        if !self.config.tracing_enabled {
106            return;
107        }
108
109        debug!("Adding event: {}", name);
110
111        let attrs: Vec<(String, String)> = attributes
112            .into_iter()
113            .map(|(k, v)| (k.to_string(), v.to_string()))
114            .collect();
115
116        if let Ok(mut events) = self.events.lock() {
117            events.push((name.to_string(), attrs));
118
119            // Keep only last 5000 events
120            if events.len() > 5000 {
121                events.drain(0..500);
122            }
123        }
124    }
125
126    fn set_status(&self, span: &TelemetrySpan, is_error: bool, message: Option<&str>) {
127        if !self.config.tracing_enabled {
128            return;
129        }
130
131        debug!(
132            "Setting span status for {}: error={}, message={:?}",
133            span.name, is_error, message
134        );
135    }
136
137    async fn flush(&self) -> Result<()> {
138        info!("Flushing telemetry data");
139
140        // In a real implementation, this would export to OTLP endpoint
141        if let Some(endpoint) = &self.config.export_endpoint {
142            debug!("Would export to: {}", endpoint);
143        }
144
145        // Log summary
146        let spans_count = self.spans.lock().map(|s| s.len()).unwrap_or(0);
147        let metrics_count = self.metrics.lock().map(|m| m.len()).unwrap_or(0);
148        let events_count = self.events.lock().map(|e| e.len()).unwrap_or(0);
149
150        info!(
151            "Telemetry summary: {} spans, {} metrics, {} events",
152            spans_count, metrics_count, events_count
153        );
154
155        Ok(())
156    }
157
158    async fn shutdown(&self) -> Result<()> {
159        info!("Shutting down telemetry provider");
160        self.flush().await?;
161
162        // Clear all data
163        if let Ok(mut spans) = self.spans.lock() {
164            spans.clear();
165        }
166        if let Ok(mut metrics) = self.metrics.lock() {
167            metrics.clear();
168        }
169        if let Ok(mut events) = self.events.lock() {
170            events.clear();
171        }
172
173        Ok(())
174    }
175}
176
177/// Factory for standard telemetry
178pub struct StandardTelemetryFactory;
179
180impl TelemetryProviderFactory for StandardTelemetryFactory {
181    fn create(&self, config: &TelemetryConfig) -> Result<Arc<dyn TelemetryProvider>> {
182        Ok(Arc::new(StandardTelemetryProvider::new(config.clone())))
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189
190    #[tokio::test]
191    async fn test_standard_telemetry() {
192        let config = TelemetryConfig::default();
193        let provider = StandardTelemetryProvider::new(config);
194
195        // Test span
196        let span = provider.start_span("test.operation");
197        std::thread::sleep(std::time::Duration::from_millis(10));
198        provider.end_span(span);
199
200        // Test metric
201        provider.record_metric(TelemetryMetric {
202            name: "test.counter".to_string(),
203            value: crate::telemetry::MetricValue::Counter(42),
204            labels: vec![],
205        });
206
207        // Test event
208        provider.add_event("test.event", vec![("key", "value")]);
209
210        // Verify data was collected
211        let metrics = provider.get_metrics();
212        assert_eq!(metrics.len(), 1);
213        assert_eq!(metrics[0].name, "test.counter");
214
215        // Test flush
216        provider.flush().await.unwrap();
217    }
218}