1use std::fmt;
8use std::net::SocketAddr;
9use std::sync::atomic::{AtomicU64, Ordering};
10use std::time::{Duration, Instant};
11
12use crate::nat_traversal_api::PeerId;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
16pub enum EventSeverity {
17 Trace,
19 Debug,
21 Info,
23 Warn,
25 Error,
27}
28
29impl fmt::Display for EventSeverity {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 match self {
32 Self::Trace => write!(f, "TRACE"),
33 Self::Debug => write!(f, "DEBUG"),
34 Self::Info => write!(f, "INFO"),
35 Self::Warn => write!(f, "WARN"),
36 Self::Error => write!(f, "ERROR"),
37 }
38 }
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
43pub enum EventComponent {
44 NatTraversal,
46 Connection,
48 Discovery,
50 Transport,
52 PathSelection,
54 Shutdown,
56 Relay,
58 Crypto,
60 Endpoint,
62}
63
64impl fmt::Display for EventComponent {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 match self {
67 Self::NatTraversal => write!(f, "nat_traversal"),
68 Self::Connection => write!(f, "connection"),
69 Self::Discovery => write!(f, "discovery"),
70 Self::Transport => write!(f, "transport"),
71 Self::PathSelection => write!(f, "path_selection"),
72 Self::Shutdown => write!(f, "shutdown"),
73 Self::Relay => write!(f, "relay"),
74 Self::Crypto => write!(f, "crypto"),
75 Self::Endpoint => write!(f, "endpoint"),
76 }
77 }
78}
79
80#[derive(Debug, Clone)]
82pub struct StructuredEvent {
83 pub severity: EventSeverity,
85 pub component: EventComponent,
87 pub kind: EventKind,
89 pub message: String,
91 pub timestamp: Instant,
93 pub peer_id: Option<PeerId>,
95 pub addr: Option<SocketAddr>,
97 pub duration: Option<Duration>,
99 pub count: Option<u64>,
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
105pub enum EventKind {
106 ConnectionEstablished,
109 ConnectionClosed,
111 ConnectionFailed,
113 ConnectionMigrated,
115
116 CandidateDiscovered,
119 CandidateValidated,
121 CandidateFailed,
123 HolePunchStarted,
125 HolePunchSucceeded,
127 HolePunchFailed,
129
130 PathSelected,
133 PathChanged,
135 PathClosed,
137 PathRttUpdated,
139
140 PacketSent,
143 PacketReceived,
145 TransportError,
147
148 DiscoveryStarted,
151 AddressDiscovered,
153 DiscoveryCompleted,
155
156 EndpointStarted,
159 ShutdownInitiated,
161 ShutdownCompleted,
163
164 ActorTick,
167 CleanupPerformed,
169}
170
171impl fmt::Display for EventKind {
172 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173 match self {
174 Self::ConnectionEstablished => write!(f, "connection_established"),
175 Self::ConnectionClosed => write!(f, "connection_closed"),
176 Self::ConnectionFailed => write!(f, "connection_failed"),
177 Self::ConnectionMigrated => write!(f, "connection_migrated"),
178 Self::CandidateDiscovered => write!(f, "candidate_discovered"),
179 Self::CandidateValidated => write!(f, "candidate_validated"),
180 Self::CandidateFailed => write!(f, "candidate_failed"),
181 Self::HolePunchStarted => write!(f, "hole_punch_started"),
182 Self::HolePunchSucceeded => write!(f, "hole_punch_succeeded"),
183 Self::HolePunchFailed => write!(f, "hole_punch_failed"),
184 Self::PathSelected => write!(f, "path_selected"),
185 Self::PathChanged => write!(f, "path_changed"),
186 Self::PathClosed => write!(f, "path_closed"),
187 Self::PathRttUpdated => write!(f, "path_rtt_updated"),
188 Self::PacketSent => write!(f, "packet_sent"),
189 Self::PacketReceived => write!(f, "packet_received"),
190 Self::TransportError => write!(f, "transport_error"),
191 Self::DiscoveryStarted => write!(f, "discovery_started"),
192 Self::AddressDiscovered => write!(f, "address_discovered"),
193 Self::DiscoveryCompleted => write!(f, "discovery_completed"),
194 Self::EndpointStarted => write!(f, "endpoint_started"),
195 Self::ShutdownInitiated => write!(f, "shutdown_initiated"),
196 Self::ShutdownCompleted => write!(f, "shutdown_completed"),
197 Self::ActorTick => write!(f, "actor_tick"),
198 Self::CleanupPerformed => write!(f, "cleanup_performed"),
199 }
200 }
201}
202
203impl StructuredEvent {
204 pub fn builder(component: EventComponent, kind: EventKind) -> StructuredEventBuilder {
206 StructuredEventBuilder::new(component, kind)
207 }
208
209 pub fn log(&self) {
211 match self.severity {
212 EventSeverity::Trace => {
213 tracing::trace!(
214 component = %self.component,
215 kind = %self.kind,
216 peer_id = ?self.peer_id,
217 addr = ?self.addr,
218 duration_ms = ?self.duration.map(|d| d.as_millis()),
219 count = ?self.count,
220 "{}",
221 self.message
222 );
223 }
224 EventSeverity::Debug => {
225 tracing::debug!(
226 component = %self.component,
227 kind = %self.kind,
228 peer_id = ?self.peer_id,
229 addr = ?self.addr,
230 duration_ms = ?self.duration.map(|d| d.as_millis()),
231 count = ?self.count,
232 "{}",
233 self.message
234 );
235 }
236 EventSeverity::Info => {
237 tracing::info!(
238 component = %self.component,
239 kind = %self.kind,
240 peer_id = ?self.peer_id,
241 addr = ?self.addr,
242 duration_ms = ?self.duration.map(|d| d.as_millis()),
243 count = ?self.count,
244 "{}",
245 self.message
246 );
247 }
248 EventSeverity::Warn => {
249 tracing::warn!(
250 component = %self.component,
251 kind = %self.kind,
252 peer_id = ?self.peer_id,
253 addr = ?self.addr,
254 duration_ms = ?self.duration.map(|d| d.as_millis()),
255 count = ?self.count,
256 "{}",
257 self.message
258 );
259 }
260 EventSeverity::Error => {
261 tracing::error!(
262 component = %self.component,
263 kind = %self.kind,
264 peer_id = ?self.peer_id,
265 addr = ?self.addr,
266 duration_ms = ?self.duration.map(|d| d.as_millis()),
267 count = ?self.count,
268 "{}",
269 self.message
270 );
271 }
272 }
273 }
274}
275
276#[derive(Debug)]
278pub struct StructuredEventBuilder {
279 component: EventComponent,
280 kind: EventKind,
281 severity: EventSeverity,
282 message: Option<String>,
283 peer_id: Option<PeerId>,
284 addr: Option<SocketAddr>,
285 duration: Option<Duration>,
286 count: Option<u64>,
287}
288
289impl StructuredEventBuilder {
290 pub fn new(component: EventComponent, kind: EventKind) -> Self {
292 Self {
293 component,
294 kind,
295 severity: EventSeverity::Info,
296 message: None,
297 peer_id: None,
298 addr: None,
299 duration: None,
300 count: None,
301 }
302 }
303
304 pub fn severity(mut self, severity: EventSeverity) -> Self {
306 self.severity = severity;
307 self
308 }
309
310 pub fn message(mut self, message: impl Into<String>) -> Self {
312 self.message = Some(message.into());
313 self
314 }
315
316 pub fn peer_id(mut self, peer_id: PeerId) -> Self {
318 self.peer_id = Some(peer_id);
319 self
320 }
321
322 pub fn addr(mut self, addr: SocketAddr) -> Self {
324 self.addr = Some(addr);
325 self
326 }
327
328 pub fn duration(mut self, duration: Duration) -> Self {
330 self.duration = Some(duration);
331 self
332 }
333
334 pub fn count(mut self, count: u64) -> Self {
336 self.count = Some(count);
337 self
338 }
339
340 pub fn build(self) -> StructuredEvent {
342 StructuredEvent {
343 severity: self.severity,
344 component: self.component,
345 kind: self.kind,
346 message: self.message.unwrap_or_else(|| format!("{}", self.kind)),
347 timestamp: Instant::now(),
348 peer_id: self.peer_id,
349 addr: self.addr,
350 duration: self.duration,
351 count: self.count,
352 }
353 }
354
355 pub fn log(self) {
357 self.build().log();
358 }
359}
360
361#[derive(Debug)]
363pub struct ActorTickMetrics {
364 name: &'static str,
366 tick_count: AtomicU64,
368 total_time_ns: AtomicU64,
370 max_tick_ns: AtomicU64,
372}
373
374impl ActorTickMetrics {
375 pub fn new(name: &'static str) -> Self {
377 Self {
378 name,
379 tick_count: AtomicU64::new(0),
380 total_time_ns: AtomicU64::new(0),
381 max_tick_ns: AtomicU64::new(0),
382 }
383 }
384
385 pub fn record_tick(&self, duration: Duration) {
387 let ns = duration.as_nanos() as u64;
388
389 self.tick_count.fetch_add(1, Ordering::Relaxed);
390 self.total_time_ns.fetch_add(ns, Ordering::Relaxed);
391
392 let mut current_max = self.max_tick_ns.load(Ordering::Relaxed);
394 while ns > current_max {
395 match self.max_tick_ns.compare_exchange_weak(
396 current_max,
397 ns,
398 Ordering::Relaxed,
399 Ordering::Relaxed,
400 ) {
401 Ok(_) => break,
402 Err(new_max) => current_max = new_max,
403 }
404 }
405 }
406
407 pub fn start_tick(&self) -> TickGuard<'_> {
409 TickGuard {
410 metrics: self,
411 start: Instant::now(),
412 }
413 }
414
415 pub fn name(&self) -> &'static str {
417 self.name
418 }
419
420 pub fn tick_count(&self) -> u64 {
422 self.tick_count.load(Ordering::Relaxed)
423 }
424
425 pub fn average_tick_duration(&self) -> Duration {
427 let count = self.tick_count.load(Ordering::Relaxed);
428 if count == 0 {
429 return Duration::ZERO;
430 }
431 let total_ns = self.total_time_ns.load(Ordering::Relaxed);
432 Duration::from_nanos(total_ns / count)
433 }
434
435 pub fn max_tick_duration(&self) -> Duration {
437 Duration::from_nanos(self.max_tick_ns.load(Ordering::Relaxed))
438 }
439
440 pub fn snapshot(&self) -> ActorTickSnapshot {
442 ActorTickSnapshot {
443 name: self.name,
444 tick_count: self.tick_count(),
445 average_duration: self.average_tick_duration(),
446 max_duration: self.max_tick_duration(),
447 }
448 }
449
450 pub fn reset(&self) {
452 self.tick_count.store(0, Ordering::Relaxed);
453 self.total_time_ns.store(0, Ordering::Relaxed);
454 self.max_tick_ns.store(0, Ordering::Relaxed);
455 }
456}
457
458pub struct TickGuard<'a> {
460 metrics: &'a ActorTickMetrics,
461 start: Instant,
462}
463
464impl<'a> Drop for TickGuard<'a> {
465 fn drop(&mut self) {
466 self.metrics.record_tick(self.start.elapsed());
467 }
468}
469
470#[derive(Debug, Clone)]
472pub struct ActorTickSnapshot {
473 pub name: &'static str,
475 pub tick_count: u64,
477 pub average_duration: Duration,
479 pub max_duration: Duration,
481}
482
483impl fmt::Display for ActorTickSnapshot {
484 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
485 write!(
486 f,
487 "{}: {} ticks, avg={:?}, max={:?}",
488 self.name, self.tick_count, self.average_duration, self.max_duration
489 )
490 }
491}
492
493#[macro_export]
495macro_rules! log_event {
496 ($component:expr, $kind:expr, $msg:expr) => {
497 $crate::structured_events::StructuredEvent::builder($component, $kind)
498 .message($msg)
499 .log()
500 };
501 ($component:expr, $kind:expr, $msg:expr, severity = $sev:expr) => {
502 $crate::structured_events::StructuredEvent::builder($component, $kind)
503 .message($msg)
504 .severity($sev)
505 .log()
506 };
507 ($component:expr, $kind:expr, $msg:expr, addr = $addr:expr) => {
508 $crate::structured_events::StructuredEvent::builder($component, $kind)
509 .message($msg)
510 .addr($addr)
511 .log()
512 };
513 ($component:expr, $kind:expr, $msg:expr, peer = $peer:expr) => {
514 $crate::structured_events::StructuredEvent::builder($component, $kind)
515 .message($msg)
516 .peer_id($peer)
517 .log()
518 };
519}
520
521#[cfg(test)]
522mod tests {
523 use super::*;
524
525 #[test]
526 fn test_event_severity_ordering() {
527 assert!(EventSeverity::Trace < EventSeverity::Debug);
528 assert!(EventSeverity::Debug < EventSeverity::Info);
529 assert!(EventSeverity::Info < EventSeverity::Warn);
530 assert!(EventSeverity::Warn < EventSeverity::Error);
531 }
532
533 #[test]
534 fn test_event_builder() {
535 let event =
536 StructuredEvent::builder(EventComponent::Connection, EventKind::ConnectionEstablished)
537 .severity(EventSeverity::Info)
538 .message("Connection established")
539 .addr("192.168.1.1:5000".parse().unwrap())
540 .build();
541
542 assert_eq!(event.component, EventComponent::Connection);
543 assert_eq!(event.kind, EventKind::ConnectionEstablished);
544 assert_eq!(event.severity, EventSeverity::Info);
545 assert_eq!(event.message, "Connection established");
546 assert_eq!(event.addr, Some("192.168.1.1:5000".parse().unwrap()));
547 }
548
549 #[test]
550 fn test_event_builder_defaults() {
551 let event =
552 StructuredEvent::builder(EventComponent::Discovery, EventKind::DiscoveryStarted)
553 .build();
554
555 assert_eq!(event.severity, EventSeverity::Info);
556 assert_eq!(event.message, "discovery_started");
557 assert!(event.peer_id.is_none());
558 assert!(event.addr.is_none());
559 }
560
561 #[test]
562 fn test_actor_tick_metrics() {
563 let metrics = ActorTickMetrics::new("test_actor");
564
565 metrics.record_tick(Duration::from_millis(10));
566 metrics.record_tick(Duration::from_millis(20));
567 metrics.record_tick(Duration::from_millis(5));
568
569 assert_eq!(metrics.tick_count(), 3);
570 assert_eq!(metrics.max_tick_duration(), Duration::from_millis(20));
571
572 let avg = metrics.average_tick_duration();
573 assert!(avg.as_millis() >= 10 && avg.as_millis() <= 13);
575 }
576
577 #[test]
578 fn test_actor_tick_guard() {
579 let metrics = ActorTickMetrics::new("test_actor");
580
581 {
582 let _guard = metrics.start_tick();
583 std::thread::sleep(Duration::from_millis(5));
584 }
585
586 assert_eq!(metrics.tick_count(), 1);
587 assert!(metrics.max_tick_duration() >= Duration::from_millis(4));
588 }
589
590 #[test]
591 fn test_actor_tick_reset() {
592 let metrics = ActorTickMetrics::new("test_actor");
593
594 metrics.record_tick(Duration::from_millis(10));
595 assert_eq!(metrics.tick_count(), 1);
596
597 metrics.reset();
598 assert_eq!(metrics.tick_count(), 0);
599 assert_eq!(metrics.max_tick_duration(), Duration::ZERO);
600 }
601
602 #[test]
603 fn test_actor_tick_snapshot() {
604 let metrics = ActorTickMetrics::new("test_actor");
605 metrics.record_tick(Duration::from_millis(10));
606
607 let snapshot = metrics.snapshot();
608 assert_eq!(snapshot.name, "test_actor");
609 assert_eq!(snapshot.tick_count, 1);
610 }
611
612 #[test]
613 fn test_event_component_display() {
614 assert_eq!(format!("{}", EventComponent::NatTraversal), "nat_traversal");
615 assert_eq!(format!("{}", EventComponent::Connection), "connection");
616 assert_eq!(
617 format!("{}", EventComponent::PathSelection),
618 "path_selection"
619 );
620 }
621
622 #[test]
623 fn test_event_kind_display() {
624 assert_eq!(
625 format!("{}", EventKind::ConnectionEstablished),
626 "connection_established"
627 );
628 assert_eq!(
629 format!("{}", EventKind::HolePunchStarted),
630 "hole_punch_started"
631 );
632 assert_eq!(format!("{}", EventKind::PathSelected), "path_selected");
633 }
634
635 #[test]
636 fn test_actor_tick_concurrent() {
637 use std::sync::Arc;
638 use std::thread;
639
640 let metrics = Arc::new(ActorTickMetrics::new("concurrent_actor"));
641 let mut handles = vec![];
642
643 for _ in 0..10 {
644 let m = Arc::clone(&metrics);
645 handles.push(thread::spawn(move || {
646 for _ in 0..100 {
647 m.record_tick(Duration::from_micros(1));
648 }
649 }));
650 }
651
652 for handle in handles {
653 handle.join().unwrap();
654 }
655
656 assert_eq!(metrics.tick_count(), 1000);
657 }
658
659 #[test]
660 fn test_event_with_duration() {
661 let event =
662 StructuredEvent::builder(EventComponent::PathSelection, EventKind::PathRttUpdated)
663 .duration(Duration::from_millis(42))
664 .build();
665
666 assert_eq!(event.duration, Some(Duration::from_millis(42)));
667 }
668
669 #[test]
670 fn test_event_with_count() {
671 let event =
672 StructuredEvent::builder(EventComponent::NatTraversal, EventKind::CleanupPerformed)
673 .count(5)
674 .message("Cleaned up 5 expired candidates")
675 .build();
676
677 assert_eq!(event.count, Some(5));
678 }
679}