Skip to main content

mockforge_tracing/
lib.rs

1//! OpenTelemetry tracing integration for MockForge
2//!
3//! This crate provides distributed tracing capabilities across all MockForge protocols
4//! (HTTP, gRPC, WebSocket, GraphQL) using OpenTelemetry and Jaeger.
5
6pub mod context;
7pub mod exporter;
8pub mod tracer;
9
10pub use context::{
11    extract_from_axum_headers, extract_trace_context, inject_into_axum_headers,
12    inject_trace_context, TraceContext,
13};
14pub use exporter::{
15    ExporterError, ExporterType, JaegerExporter, OtlpCompression, OtlpExporter, OtlpProtocol,
16};
17pub use tracer::{init_tracer, shutdown_tracer, TracingConfig};
18
19use opentelemetry::global::BoxedSpan;
20use opentelemetry::trace::{Span, SpanKind, Status, Tracer};
21use opentelemetry::{global, KeyValue};
22use std::time::SystemTime;
23
24/// Protocol types for tracing
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum Protocol {
27    Http,
28    Grpc,
29    WebSocket,
30    GraphQL,
31}
32
33impl Protocol {
34    pub fn as_str(&self) -> &'static str {
35        match self {
36            Protocol::Http => "http",
37            Protocol::Grpc => "grpc",
38            Protocol::WebSocket => "websocket",
39            Protocol::GraphQL => "graphql",
40        }
41    }
42}
43
44/// Create a span for an incoming request
45pub fn create_request_span(
46    protocol: Protocol,
47    operation_name: &str,
48    attributes: Vec<KeyValue>,
49) -> BoxedSpan {
50    let tracer = global::tracer("mockforge");
51
52    let mut span = tracer
53        .span_builder(operation_name.to_string())
54        .with_kind(SpanKind::Server)
55        .with_start_time(SystemTime::now())
56        .with_attributes(attributes)
57        .start(&tracer);
58
59    // Add protocol attribute
60    span.set_attribute(KeyValue::new("mockforge.protocol", protocol.as_str()));
61
62    span
63}
64
65/// Record span success with optional attributes
66pub fn record_success(span: &mut BoxedSpan, attributes: Vec<KeyValue>) {
67    for attr in attributes {
68        span.set_attribute(attr);
69    }
70    span.set_status(Status::Ok);
71}
72
73/// Record span error
74pub fn record_error(span: &mut BoxedSpan, error_message: &str) {
75    span.set_status(Status::error(error_message.to_string()));
76    span.set_attribute(KeyValue::new("error", true));
77    span.set_attribute(KeyValue::new("error.message", error_message.to_string()));
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn test_protocol_as_str() {
86        assert_eq!(Protocol::Http.as_str(), "http");
87        assert_eq!(Protocol::Grpc.as_str(), "grpc");
88        assert_eq!(Protocol::WebSocket.as_str(), "websocket");
89        assert_eq!(Protocol::GraphQL.as_str(), "graphql");
90    }
91
92    #[test]
93    fn test_protocol_debug() {
94        assert_eq!(format!("{:?}", Protocol::Http), "Http");
95        assert_eq!(format!("{:?}", Protocol::Grpc), "Grpc");
96        assert_eq!(format!("{:?}", Protocol::WebSocket), "WebSocket");
97        assert_eq!(format!("{:?}", Protocol::GraphQL), "GraphQL");
98    }
99
100    #[test]
101    fn test_protocol_clone() {
102        let proto = Protocol::Http;
103        let cloned = Clone::clone(&proto);
104        assert_eq!(proto, cloned);
105    }
106
107    #[test]
108    fn test_protocol_copy() {
109        let proto = Protocol::Grpc;
110        let copied = proto; // Copy, not move
111        assert_eq!(proto, copied);
112        assert_eq!(Protocol::Grpc, copied); // proto still accessible
113    }
114
115    #[test]
116    fn test_protocol_eq() {
117        assert_eq!(Protocol::Http, Protocol::Http);
118        assert_ne!(Protocol::Http, Protocol::Grpc);
119        assert_ne!(Protocol::WebSocket, Protocol::GraphQL);
120    }
121
122    #[test]
123    fn test_create_request_span() {
124        // Initialize a no-op tracer for testing
125        use opentelemetry_sdk::trace::TracerProvider as SdkTracerProvider;
126
127        let provider = SdkTracerProvider::builder().build();
128        global::set_tracer_provider(provider);
129
130        let attributes = vec![
131            KeyValue::new("http.method", "GET"),
132            KeyValue::new("http.url", "/api/users"),
133        ];
134
135        let span = create_request_span(Protocol::Http, "test-operation", attributes);
136
137        // Verify span was created (it's a BoxedSpan)
138        assert!(!span.span_context().trace_id().to_string().is_empty());
139    }
140
141    #[test]
142    fn test_create_request_span_all_protocols() {
143        use opentelemetry_sdk::trace::TracerProvider as SdkTracerProvider;
144
145        let provider = SdkTracerProvider::builder().build();
146        global::set_tracer_provider(provider);
147
148        let protocols = [
149            Protocol::Http,
150            Protocol::Grpc,
151            Protocol::WebSocket,
152            Protocol::GraphQL,
153        ];
154
155        for protocol in protocols {
156            let span = create_request_span(protocol, "test-op", vec![]);
157            assert!(!span.span_context().trace_id().to_string().is_empty());
158        }
159    }
160
161    #[test]
162    fn test_record_success() {
163        use opentelemetry_sdk::trace::TracerProvider as SdkTracerProvider;
164
165        let provider = SdkTracerProvider::builder().build();
166        global::set_tracer_provider(provider);
167
168        let mut span = create_request_span(Protocol::Http, "success-test", vec![]);
169
170        let attributes = vec![
171            KeyValue::new("http.status_code", 200),
172            KeyValue::new("response.size", 1024),
173        ];
174
175        record_success(&mut span, attributes);
176        // If we get here without panic, the function worked
177    }
178
179    #[test]
180    fn test_record_success_empty_attributes() {
181        use opentelemetry_sdk::trace::TracerProvider as SdkTracerProvider;
182
183        let provider = SdkTracerProvider::builder().build();
184        global::set_tracer_provider(provider);
185
186        let mut span = create_request_span(Protocol::Grpc, "success-empty", vec![]);
187        record_success(&mut span, vec![]);
188        // Should complete without error
189    }
190
191    #[test]
192    fn test_record_error() {
193        use opentelemetry_sdk::trace::TracerProvider as SdkTracerProvider;
194
195        let provider = SdkTracerProvider::builder().build();
196        global::set_tracer_provider(provider);
197
198        let mut span = create_request_span(Protocol::Http, "error-test", vec![]);
199
200        record_error(&mut span, "Connection refused");
201        // If we get here without panic, the function worked
202    }
203
204    #[test]
205    fn test_record_error_with_details() {
206        use opentelemetry_sdk::trace::TracerProvider as SdkTracerProvider;
207
208        let provider = SdkTracerProvider::builder().build();
209        global::set_tracer_provider(provider);
210
211        let mut span = create_request_span(Protocol::WebSocket, "error-details", vec![]);
212
213        record_error(&mut span, "WebSocket handshake failed: 401 Unauthorized");
214        // Should complete without error
215    }
216
217    #[test]
218    fn test_record_error_empty_message() {
219        use opentelemetry_sdk::trace::TracerProvider as SdkTracerProvider;
220
221        let provider = SdkTracerProvider::builder().build();
222        global::set_tracer_provider(provider);
223
224        let mut span = create_request_span(Protocol::GraphQL, "error-empty", vec![]);
225        record_error(&mut span, "");
226        // Should handle empty error messages gracefully
227    }
228}