Skip to main content

a2a_rs/observability/
mod.rs

1//! Observability and logging utilities for a2a-rs
2//!
3//! This module provides utilities for structured logging, tracing, and metrics collection
4//! to help with debugging, monitoring, and understanding system behavior.
5
6#[cfg(feature = "tracing")]
7use tracing_subscriber::{EnvFilter, fmt, prelude::*};
8
9/// Initialize tracing with sensible defaults for the A2A application
10///
11/// This sets up:
12/// - Console output with timestamps
13/// - Environment-based filtering (RUST_LOG environment variable)
14/// - Structured JSON output for production environments
15///
16/// # Examples
17///
18/// ```rust,no_run
19/// use a2a_rs::observability;
20///
21/// // Initialize with default settings
22/// observability::init_tracing();
23///
24/// // Or with custom environment filter
25/// observability::init_tracing_with_filter("a2a_rs=debug,tower_http=debug");
26/// ```
27#[cfg(feature = "tracing")]
28pub fn init_tracing() {
29    init_tracing_with_filter("a2a_rs=info");
30}
31
32/// Initialize tracing with a custom filter string
33///
34/// # Arguments
35///
36/// * `filter` - Environment filter string (e.g., "a2a_rs=debug,tower_http=debug")
37#[cfg(feature = "tracing")]
38pub fn init_tracing_with_filter(filter: &str) {
39    let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(filter));
40
41    let fmt_layer = fmt::layer()
42        .with_target(true)
43        .with_level(true)
44        .with_thread_ids(true)
45        .with_thread_names(true);
46
47    tracing_subscriber::registry()
48        .with(env_filter)
49        .with(fmt_layer)
50        .init();
51}
52
53/// Initialize tracing for production environments with JSON output
54///
55/// This provides structured JSON logs suitable for log aggregation systems
56#[cfg(feature = "tracing")]
57pub fn init_tracing_json() {
58    let env_filter =
59        EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("a2a_rs=info,warn"));
60
61    let fmt_layer = fmt::layer()
62        .with_target(true)
63        .with_level(true)
64        .with_thread_ids(true)
65        .with_thread_names(true);
66
67    tracing_subscriber::registry()
68        .with(env_filter)
69        .with(fmt_layer)
70        .init();
71}
72
73/// Utility struct for creating spans with consistent naming
74pub struct SpanBuilder;
75
76impl SpanBuilder {
77    /// Create a span for message processing operations
78    #[cfg(feature = "tracing")]
79    pub fn message_processing(message_id: &str) -> tracing::Span {
80        tracing::info_span!(
81            "message_processing",
82            message.id = %message_id,
83            processing.duration_ms = tracing::field::Empty,
84            processing.status = tracing::field::Empty,
85        )
86    }
87
88    /// Create a span for task operations
89    #[cfg(feature = "tracing")]
90    pub fn task_operation(task_id: &str, operation: &str) -> tracing::Span {
91        tracing::info_span!(
92            "task_operation",
93            task.id = %task_id,
94            task.operation = %operation,
95            operation.duration_ms = tracing::field::Empty,
96            operation.status = tracing::field::Empty,
97        )
98    }
99
100    /// Create a span for HTTP requests
101    #[cfg(feature = "tracing")]
102    pub fn http_request(method: &str, path: &str) -> tracing::Span {
103        tracing::info_span!(
104            "http_request",
105            http.method = %method,
106            http.path = %path,
107            http.status_code = tracing::field::Empty,
108            http.duration_ms = tracing::field::Empty,
109        )
110    }
111
112    /// Create a span for WebSocket connections
113    #[cfg(feature = "tracing")]
114    pub fn websocket_connection(connection_id: &str) -> tracing::Span {
115        tracing::info_span!(
116            "websocket_connection",
117            ws.connection_id = %connection_id,
118            ws.messages_sent = 0u64,
119            ws.messages_received = 0u64,
120            ws.duration_ms = tracing::field::Empty,
121        )
122    }
123
124    /// Create a span for authentication operations
125    #[cfg(feature = "tracing")]
126    pub fn authentication(scheme: &str) -> tracing::Span {
127        tracing::info_span!(
128            "authentication",
129            auth.scheme = %scheme,
130            auth.status = tracing::field::Empty,
131            auth.duration_ms = tracing::field::Empty,
132        )
133    }
134}
135
136/// Helper macros for consistent error logging
137#[macro_export]
138macro_rules! log_error {
139    ($err:expr) => {
140        tracing::error!(
141            error = %$err,
142            error_type = std::any::type_name_of_val(&$err),
143            "Operation failed"
144        )
145    };
146    ($err:expr, $msg:expr) => {
147        tracing::error!(
148            error = %$err,
149            error_type = std::any::type_name_of_val(&$err),
150            message = $msg,
151            "Operation failed"
152        )
153    };
154}
155
156/// Helper macro for performance tracking
157#[macro_export]
158macro_rules! measure_duration {
159    ($span:expr, $field:expr, $block:expr) => {{
160        let start = std::time::Instant::now();
161        let result = $block;
162        let duration = start.elapsed();
163        $span.record($field, duration.as_millis() as u64);
164        result
165    }};
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171
172    #[test]
173    #[cfg(feature = "tracing")]
174    fn test_span_builders() {
175        // Just ensure they compile and can be created
176        let _ = SpanBuilder::message_processing("test-message-id");
177        let _ = SpanBuilder::task_operation("test-task-id", "create");
178        let _ = SpanBuilder::http_request("GET", "/api/v1/agent-card");
179        let _ = SpanBuilder::websocket_connection("ws-conn-123");
180        let _ = SpanBuilder::authentication("bearer");
181    }
182}