use std::fmt;
use std::net::SocketAddr;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, Instant};
use crate::nat_traversal_api::PeerId;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum EventSeverity {
Trace,
Debug,
Info,
Warn,
Error,
}
impl fmt::Display for EventSeverity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Trace => write!(f, "TRACE"),
Self::Debug => write!(f, "DEBUG"),
Self::Info => write!(f, "INFO"),
Self::Warn => write!(f, "WARN"),
Self::Error => write!(f, "ERROR"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum EventComponent {
NatTraversal,
Connection,
Discovery,
Transport,
PathSelection,
Shutdown,
Relay,
Crypto,
Endpoint,
}
impl fmt::Display for EventComponent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NatTraversal => write!(f, "nat_traversal"),
Self::Connection => write!(f, "connection"),
Self::Discovery => write!(f, "discovery"),
Self::Transport => write!(f, "transport"),
Self::PathSelection => write!(f, "path_selection"),
Self::Shutdown => write!(f, "shutdown"),
Self::Relay => write!(f, "relay"),
Self::Crypto => write!(f, "crypto"),
Self::Endpoint => write!(f, "endpoint"),
}
}
}
#[derive(Debug, Clone)]
pub struct StructuredEvent {
pub severity: EventSeverity,
pub component: EventComponent,
pub kind: EventKind,
pub message: String,
pub timestamp: Instant,
pub peer_id: Option<PeerId>,
pub addr: Option<SocketAddr>,
pub duration: Option<Duration>,
pub count: Option<u64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum EventKind {
ConnectionEstablished,
ConnectionClosed,
ConnectionFailed,
ConnectionMigrated,
CandidateDiscovered,
CandidateValidated,
CandidateFailed,
HolePunchStarted,
HolePunchSucceeded,
HolePunchFailed,
PathSelected,
PathChanged,
PathClosed,
PathRttUpdated,
PacketSent,
PacketReceived,
TransportError,
DiscoveryStarted,
AddressDiscovered,
DiscoveryCompleted,
EndpointStarted,
ShutdownInitiated,
ShutdownCompleted,
ActorTick,
CleanupPerformed,
}
impl fmt::Display for EventKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ConnectionEstablished => write!(f, "connection_established"),
Self::ConnectionClosed => write!(f, "connection_closed"),
Self::ConnectionFailed => write!(f, "connection_failed"),
Self::ConnectionMigrated => write!(f, "connection_migrated"),
Self::CandidateDiscovered => write!(f, "candidate_discovered"),
Self::CandidateValidated => write!(f, "candidate_validated"),
Self::CandidateFailed => write!(f, "candidate_failed"),
Self::HolePunchStarted => write!(f, "hole_punch_started"),
Self::HolePunchSucceeded => write!(f, "hole_punch_succeeded"),
Self::HolePunchFailed => write!(f, "hole_punch_failed"),
Self::PathSelected => write!(f, "path_selected"),
Self::PathChanged => write!(f, "path_changed"),
Self::PathClosed => write!(f, "path_closed"),
Self::PathRttUpdated => write!(f, "path_rtt_updated"),
Self::PacketSent => write!(f, "packet_sent"),
Self::PacketReceived => write!(f, "packet_received"),
Self::TransportError => write!(f, "transport_error"),
Self::DiscoveryStarted => write!(f, "discovery_started"),
Self::AddressDiscovered => write!(f, "address_discovered"),
Self::DiscoveryCompleted => write!(f, "discovery_completed"),
Self::EndpointStarted => write!(f, "endpoint_started"),
Self::ShutdownInitiated => write!(f, "shutdown_initiated"),
Self::ShutdownCompleted => write!(f, "shutdown_completed"),
Self::ActorTick => write!(f, "actor_tick"),
Self::CleanupPerformed => write!(f, "cleanup_performed"),
}
}
}
impl StructuredEvent {
pub fn builder(component: EventComponent, kind: EventKind) -> StructuredEventBuilder {
StructuredEventBuilder::new(component, kind)
}
pub fn log(&self) {
match self.severity {
EventSeverity::Trace => {
tracing::trace!(
component = %self.component,
kind = %self.kind,
peer_id = ?self.peer_id,
addr = ?self.addr,
duration_ms = ?self.duration.map(|d| d.as_millis()),
count = ?self.count,
"{}",
self.message
);
}
EventSeverity::Debug => {
tracing::debug!(
component = %self.component,
kind = %self.kind,
peer_id = ?self.peer_id,
addr = ?self.addr,
duration_ms = ?self.duration.map(|d| d.as_millis()),
count = ?self.count,
"{}",
self.message
);
}
EventSeverity::Info => {
tracing::info!(
component = %self.component,
kind = %self.kind,
peer_id = ?self.peer_id,
addr = ?self.addr,
duration_ms = ?self.duration.map(|d| d.as_millis()),
count = ?self.count,
"{}",
self.message
);
}
EventSeverity::Warn => {
tracing::warn!(
component = %self.component,
kind = %self.kind,
peer_id = ?self.peer_id,
addr = ?self.addr,
duration_ms = ?self.duration.map(|d| d.as_millis()),
count = ?self.count,
"{}",
self.message
);
}
EventSeverity::Error => {
tracing::error!(
component = %self.component,
kind = %self.kind,
peer_id = ?self.peer_id,
addr = ?self.addr,
duration_ms = ?self.duration.map(|d| d.as_millis()),
count = ?self.count,
"{}",
self.message
);
}
}
}
}
#[derive(Debug)]
pub struct StructuredEventBuilder {
component: EventComponent,
kind: EventKind,
severity: EventSeverity,
message: Option<String>,
peer_id: Option<PeerId>,
addr: Option<SocketAddr>,
duration: Option<Duration>,
count: Option<u64>,
}
impl StructuredEventBuilder {
pub fn new(component: EventComponent, kind: EventKind) -> Self {
Self {
component,
kind,
severity: EventSeverity::Info,
message: None,
peer_id: None,
addr: None,
duration: None,
count: None,
}
}
pub fn severity(mut self, severity: EventSeverity) -> Self {
self.severity = severity;
self
}
pub fn message(mut self, message: impl Into<String>) -> Self {
self.message = Some(message.into());
self
}
pub fn peer_id(mut self, peer_id: PeerId) -> Self {
self.peer_id = Some(peer_id);
self
}
pub fn addr(mut self, addr: SocketAddr) -> Self {
self.addr = Some(addr);
self
}
pub fn duration(mut self, duration: Duration) -> Self {
self.duration = Some(duration);
self
}
pub fn count(mut self, count: u64) -> Self {
self.count = Some(count);
self
}
pub fn build(self) -> StructuredEvent {
StructuredEvent {
severity: self.severity,
component: self.component,
kind: self.kind,
message: self.message.unwrap_or_else(|| format!("{}", self.kind)),
timestamp: Instant::now(),
peer_id: self.peer_id,
addr: self.addr,
duration: self.duration,
count: self.count,
}
}
pub fn log(self) {
self.build().log();
}
}
#[derive(Debug)]
pub struct ActorTickMetrics {
name: &'static str,
tick_count: AtomicU64,
total_time_ns: AtomicU64,
max_tick_ns: AtomicU64,
}
impl ActorTickMetrics {
pub fn new(name: &'static str) -> Self {
Self {
name,
tick_count: AtomicU64::new(0),
total_time_ns: AtomicU64::new(0),
max_tick_ns: AtomicU64::new(0),
}
}
pub fn record_tick(&self, duration: Duration) {
let ns = duration.as_nanos() as u64;
self.tick_count.fetch_add(1, Ordering::Relaxed);
self.total_time_ns.fetch_add(ns, Ordering::Relaxed);
let mut current_max = self.max_tick_ns.load(Ordering::Relaxed);
while ns > current_max {
match self.max_tick_ns.compare_exchange_weak(
current_max,
ns,
Ordering::Relaxed,
Ordering::Relaxed,
) {
Ok(_) => break,
Err(new_max) => current_max = new_max,
}
}
}
pub fn start_tick(&self) -> TickGuard<'_> {
TickGuard {
metrics: self,
start: Instant::now(),
}
}
pub fn name(&self) -> &'static str {
self.name
}
pub fn tick_count(&self) -> u64 {
self.tick_count.load(Ordering::Relaxed)
}
pub fn average_tick_duration(&self) -> Duration {
let count = self.tick_count.load(Ordering::Relaxed);
if count == 0 {
return Duration::ZERO;
}
let total_ns = self.total_time_ns.load(Ordering::Relaxed);
Duration::from_nanos(total_ns / count)
}
pub fn max_tick_duration(&self) -> Duration {
Duration::from_nanos(self.max_tick_ns.load(Ordering::Relaxed))
}
pub fn snapshot(&self) -> ActorTickSnapshot {
ActorTickSnapshot {
name: self.name,
tick_count: self.tick_count(),
average_duration: self.average_tick_duration(),
max_duration: self.max_tick_duration(),
}
}
pub fn reset(&self) {
self.tick_count.store(0, Ordering::Relaxed);
self.total_time_ns.store(0, Ordering::Relaxed);
self.max_tick_ns.store(0, Ordering::Relaxed);
}
}
pub struct TickGuard<'a> {
metrics: &'a ActorTickMetrics,
start: Instant,
}
impl<'a> Drop for TickGuard<'a> {
fn drop(&mut self) {
self.metrics.record_tick(self.start.elapsed());
}
}
#[derive(Debug, Clone)]
pub struct ActorTickSnapshot {
pub name: &'static str,
pub tick_count: u64,
pub average_duration: Duration,
pub max_duration: Duration,
}
impl fmt::Display for ActorTickSnapshot {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}: {} ticks, avg={:?}, max={:?}",
self.name, self.tick_count, self.average_duration, self.max_duration
)
}
}
#[macro_export]
macro_rules! log_event {
($component:expr, $kind:expr, $msg:expr) => {
$crate::structured_events::StructuredEvent::builder($component, $kind)
.message($msg)
.log()
};
($component:expr, $kind:expr, $msg:expr, severity = $sev:expr) => {
$crate::structured_events::StructuredEvent::builder($component, $kind)
.message($msg)
.severity($sev)
.log()
};
($component:expr, $kind:expr, $msg:expr, addr = $addr:expr) => {
$crate::structured_events::StructuredEvent::builder($component, $kind)
.message($msg)
.addr($addr)
.log()
};
($component:expr, $kind:expr, $msg:expr, peer = $peer:expr) => {
$crate::structured_events::StructuredEvent::builder($component, $kind)
.message($msg)
.peer_id($peer)
.log()
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_event_severity_ordering() {
assert!(EventSeverity::Trace < EventSeverity::Debug);
assert!(EventSeverity::Debug < EventSeverity::Info);
assert!(EventSeverity::Info < EventSeverity::Warn);
assert!(EventSeverity::Warn < EventSeverity::Error);
}
#[test]
fn test_event_builder() {
let event =
StructuredEvent::builder(EventComponent::Connection, EventKind::ConnectionEstablished)
.severity(EventSeverity::Info)
.message("Connection established")
.addr("192.168.1.1:5000".parse().unwrap())
.build();
assert_eq!(event.component, EventComponent::Connection);
assert_eq!(event.kind, EventKind::ConnectionEstablished);
assert_eq!(event.severity, EventSeverity::Info);
assert_eq!(event.message, "Connection established");
assert_eq!(event.addr, Some("192.168.1.1:5000".parse().unwrap()));
}
#[test]
fn test_event_builder_defaults() {
let event =
StructuredEvent::builder(EventComponent::Discovery, EventKind::DiscoveryStarted)
.build();
assert_eq!(event.severity, EventSeverity::Info);
assert_eq!(event.message, "discovery_started");
assert!(event.peer_id.is_none());
assert!(event.addr.is_none());
}
#[test]
fn test_actor_tick_metrics() {
let metrics = ActorTickMetrics::new("test_actor");
metrics.record_tick(Duration::from_millis(10));
metrics.record_tick(Duration::from_millis(20));
metrics.record_tick(Duration::from_millis(5));
assert_eq!(metrics.tick_count(), 3);
assert_eq!(metrics.max_tick_duration(), Duration::from_millis(20));
let avg = metrics.average_tick_duration();
assert!(avg.as_millis() >= 10 && avg.as_millis() <= 13);
}
#[test]
fn test_actor_tick_guard() {
let metrics = ActorTickMetrics::new("test_actor");
{
let _guard = metrics.start_tick();
std::thread::sleep(Duration::from_millis(5));
}
assert_eq!(metrics.tick_count(), 1);
assert!(metrics.max_tick_duration() >= Duration::from_millis(4));
}
#[test]
fn test_actor_tick_reset() {
let metrics = ActorTickMetrics::new("test_actor");
metrics.record_tick(Duration::from_millis(10));
assert_eq!(metrics.tick_count(), 1);
metrics.reset();
assert_eq!(metrics.tick_count(), 0);
assert_eq!(metrics.max_tick_duration(), Duration::ZERO);
}
#[test]
fn test_actor_tick_snapshot() {
let metrics = ActorTickMetrics::new("test_actor");
metrics.record_tick(Duration::from_millis(10));
let snapshot = metrics.snapshot();
assert_eq!(snapshot.name, "test_actor");
assert_eq!(snapshot.tick_count, 1);
}
#[test]
fn test_event_component_display() {
assert_eq!(format!("{}", EventComponent::NatTraversal), "nat_traversal");
assert_eq!(format!("{}", EventComponent::Connection), "connection");
assert_eq!(
format!("{}", EventComponent::PathSelection),
"path_selection"
);
}
#[test]
fn test_event_kind_display() {
assert_eq!(
format!("{}", EventKind::ConnectionEstablished),
"connection_established"
);
assert_eq!(
format!("{}", EventKind::HolePunchStarted),
"hole_punch_started"
);
assert_eq!(format!("{}", EventKind::PathSelected), "path_selected");
}
#[test]
fn test_actor_tick_concurrent() {
use std::sync::Arc;
use std::thread;
let metrics = Arc::new(ActorTickMetrics::new("concurrent_actor"));
let mut handles = vec![];
for _ in 0..10 {
let m = Arc::clone(&metrics);
handles.push(thread::spawn(move || {
for _ in 0..100 {
m.record_tick(Duration::from_micros(1));
}
}));
}
for handle in handles {
handle.join().unwrap();
}
assert_eq!(metrics.tick_count(), 1000);
}
#[test]
fn test_event_with_duration() {
let event =
StructuredEvent::builder(EventComponent::PathSelection, EventKind::PathRttUpdated)
.duration(Duration::from_millis(42))
.build();
assert_eq!(event.duration, Some(Duration::from_millis(42)));
}
#[test]
fn test_event_with_count() {
let event =
StructuredEvent::builder(EventComponent::NatTraversal, EventKind::CleanupPerformed)
.count(5)
.message("Cleaned up 5 expired candidates")
.build();
assert_eq!(event.count, Some(5));
}
}