1use std::collections::HashMap;
12use tracing::{Span, debug, info, warn};
13
14use super::{ConnectionRole, LogEvent, logger};
15use crate::{ConnectionId, Duration, Instant};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum ConnectionState {
20 Initiated,
22 Handshaking,
24 Established,
26 Migrating,
28 Closing,
30 Closed,
32 Lost,
34}
35
36pub struct ConnectionLifecycle {
38 pub conn_id: ConnectionId,
40 pub role: ConnectionRole,
42 pub state: ConnectionState,
44 pub initiated_at: Instant,
46 pub handshake_started_at: Option<Instant>,
48 pub established_at: Option<Instant>,
50 pub closed_at: Option<Instant>,
52 pub close_reason: Option<String>,
54 pub total_bytes_sent: u64,
56 pub total_bytes_received: u64,
58 pub total_packets_sent: u64,
60 pub total_packets_received: u64,
62}
63
64impl ConnectionLifecycle {
65 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 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 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 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 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 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
217pub 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
232pub 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
241pub 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
251pub 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
261pub 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
272pub 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
292pub 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
302pub 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}