allframe_core/otel/
mod.rs

1//! OpenTelemetry automatic instrumentation
2//!
3//! This module provides automatic distributed tracing, metrics, and context
4//! propagation for AllFrame applications.
5//!
6//! # Features
7//!
8//! - `otel` - Basic tracing support with the `#[traced]` macro
9//! - `otel-otlp` - Full OpenTelemetry integration with OTLP export
10//!
11//! # Quick Start
12//!
13//! ```rust,ignore
14//! use allframe_core::otel::Observability;
15//!
16//! let _guard = Observability::builder("my-service")
17//!     .service_version(env!("CARGO_PKG_VERSION"))
18//!     .environment_from_env()
19//!     .otlp_endpoint_from_env()
20//!     .json_logging()
21//!     .log_level_from_env()
22//!     .build()?;
23//!
24//! // Guard keeps the subscriber active
25//! // When dropped, flushes pending spans
26//! ```
27
28mod builder;
29mod testing;
30
31// Re-export the traced macro
32#[cfg(feature = "otel")]
33pub use allframe_macros::traced;
34
35// Re-export builder types
36pub use builder::{Observability, ObservabilityBuilder, ObservabilityError, ObservabilityGuard};
37
38// Re-export testing utilities
39pub use testing::{Histogram, MetricsRecorder, Span, SpanContext, SpanRecorder};
40
41// Legacy placeholder functions - kept for backwards compatibility
42// These will be removed in a future version
43
44use std::collections::HashMap;
45
46/// Get the current span ID (placeholder - use tracing for real spans)
47#[deprecated(since = "0.2.0", note = "Use tracing::Span::current() instead")]
48pub fn current_span_id() -> String {
49    "span-placeholder".to_string()
50}
51
52/// Get the current trace ID (placeholder - use tracing for real traces)
53#[deprecated(since = "0.2.0", note = "Use tracing::Span::current() instead")]
54pub fn current_trace_id() -> String {
55    "trace-placeholder".to_string()
56}
57
58/// Start a new trace (placeholder)
59#[deprecated(since = "0.2.0", note = "Use tracing spans instead")]
60pub fn start_trace(_trace_id: &str) {
61    // Placeholder for backwards compatibility
62}
63
64/// Set baggage value (placeholder)
65#[deprecated(since = "0.2.0", note = "Use opentelemetry baggage API instead")]
66pub fn set_baggage(_key: &str, _value: &str) {
67    // Placeholder for backwards compatibility
68}
69
70/// Get baggage value (placeholder)
71#[deprecated(since = "0.2.0", note = "Use opentelemetry baggage API instead")]
72pub fn get_baggage(_key: &str) -> Option<String> {
73    None
74}
75
76/// Inject context into headers (placeholder)
77#[deprecated(
78    since = "0.2.0",
79    note = "Use opentelemetry propagator API instead"
80)]
81pub fn inject_context(_context: &SpanContext) -> HashMap<String, String> {
82    let mut headers = HashMap::new();
83    headers.insert("traceparent".to_string(), "placeholder".to_string());
84    headers
85}
86
87/// Extract context from headers (placeholder)
88#[deprecated(
89    since = "0.2.0",
90    note = "Use opentelemetry propagator API instead"
91)]
92pub fn extract_context(headers: &HashMap<String, String>) -> Option<SpanContext> {
93    headers.get("traceparent").map(|_| SpanContext {
94        trace_id: "extracted-trace".to_string(),
95        parent_span_id: "extracted-span".to_string(),
96        sampled: true,
97    })
98}
99
100/// Exporter type (legacy)
101#[derive(Debug, Clone, PartialEq)]
102#[deprecated(since = "0.2.0", note = "Use ObservabilityBuilder instead")]
103pub enum ExporterType {
104    /// Stdout console exporter
105    Stdout,
106    /// Jaeger exporter
107    Jaeger {
108        /// Jaeger endpoint URL
109        endpoint: String,
110    },
111    /// OTLP exporter
112    Otlp {
113        /// OTLP endpoint URL
114        endpoint: String,
115    },
116}
117
118/// Configure exporter (placeholder)
119#[deprecated(since = "0.2.0", note = "Use ObservabilityBuilder instead")]
120#[allow(deprecated)]
121pub fn configure_exporter(_exporter: ExporterType) {
122    // Placeholder for backwards compatibility
123}
124
125/// Configure batch export (placeholder)
126#[deprecated(since = "0.2.0", note = "Use ObservabilityBuilder instead")]
127pub fn configure_batch_export(_batch_size: usize, _flush_interval_ms: u64) {
128    // Placeholder for backwards compatibility
129}
130
131/// Get export count (placeholder)
132#[deprecated(since = "0.2.0", note = "This function will be removed")]
133pub fn get_export_count() -> usize {
134    0
135}
136
137/// Configure sampling rate (placeholder)
138#[deprecated(since = "0.2.0", note = "Use ObservabilityBuilder instead")]
139pub fn configure_sampling(_rate: f64) {
140    // Placeholder for backwards compatibility
141}
142
143/// Enable tracing (placeholder)
144#[deprecated(since = "0.2.0", note = "Use ObservabilityBuilder::build() instead")]
145pub fn enable_tracing() {
146    // Placeholder for backwards compatibility
147}
148
149/// Disable tracing (placeholder)
150#[deprecated(since = "0.2.0", note = "Drop the ObservabilityGuard instead")]
151pub fn disable_tracing() {
152    // Placeholder for backwards compatibility
153}
154
155/// OTel configuration (legacy)
156#[derive(Debug, Clone)]
157#[deprecated(since = "0.2.0", note = "Use ObservabilityBuilder instead")]
158pub struct OtelConfig {
159    /// Service name
160    pub service_name: String,
161    /// Exporter type
162    pub exporter_type: String,
163    /// Sampling rate
164    pub sampling_rate: f64,
165    /// Batch size
166    pub batch_size: usize,
167}
168
169/// Configure from file (placeholder)
170#[deprecated(since = "0.2.0", note = "Use ObservabilityBuilder instead")]
171pub async fn configure_from_file(_path: &str) -> Result<(), String> {
172    Ok(())
173}
174
175/// Get current config (placeholder)
176#[deprecated(since = "0.2.0", note = "Use ObservabilityBuilder instead")]
177#[allow(deprecated)]
178pub fn get_config() -> OtelConfig {
179    OtelConfig {
180        service_name: "allframe".to_string(),
181        exporter_type: "stdout".to_string(),
182        sampling_rate: 1.0,
183        batch_size: 512,
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190
191    #[test]
192    fn test_span_recorder() {
193        let recorder = SpanRecorder::new();
194
195        let span = Span {
196            span_id: "span-1".to_string(),
197            parent_span_id: None,
198            trace_id: "trace-1".to_string(),
199            name: "test".to_string(),
200            attributes: std::collections::HashMap::new(),
201            status: "ok".to_string(),
202            error_message: String::new(),
203            duration_ms: 100.0,
204            layer: String::new(),
205        };
206
207        recorder.record(span.clone());
208        let spans = recorder.spans();
209
210        assert_eq!(spans.len(), 1);
211        assert_eq!(spans[0].span_id, "span-1");
212    }
213
214    #[test]
215    fn test_histogram() {
216        let hist = Histogram::new(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
217
218        assert_eq!(hist.count(), 5);
219        assert_eq!(hist.sum(), 15.0);
220        assert_eq!(hist.p50(), 3.0);
221    }
222
223    #[test]
224    fn test_builder_creation() {
225        // Just verify we can create the builder - fields are tested in builder.rs
226        let _builder = ObservabilityBuilder::new("test-service");
227    }
228}