ipfrs_network/
logging.rs

1//! Structured logging and tracing support
2//!
3//! Provides enhanced logging capabilities with:
4//! - Structured event logging
5//! - Tracing spans for operations
6//! - Context propagation
7//! - Log filtering and formatting
8
9use tracing::{span, Level, Span};
10
11/// Network event types for structured logging
12#[derive(Debug, Clone, Copy)]
13pub enum NetworkEventType {
14    /// Connection establishment
15    ConnectionEstablished,
16    /// Connection closed
17    ConnectionClosed,
18    /// Connection failed
19    ConnectionFailed,
20    /// DHT query started
21    DhtQuery,
22    /// DHT query completed
23    DhtQueryComplete,
24    /// Peer discovered
25    PeerDiscovered,
26    /// Content announced
27    ContentAnnounced,
28    /// Content found
29    ContentFound,
30    /// Protocol negotiation
31    ProtocolNegotiation,
32}
33
34impl NetworkEventType {
35    /// Get the event type name as a string
36    pub fn as_str(&self) -> &'static str {
37        match self {
38            Self::ConnectionEstablished => "connection_established",
39            Self::ConnectionClosed => "connection_closed",
40            Self::ConnectionFailed => "connection_failed",
41            Self::DhtQuery => "dht_query",
42            Self::DhtQueryComplete => "dht_query_complete",
43            Self::PeerDiscovered => "peer_discovered",
44            Self::ContentAnnounced => "content_announced",
45            Self::ContentFound => "content_found",
46            Self::ProtocolNegotiation => "protocol_negotiation",
47        }
48    }
49}
50
51/// Create a span for a network operation
52pub fn network_span(operation: &str) -> Span {
53    span!(Level::INFO, "network_operation", operation = operation)
54}
55
56/// Create a span for a DHT operation
57pub fn dht_span(operation: &str, key: Option<&str>) -> Span {
58    if let Some(k) = key {
59        span!(Level::INFO, "dht_operation", operation = operation, key = k)
60    } else {
61        span!(Level::INFO, "dht_operation", operation = operation)
62    }
63}
64
65/// Create a span for a connection operation
66pub fn connection_span(peer_id: &str, direction: &str) -> Span {
67    span!(
68        Level::INFO,
69        "connection",
70        peer_id = peer_id,
71        direction = direction
72    )
73}
74
75/// Log a structured network event
76#[macro_export]
77macro_rules! log_network_event {
78    ($event_type:expr, $($key:ident = $value:expr),* $(,)?) => {
79        tracing::info!(
80            event_type = $event_type.as_str(),
81            $($key = tracing::field::debug(&$value)),*
82        );
83    };
84}
85
86/// Log a network error with context
87#[macro_export]
88macro_rules! log_network_error {
89    ($message:expr, $($key:ident = $value:expr),* $(,)?) => {
90        tracing::error!(
91            message = $message,
92            $($key = tracing::field::debug(&$value)),*
93        );
94    };
95}
96
97/// Log a network warning with context
98#[macro_export]
99macro_rules! log_network_warn {
100    ($message:expr, $($key:ident = $value:expr),* $(,)?) => {
101        tracing::warn!(
102            message = $message,
103            $($key = tracing::field::debug(&$value)),*
104        );
105    };
106}
107
108/// Logging configuration
109#[derive(Debug, Clone)]
110pub struct LoggingConfig {
111    /// Log level
112    pub level: LogLevel,
113    /// Enable JSON formatting
114    pub json_format: bool,
115    /// Enable timestamp
116    pub with_timestamp: bool,
117    /// Enable target (module path)
118    pub with_target: bool,
119    /// Enable thread ID
120    pub with_thread_id: bool,
121    /// Enable span information
122    pub with_spans: bool,
123}
124
125impl Default for LoggingConfig {
126    fn default() -> Self {
127        Self {
128            level: LogLevel::Info,
129            json_format: false,
130            with_timestamp: true,
131            with_target: true,
132            with_thread_id: false,
133            with_spans: true,
134        }
135    }
136}
137
138/// Log level configuration
139#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140pub enum LogLevel {
141    /// Trace level (most verbose)
142    Trace,
143    /// Debug level
144    Debug,
145    /// Info level
146    Info,
147    /// Warn level
148    Warn,
149    /// Error level
150    Error,
151}
152
153impl LogLevel {
154    /// Convert to tracing Level
155    pub fn to_tracing_level(&self) -> Level {
156        match self {
157            Self::Trace => Level::TRACE,
158            Self::Debug => Level::DEBUG,
159            Self::Info => Level::INFO,
160            Self::Warn => Level::WARN,
161            Self::Error => Level::ERROR,
162        }
163    }
164
165    /// Parse from string
166    pub fn parse(s: &str) -> Option<Self> {
167        match s.to_lowercase().as_str() {
168            "trace" => Some(Self::Trace),
169            "debug" => Some(Self::Debug),
170            "info" => Some(Self::Info),
171            "warn" | "warning" => Some(Self::Warn),
172            "error" => Some(Self::Error),
173            _ => None,
174        }
175    }
176}
177
178impl std::fmt::Display for LogLevel {
179    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
180        match self {
181            Self::Trace => write!(f, "TRACE"),
182            Self::Debug => write!(f, "DEBUG"),
183            Self::Info => write!(f, "INFO"),
184            Self::Warn => write!(f, "WARN"),
185            Self::Error => write!(f, "ERROR"),
186        }
187    }
188}
189
190/// Network operation context for tracing
191pub struct OperationContext {
192    span: Span,
193}
194
195impl OperationContext {
196    /// Create a new operation context
197    pub fn new(name: &str) -> Self {
198        Self {
199            span: span!(Level::INFO, "operation", name = name),
200        }
201    }
202
203    /// Enter the context
204    pub fn enter(&self) -> tracing::span::Entered<'_> {
205        self.span.enter()
206    }
207
208    /// Add a field to the context
209    pub fn record<T: std::fmt::Debug>(&self, field: &str, value: T) {
210        self.span.record(field, tracing::field::debug(&value));
211    }
212}
213
214#[cfg(test)]
215mod tests {
216    use super::*;
217
218    #[test]
219    fn test_log_level_conversion() {
220        assert_eq!(LogLevel::Info.to_tracing_level(), Level::INFO);
221        assert_eq!(LogLevel::Debug.to_tracing_level(), Level::DEBUG);
222        assert_eq!(LogLevel::Error.to_tracing_level(), Level::ERROR);
223    }
224
225    #[test]
226    fn test_log_level_from_string() {
227        assert_eq!(LogLevel::parse("info"), Some(LogLevel::Info));
228        assert_eq!(LogLevel::parse("INFO"), Some(LogLevel::Info));
229        assert_eq!(LogLevel::parse("debug"), Some(LogLevel::Debug));
230        assert_eq!(LogLevel::parse("warn"), Some(LogLevel::Warn));
231        assert_eq!(LogLevel::parse("warning"), Some(LogLevel::Warn));
232        assert_eq!(LogLevel::parse("error"), Some(LogLevel::Error));
233        assert_eq!(LogLevel::parse("trace"), Some(LogLevel::Trace));
234        assert_eq!(LogLevel::parse("invalid"), None);
235    }
236
237    #[test]
238    fn test_log_level_display() {
239        assert_eq!(format!("{}", LogLevel::Info), "INFO");
240        assert_eq!(format!("{}", LogLevel::Debug), "DEBUG");
241        assert_eq!(format!("{}", LogLevel::Error), "ERROR");
242    }
243
244    #[test]
245    fn test_event_type_str() {
246        assert_eq!(
247            NetworkEventType::ConnectionEstablished.as_str(),
248            "connection_established"
249        );
250        assert_eq!(NetworkEventType::DhtQuery.as_str(), "dht_query");
251    }
252
253    #[test]
254    fn test_network_span_creation() {
255        let _span = network_span("test_operation");
256        // Span creation succeeds (may be disabled without subscriber)
257    }
258
259    #[test]
260    fn test_dht_span_creation() {
261        let _span = dht_span("query", Some("test_key"));
262        let _span_no_key = dht_span("bootstrap", None);
263        // Span creation succeeds (may be disabled without subscriber)
264    }
265
266    #[test]
267    fn test_connection_span_creation() {
268        let _span = connection_span("12D3KooTest", "outbound");
269        // Span creation succeeds (may be disabled without subscriber)
270    }
271
272    #[test]
273    fn test_operation_context() {
274        let ctx = OperationContext::new("test_op");
275        let _guard = ctx.enter();
276        ctx.record("status", "success");
277    }
278
279    #[test]
280    fn test_logging_config_default() {
281        let config = LoggingConfig::default();
282        assert_eq!(config.level, LogLevel::Info);
283        assert!(config.with_timestamp);
284        assert!(config.with_target);
285        assert!(!config.json_format);
286    }
287}