1use tracing::{span, Level, Span};
10
11#[derive(Debug, Clone, Copy)]
13pub enum NetworkEventType {
14 ConnectionEstablished,
16 ConnectionClosed,
18 ConnectionFailed,
20 DhtQuery,
22 DhtQueryComplete,
24 PeerDiscovered,
26 ContentAnnounced,
28 ContentFound,
30 ProtocolNegotiation,
32}
33
34impl NetworkEventType {
35 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
51pub fn network_span(operation: &str) -> Span {
53 span!(Level::INFO, "network_operation", operation = operation)
54}
55
56pub 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
65pub 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#[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#[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#[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#[derive(Debug, Clone)]
110pub struct LoggingConfig {
111 pub level: LogLevel,
113 pub json_format: bool,
115 pub with_timestamp: bool,
117 pub with_target: bool,
119 pub with_thread_id: bool,
121 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140pub enum LogLevel {
141 Trace,
143 Debug,
145 Info,
147 Warn,
149 Error,
151}
152
153impl LogLevel {
154 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 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
190pub struct OperationContext {
192 span: Span,
193}
194
195impl OperationContext {
196 pub fn new(name: &str) -> Self {
198 Self {
199 span: span!(Level::INFO, "operation", name = name),
200 }
201 }
202
203 pub fn enter(&self) -> tracing::span::Entered<'_> {
205 self.span.enter()
206 }
207
208 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 }
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 }
265
266 #[test]
267 fn test_connection_span_creation() {
268 let _span = connection_span("12D3KooTest", "outbound");
269 }
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}