ultrafast_mcp_monitoring/
tracing.rs

1//! Distributed tracing and OpenTelemetry integration for UltraFast MCP
2//!
3//! This module provides comprehensive distributed tracing capabilities for MCP servers
4//! and clients, including OpenTelemetry integration, span management, and trace export.
5
6use tracing::{Level, debug, error, info, warn};
7use tracing_subscriber::{
8    EnvFilter,
9    fmt::{format::FmtSpan, time::UtcTime},
10};
11
12/// Configuration for tracing system
13#[derive(Debug, Clone)]
14pub struct TracingConfig {
15    pub enabled: bool,
16    pub service_name: String,
17    pub service_version: String,
18    pub log_level: Level,
19    pub enable_console: bool,
20    pub enable_json: bool,
21    pub enable_otlp: bool,
22    pub otlp_endpoint: Option<String>,
23    pub enable_jaeger: bool,
24    pub jaeger_endpoint: Option<String>,
25    pub sample_rate: f64,
26    pub max_attributes: usize,
27}
28
29impl Default for TracingConfig {
30    fn default() -> Self {
31        Self {
32            enabled: true,
33            service_name: "ultrafast-mcp".to_string(),
34            service_version: "1.0.0".to_string(),
35            log_level: Level::INFO,
36            enable_console: true,
37            enable_json: false,
38            enable_otlp: false,
39            otlp_endpoint: None,
40            enable_jaeger: false,
41            jaeger_endpoint: None,
42            sample_rate: 1.0,
43            max_attributes: 10,
44        }
45    }
46}
47
48/// Tracing system for managing distributed tracing
49pub struct TracingSystem {
50    config: TracingConfig,
51    _guard: Option<()>,
52}
53
54impl TracingSystem {
55    /// Create a new tracing system
56    pub fn new(config: TracingConfig) -> Self {
57        Self {
58            config,
59            _guard: None,
60        }
61    }
62
63    /// Initialize the tracing system
64    pub fn init(config: TracingConfig) -> anyhow::Result<Self> {
65        if !config.enabled {
66            return Ok(Self::new(config));
67        }
68
69        // Initialize the subscriber with console output
70        if config.enable_console {
71            tracing_subscriber::fmt()
72                .with_timer(UtcTime::rfc_3339())
73                .with_span_events(FmtSpan::CLOSE)
74                .with_target(false)
75                .with_thread_ids(false)
76                .with_thread_names(false)
77                .with_env_filter(EnvFilter::from_default_env())
78                .init();
79        } else if config.enable_json {
80            tracing_subscriber::fmt()
81                .json()
82                .with_timer(UtcTime::rfc_3339())
83                .with_span_events(FmtSpan::CLOSE)
84                .with_env_filter(EnvFilter::from_default_env())
85                .init();
86        } else {
87            tracing_subscriber::fmt()
88                .with_env_filter(EnvFilter::from_default_env())
89                .init();
90        }
91
92        let guard = ();
93
94        info!(
95            "Tracing system initialized for service: {} v{}",
96            config.service_name, config.service_version
97        );
98
99        Ok(Self {
100            config,
101            _guard: Some(guard),
102        })
103    }
104
105    /// Get the configuration
106    pub fn config(&self) -> &TracingConfig {
107        &self.config
108    }
109
110    /// Check if tracing is enabled
111    pub fn is_enabled(&self) -> bool {
112        self.config.enabled
113    }
114
115    /// Create a span for a specific operation
116    pub fn span(&self, name: &str) -> tracing::Span {
117        tracing::info_span!("operation", name = name)
118    }
119
120    /// Create a span with attributes
121    pub fn span_with_attrs(&self, name: &str, attrs: &[(&str, &str)]) -> tracing::Span {
122        let span = tracing::info_span!("operation", name = name);
123
124        for (_key, _value) in attrs.iter().take(self.config.max_attributes) {
125            // TODO: tracing::Span::record only supports static keys, so dynamic keys are not supported here.
126            // span.record(key, value);
127        }
128
129        span
130    }
131
132    /// Record an event with the current span context
133    pub fn event(&self, level: Level, message: &str) {
134        match level {
135            Level::ERROR => error!(message),
136            Level::WARN => warn!(message),
137            Level::INFO => info!(message),
138            Level::DEBUG => debug!(message),
139            Level::TRACE => tracing::trace!(message),
140        }
141    }
142
143    /// Record an event with attributes
144    pub fn event_with_attrs(&self, level: Level, message: &str, attrs: &[(&str, &str)]) {
145        match level {
146            Level::ERROR => error!(?attrs, message),
147            Level::WARN => warn!(?attrs, message),
148            Level::INFO => info!(?attrs, message),
149            Level::DEBUG => debug!(?attrs, message),
150            Level::TRACE => tracing::trace!(?attrs, message),
151        }
152    }
153}
154
155impl Drop for TracingSystem {
156    fn drop(&mut self) {
157        if self.config.enabled {
158            info!("Shutting down tracing system");
159            // The guard will automatically flush any pending traces
160        }
161    }
162}
163
164/// Tracing utilities for common operations
165pub struct TracingUtils;
166
167impl TracingUtils {
168    /// Create a span for MCP request processing
169    pub fn mcp_request_span(method: &str, request_id: &str) -> tracing::Span {
170        tracing::info_span!(
171            "mcp_request",
172            method = method,
173            request_id = request_id,
174            service = "ultrafast-mcp"
175        )
176    }
177
178    /// Create a span for tool execution
179    pub fn tool_execution_span(tool_name: &str, request_id: &str) -> tracing::Span {
180        tracing::info_span!(
181            "tool_execution",
182            tool_name = tool_name,
183            request_id = request_id,
184            service = "ultrafast-mcp"
185        )
186    }
187
188    /// Create a span for resource operations
189    pub fn resource_operation_span(operation: &str, uri: &str) -> tracing::Span {
190        tracing::info_span!(
191            "resource_operation",
192            operation = operation,
193            uri = uri,
194            service = "ultrafast-mcp"
195        )
196    }
197
198    /// Create a span for transport operations
199    pub fn transport_operation_span(operation: &str, transport_type: &str) -> tracing::Span {
200        tracing::info_span!(
201            "transport_operation",
202            operation = operation,
203            transport_type = transport_type,
204            service = "ultrafast-mcp"
205        )
206    }
207
208    /// Record a request start event
209    pub fn record_request_start(method: &str, request_id: &str) {
210        info!(
211            "Request started method={} request_id={} service=ultrafast-mcp",
212            method, request_id
213        );
214    }
215
216    /// Record a request completion event
217    pub fn record_request_complete(
218        method: &str,
219        request_id: &str,
220        duration_ms: u64,
221        success: bool,
222    ) {
223        if success {
224            info!(
225                "Request completed method={} request_id={} duration_ms={} success={} service=ultrafast-mcp",
226                method, request_id, duration_ms, success
227            );
228        } else {
229            error!(
230                "Request failed method={} request_id={} duration_ms={} success={} service=ultrafast-mcp",
231                method, request_id, duration_ms, success
232            );
233        }
234    }
235
236    /// Record a tool execution event
237    pub fn record_tool_execution(
238        tool_name: &str,
239        request_id: &str,
240        duration_ms: u64,
241        success: bool,
242    ) {
243        if success {
244            info!(
245                "Tool execution completed tool_name={} request_id={} duration_ms={} success={} service=ultrafast-mcp",
246                tool_name, request_id, duration_ms, success
247            );
248        } else {
249            error!(
250                "Tool execution failed tool_name={} request_id={} duration_ms={} success={} service=ultrafast-mcp",
251                tool_name, request_id, duration_ms, success
252            );
253        }
254    }
255
256    /// Record a transport event
257    pub fn record_transport_event(
258        event_type: &str,
259        transport_type: &str,
260        bytes: Option<u64>,
261        error: Option<&str>,
262    ) {
263        let error_str = error.unwrap_or("none");
264        let bytes_str = bytes
265            .map(|b| b.to_string())
266            .unwrap_or_else(|| "none".to_string());
267
268        if error.is_some() {
269            error!(
270                "Transport event event_type={} transport_type={} bytes={} error={} service=ultrafast-mcp",
271                event_type, transport_type, bytes_str, error_str
272            );
273        } else {
274            debug!(
275                "Transport event event_type={} transport_type={} bytes={} error={} service=ultrafast-mcp",
276                event_type, transport_type, bytes_str, error_str
277            );
278        }
279    }
280}
281
282/// Macro for creating spans with automatic enter/exit
283#[macro_export]
284macro_rules! trace_span {
285    ($name:expr) => {
286        let _span = tracing::info_span!($name);
287        let _enter = _span.enter();
288    };
289    ($name:expr, $($key:ident = $val:expr),*) => {
290        let _span = tracing::info_span!($name, $($key = $val),*);
291        let _enter = _span.enter();
292    };
293}
294
295/// Macro for tracing function entry/exit
296#[macro_export]
297macro_rules! trace_function {
298    ($func_name:expr) => {
299        let _span = tracing::info_span!("function", name = $func_name);
300        let _enter = _span.enter();
301    };
302}
303
304#[cfg(test)]
305mod tests {
306    use super::*;
307
308    #[test]
309    fn test_tracing_config_default() {
310        let config = TracingConfig::default();
311        assert!(config.enabled);
312        assert_eq!(config.service_name, "ultrafast-mcp");
313        assert_eq!(config.service_version, "1.0.0");
314        assert_eq!(config.log_level, Level::INFO);
315        assert!(config.enable_console);
316        assert!(!config.enable_json);
317        assert!(!config.enable_otlp);
318        assert!(!config.enable_jaeger);
319    }
320
321    #[test]
322    fn test_tracing_config_custom() {
323        let config = TracingConfig {
324            service_name: "test-service".to_string(),
325            service_version: "2.0.0".to_string(),
326            log_level: Level::DEBUG,
327            enable_json: true,
328            ..Default::default()
329        };
330        assert_eq!(config.service_name, "test-service");
331        assert_eq!(config.service_version, "2.0.0");
332        assert_eq!(config.log_level, Level::DEBUG);
333        assert!(config.enable_json);
334    }
335
336    #[test]
337    fn test_tracing_system_creation() {
338        let config = TracingConfig::default();
339        let system = TracingSystem::new(config);
340
341        assert!(system.is_enabled());
342        assert_eq!(system.config().service_name, "ultrafast-mcp");
343    }
344
345    #[test]
346    fn test_tracing_utils_spans() {
347        let span = TracingUtils::mcp_request_span("test_method", "test_id");
348        assert_eq!(
349            span.metadata().map(|m| m.name()).unwrap_or("unknown"),
350            "mcp_request"
351        );
352
353        let span = TracingUtils::tool_execution_span("test_tool", "test_id");
354        assert_eq!(
355            span.metadata().map(|m| m.name()).unwrap_or("unknown"),
356            "tool_execution"
357        );
358
359        let span = TracingUtils::resource_operation_span("read", "test://uri");
360        assert_eq!(
361            span.metadata().map(|m| m.name()).unwrap_or("unknown"),
362            "resource_operation"
363        );
364
365        let span = TracingUtils::transport_operation_span("send", "http");
366        assert_eq!(
367            span.metadata().map(|m| m.name()).unwrap_or("unknown"),
368            "transport_operation"
369        );
370    }
371
372    #[test]
373    fn test_tracing_utils_events() {
374        // These should not panic
375        TracingUtils::record_request_start("test_method", "test_id");
376        TracingUtils::record_request_complete("test_method", "test_id", 100, true);
377        TracingUtils::record_tool_execution("test_tool", "test_id", 50, true);
378        TracingUtils::record_transport_event("send", "http", Some(1024), None);
379    }
380}