ant_quic/logging/
lifecycle.rs

1/// Connection lifecycle logging
2///
3/// Tracks and logs the complete lifecycle of QUIC connections
4use std::collections::HashMap;
5use tracing::{Span, debug, info, warn};
6
7use super::{ConnectionRole, LogEvent, logger};
8use crate::{ConnectionId, Duration, Instant};
9
10/// Connection lifecycle state
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum ConnectionState {
13    /// Connection attempt initiated
14    Initiated,
15    /// Performing handshake
16    Handshaking,
17    /// Handshake complete, connection established
18    Established,
19    /// Connection is migrating to new path
20    Migrating,
21    /// Connection is closing
22    Closing,
23    /// Connection is closed
24    Closed,
25    /// Connection was lost (timeout/error)
26    Lost,
27}
28
29/// Connection lifecycle tracker
30pub struct ConnectionLifecycle {
31    pub conn_id: ConnectionId,
32    pub role: ConnectionRole,
33    pub state: ConnectionState,
34    pub initiated_at: Instant,
35    pub handshake_started_at: Option<Instant>,
36    pub established_at: Option<Instant>,
37    pub closed_at: Option<Instant>,
38    pub close_reason: Option<String>,
39    pub total_bytes_sent: u64,
40    pub total_bytes_received: u64,
41    pub total_packets_sent: u64,
42    pub total_packets_received: u64,
43}
44
45impl ConnectionLifecycle {
46    /// Create a new connection lifecycle tracker
47    pub fn new(conn_id: ConnectionId, role: ConnectionRole) -> Self {
48        Self {
49            conn_id,
50            role,
51            state: ConnectionState::Initiated,
52            initiated_at: Instant::now(),
53            handshake_started_at: None,
54            established_at: None,
55            closed_at: None,
56            close_reason: None,
57            total_bytes_sent: 0,
58            total_bytes_received: 0,
59            total_packets_sent: 0,
60            total_packets_received: 0,
61        }
62    }
63
64    /// Update connection state
65    pub fn update_state(&mut self, new_state: ConnectionState) {
66        let old_state = self.state;
67        self.state = new_state;
68
69        match new_state {
70            ConnectionState::Handshaking => {
71                self.handshake_started_at = Some(Instant::now());
72            }
73            ConnectionState::Established => {
74                self.established_at = Some(Instant::now());
75            }
76            ConnectionState::Closed | ConnectionState::Lost => {
77                self.closed_at = Some(Instant::now());
78            }
79            _ => {}
80        }
81
82        self.log_state_transition(old_state, new_state);
83    }
84
85    /// Log state transition
86    fn log_state_transition(&self, old_state: ConnectionState, new_state: ConnectionState) {
87        let mut fields = HashMap::new();
88        fields.insert("conn_id".to_string(), format!("{:?}", self.conn_id));
89        fields.insert("role".to_string(), format!("{:?}", self.role));
90        fields.insert("old_state".to_string(), format!("{old_state:?}"));
91        fields.insert("new_state".to_string(), format!("{new_state:?}"));
92
93        // Add timing information
94        if let Some(duration) = self.duration_in_state(old_state) {
95            fields.insert("duration_ms".to_string(), duration.as_millis().to_string());
96        }
97
98        let level = match new_state {
99            ConnectionState::Lost => tracing::Level::WARN,
100            ConnectionState::Established => tracing::Level::INFO,
101            _ => tracing::Level::DEBUG,
102        };
103
104        logger().log_event(LogEvent {
105            timestamp: Instant::now(),
106            level,
107            target: "ant_quic::connection::lifecycle".to_string(),
108            message: "connection_state_changed".to_string(),
109            fields,
110            span_id: None,
111        });
112    }
113
114    /// Get duration in a specific state
115    fn duration_in_state(&self, state: ConnectionState) -> Option<Duration> {
116        match state {
117            ConnectionState::Initiated => {
118                let end = self.handshake_started_at.unwrap_or_else(Instant::now);
119                Some(end.duration_since(self.initiated_at))
120            }
121            ConnectionState::Handshaking => {
122                if let Some(start) = self.handshake_started_at {
123                    let end = self.established_at.unwrap_or_else(Instant::now);
124                    Some(end.duration_since(start))
125                } else {
126                    None
127                }
128            }
129            ConnectionState::Established => {
130                if let Some(start) = self.established_at {
131                    let end = self.closed_at.unwrap_or_else(Instant::now);
132                    Some(end.duration_since(start))
133                } else {
134                    None
135                }
136            }
137            _ => None,
138        }
139    }
140
141    /// Log connection summary when closed
142    pub fn log_summary(&self) {
143        let total_duration = self
144            .closed_at
145            .unwrap_or_else(Instant::now)
146            .duration_since(self.initiated_at);
147
148        let mut fields = HashMap::new();
149        fields.insert("conn_id".to_string(), format!("{:?}", self.conn_id));
150        fields.insert("role".to_string(), format!("{:?}", self.role));
151        fields.insert(
152            "total_duration_ms".to_string(),
153            total_duration.as_millis().to_string(),
154        );
155        fields.insert("bytes_sent".to_string(), self.total_bytes_sent.to_string());
156        fields.insert(
157            "bytes_received".to_string(),
158            self.total_bytes_received.to_string(),
159        );
160        fields.insert(
161            "packets_sent".to_string(),
162            self.total_packets_sent.to_string(),
163        );
164        fields.insert(
165            "packets_received".to_string(),
166            self.total_packets_received.to_string(),
167        );
168
169        if let Some(handshake_duration) = self.duration_in_state(ConnectionState::Handshaking) {
170            fields.insert(
171                "handshake_duration_ms".to_string(),
172                handshake_duration.as_millis().to_string(),
173            );
174        }
175
176        if let Some(established_duration) = self.duration_in_state(ConnectionState::Established) {
177            fields.insert(
178                "established_duration_ms".to_string(),
179                established_duration.as_millis().to_string(),
180            );
181        }
182
183        if let Some(reason) = &self.close_reason {
184            fields.insert("close_reason".to_string(), reason.clone());
185        }
186
187        logger().log_event(LogEvent {
188            timestamp: Instant::now(),
189            level: tracing::Level::INFO,
190            target: "ant_quic::connection::lifecycle".to_string(),
191            message: "connection_summary".to_string(),
192            fields,
193            span_id: None,
194        });
195    }
196}
197
198/// Log connection lifecycle events
199pub fn log_connection_initiated(
200    conn_id: &ConnectionId,
201    role: ConnectionRole,
202    remote_addr: std::net::SocketAddr,
203) {
204    info!(
205        target: "ant_quic::connection::lifecycle",
206        conn_id = ?conn_id,
207        role = ?role,
208        remote_addr = %remote_addr,
209        "Connection initiated"
210    );
211}
212
213pub fn log_handshake_started(conn_id: &ConnectionId) {
214    debug!(
215        target: "ant_quic::connection::lifecycle",
216        conn_id = ?conn_id,
217        "Handshake started"
218    );
219}
220
221pub fn log_handshake_completed(conn_id: &ConnectionId, duration: Duration) {
222    info!(
223        target: "ant_quic::connection::lifecycle",
224        conn_id = ?conn_id,
225        duration_ms = duration.as_millis(),
226        "Handshake completed"
227    );
228}
229
230pub fn log_connection_established(conn_id: &ConnectionId, negotiated_version: u32) {
231    info!(
232        target: "ant_quic::connection::lifecycle",
233        conn_id = ?conn_id,
234        negotiated_version = format!("0x{:08x}", negotiated_version),
235        "Connection established"
236    );
237}
238
239pub fn log_connection_migration(conn_id: &ConnectionId, old_path: &str, new_path: &str) {
240    info!(
241        target: "ant_quic::connection::lifecycle",
242        conn_id = ?conn_id,
243        old_path = old_path,
244        new_path = new_path,
245        "Connection migrated to new path"
246    );
247}
248
249pub fn log_connection_closed(conn_id: &ConnectionId, reason: &str, error_code: Option<u64>) {
250    let mut fields = HashMap::new();
251    fields.insert("conn_id".to_string(), format!("{conn_id:?}"));
252    fields.insert("reason".to_string(), reason.to_string());
253
254    if let Some(code) = error_code {
255        fields.insert("error_code".to_string(), format!("0x{code:x}"));
256    }
257
258    logger().log_event(LogEvent {
259        timestamp: Instant::now(),
260        level: tracing::Level::DEBUG,
261        target: "ant_quic::connection::lifecycle".to_string(),
262        message: "connection_closed".to_string(),
263        fields,
264        span_id: None,
265    });
266}
267
268pub fn log_connection_lost(conn_id: &ConnectionId, reason: &str) {
269    warn!(
270        target: "ant_quic::connection::lifecycle",
271        conn_id = ?conn_id,
272        reason = reason,
273        "Connection lost"
274    );
275}
276
277/// Create a span for the entire connection lifetime
278pub fn create_connection_lifetime_span(conn_id: &ConnectionId, role: ConnectionRole) -> Span {
279    tracing::span!(
280        tracing::Level::INFO,
281        "connection_lifetime",
282        conn_id = %format!("{:?}", conn_id),
283        role = ?role,
284    )
285}