1use std::{
8 collections::HashMap,
9 sync::{Arc, RwLock},
10};
11
12#[cfg(feature = "otel")]
14pub use allframe_macros::traced;
15
16#[derive(Debug, Clone)]
18pub struct Span {
19 pub span_id: String,
21 pub parent_span_id: Option<String>,
23 pub trace_id: String,
25 pub name: String,
27 pub attributes: HashMap<String, String>,
29 pub status: String,
31 pub error_message: String,
33 pub duration_ms: f64,
35 pub layer: String,
37}
38
39#[derive(Clone, Default)]
41pub struct SpanRecorder {
42 spans: Arc<RwLock<Vec<Span>>>,
43}
44
45impl SpanRecorder {
46 pub fn new() -> Self {
48 Self {
49 spans: Arc::new(RwLock::new(Vec::new())),
50 }
51 }
52
53 pub fn record(&self, span: Span) {
55 let mut spans = self.spans.write().unwrap();
56 spans.push(span);
57 }
58
59 pub fn spans(&self) -> Vec<Span> {
61 self.spans.read().unwrap().clone()
62 }
63}
64
65pub fn current_span_id() -> String {
67 "span-placeholder".to_string()
68}
69
70pub fn current_trace_id() -> String {
72 "trace-placeholder".to_string()
73}
74
75pub fn start_trace(_trace_id: &str) {
77 }
79
80pub fn set_baggage(_key: &str, _value: &str) {
82 }
84
85pub fn get_baggage(_key: &str) -> Option<String> {
87 None
89}
90
91#[derive(Debug, Clone)]
93pub struct SpanContext {
94 pub trace_id: String,
96 pub parent_span_id: String,
98 pub sampled: bool,
100}
101
102impl SpanContext {
103 pub fn new(trace_id: &str, parent_span_id: &str) -> Self {
105 Self {
106 trace_id: trace_id.to_string(),
107 parent_span_id: parent_span_id.to_string(),
108 sampled: true,
109 }
110 }
111}
112
113pub fn inject_context(_context: &SpanContext) -> HashMap<String, String> {
115 let mut headers = HashMap::new();
116 headers.insert("traceparent".to_string(), "placeholder".to_string());
117 headers
118}
119
120pub fn extract_context(headers: &HashMap<String, String>) -> Option<SpanContext> {
122 headers.get("traceparent").map(|_| SpanContext {
123 trace_id: "extracted-trace".to_string(),
124 parent_span_id: "extracted-span".to_string(),
125 sampled: true,
126 })
127}
128
129#[derive(Clone, Default)]
131pub struct MetricsRecorder {
132 counters: Arc<RwLock<HashMap<String, u64>>>,
133 gauges: Arc<RwLock<HashMap<String, i64>>>,
134 histograms: Arc<RwLock<HashMap<String, Vec<f64>>>>,
135}
136
137impl MetricsRecorder {
138 pub fn new() -> Self {
140 Self {
141 counters: Arc::new(RwLock::new(HashMap::new())),
142 gauges: Arc::new(RwLock::new(HashMap::new())),
143 histograms: Arc::new(RwLock::new(HashMap::new())),
144 }
145 }
146
147 pub fn current() -> Self {
149 Self::new()
150 }
151
152 pub fn get_counter(&self, name: &str) -> u64 {
154 self.counters
155 .read()
156 .unwrap()
157 .get(name)
158 .copied()
159 .unwrap_or(0)
160 }
161
162 pub fn get_gauge(&self, name: &str) -> i64 {
164 self.gauges.read().unwrap().get(name).copied().unwrap_or(0)
165 }
166
167 pub fn get_histogram(&self, name: &str) -> Histogram {
169 let values = self
170 .histograms
171 .read()
172 .unwrap()
173 .get(name)
174 .cloned()
175 .unwrap_or_default();
176 Histogram::new(values)
177 }
178
179 pub fn get_counter_with_labels(&self, name: &str, _labels: &[(&str, &str)]) -> u64 {
181 self.get_counter(name)
182 }
183}
184
185pub struct Histogram {
187 values: Vec<f64>,
188}
189
190impl Histogram {
191 pub fn new(values: Vec<f64>) -> Self {
193 Self { values }
194 }
195
196 pub fn count(&self) -> usize {
198 self.values.len()
199 }
200
201 pub fn sum(&self) -> f64 {
203 self.values.iter().sum()
204 }
205
206 pub fn p50(&self) -> f64 {
208 self.percentile(0.5)
209 }
210
211 pub fn p95(&self) -> f64 {
213 self.percentile(0.95)
214 }
215
216 pub fn p99(&self) -> f64 {
218 self.percentile(0.99)
219 }
220
221 fn percentile(&self, p: f64) -> f64 {
222 if self.values.is_empty() {
223 return 0.0;
224 }
225 let mut sorted = self.values.clone();
226 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
227 let index = ((self.values.len() as f64 - 1.0) * p) as usize;
228 sorted[index]
229 }
230}
231
232#[derive(Debug, Clone, PartialEq)]
234pub enum ExporterType {
235 Stdout,
237 Jaeger {
239 endpoint: String,
241 },
242 Otlp {
244 endpoint: String,
246 },
247}
248
249pub fn configure_exporter(_exporter: ExporterType) {
251 }
253
254pub fn configure_batch_export(_batch_size: usize, _flush_interval_ms: u64) {
256 }
258
259pub fn get_export_count() -> usize {
261 0
262}
263
264pub fn configure_sampling(_rate: f64) {
266 }
268
269pub fn enable_tracing() {
271 }
273
274pub fn disable_tracing() {
276 }
278
279#[derive(Debug, Clone)]
281pub struct OtelConfig {
282 pub service_name: String,
284 pub exporter_type: String,
286 pub sampling_rate: f64,
288 pub batch_size: usize,
290}
291
292pub async fn configure_from_file(_path: &str) -> Result<(), String> {
294 Ok(())
296}
297
298pub fn get_config() -> OtelConfig {
300 OtelConfig {
301 service_name: "allframe".to_string(),
302 exporter_type: "stdout".to_string(),
303 sampling_rate: 1.0,
304 batch_size: 512,
305 }
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311
312 #[test]
313 fn test_span_recorder() {
314 let recorder = SpanRecorder::new();
315
316 let span = Span {
317 span_id: "span-1".to_string(),
318 parent_span_id: None,
319 trace_id: "trace-1".to_string(),
320 name: "test".to_string(),
321 attributes: HashMap::new(),
322 status: "ok".to_string(),
323 error_message: String::new(),
324 duration_ms: 100.0,
325 layer: String::new(),
326 };
327
328 recorder.record(span.clone());
329 let spans = recorder.spans();
330
331 assert_eq!(spans.len(), 1);
332 assert_eq!(spans[0].span_id, "span-1");
333 }
334
335 #[test]
336 fn test_histogram() {
337 let hist = Histogram::new(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
338
339 assert_eq!(hist.count(), 5);
340 assert_eq!(hist.sum(), 15.0);
341 assert_eq!(hist.p50(), 3.0);
342 }
343}