ant_quic/logging/
lifecycle.rs

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