iroh_quinn_proto/config/
qlog.rs1#[cfg(feature = "qlog")]
2use std::io;
3use std::{io::BufWriter, net::SocketAddr, path::PathBuf, time::SystemTime};
4
5use tracing::{trace, warn};
6
7use crate::{ConnectionId, Instant, Side};
8
9pub trait QlogFactory: Send + Sync + 'static {
15 fn for_connection(
19 &self,
20 side: Side,
21 remote: SocketAddr,
22 initial_dst_cid: ConnectionId,
23 now: Instant,
24 ) -> Option<QlogConfig>;
25}
26
27#[cfg(feature = "qlog")]
36pub struct QlogConfig {
37 pub(crate) writer: Box<dyn io::Write + Send + Sync>,
38 pub(crate) title: Option<String>,
39 pub(crate) description: Option<String>,
40 pub(crate) start_time: Option<Instant>,
41}
42
43#[cfg(feature = "qlog")]
44impl QlogConfig {
45 pub fn new(writer: Box<dyn io::Write + Send + Sync>) -> Self {
47 Self {
48 writer,
49 title: None,
50 description: None,
51 start_time: None,
52 }
53 }
54
55 pub fn title(&mut self, title: Option<String>) -> &mut Self {
57 self.title = title;
58 self
59 }
60
61 pub fn description(&mut self, description: Option<String>) -> &mut Self {
63 self.description = description;
64 self
65 }
66
67 pub fn start_time(&mut self, start_time: Instant) -> &mut Self {
71 self.start_time = Some(start_time);
72 self
73 }
74}
75
76#[derive(Debug)]
78pub struct QlogFileFactory {
79 dir: Option<PathBuf>,
80 prefix: Option<String>,
81 start_instant: Option<Instant>,
82}
83
84impl QlogFileFactory {
85 pub fn new(dir: PathBuf) -> Self {
87 Self {
88 dir: Some(dir),
89 prefix: None,
90 start_instant: None,
91 }
92 }
93
94 pub fn from_env() -> Self {
100 let dir = match std::env::var("QLOGDIR") {
101 Ok(dir) => {
102 if let Err(err) = std::fs::create_dir_all(&dir) {
103 warn!("qlog not enabled: failed to create qlog directory at {dir}: {err}",);
104 None
105 } else {
106 Some(PathBuf::from(dir))
107 }
108 }
109 Err(_) => None,
110 };
111 Self {
112 dir,
113 prefix: None,
114 start_instant: None,
115 }
116 }
117
118 pub fn with_prefix(mut self, prefix: impl ToString) -> Self {
120 self.prefix = Some(prefix.to_string());
121 self
122 }
123
124 pub fn with_start_instant(mut self, start: Instant) -> Self {
128 self.start_instant = Some(start);
129 self
130 }
131}
132
133impl QlogFactory for QlogFileFactory {
134 fn for_connection(
135 &self,
136 side: Side,
137 _remote: SocketAddr,
138 initial_dst_cid: ConnectionId,
139 now: Instant,
140 ) -> Option<QlogConfig> {
141 let dir = self.dir.as_ref()?;
142
143 let name = {
144 let timestamp = SystemTime::now()
145 .checked_sub(Instant::now().duration_since(now))?
146 .duration_since(SystemTime::UNIX_EPOCH)
147 .ok()?
148 .as_millis();
149 let prefix = self
150 .prefix
151 .as_ref()
152 .filter(|prefix| !prefix.is_empty())
153 .map(|prefix| format!("{prefix}-"))
154 .unwrap_or_default();
155 let side = format!("{side:?}").to_lowercase();
156 format!("{prefix}{timestamp}-{initial_dst_cid}-{side}.qlog")
157 };
158 let path = dir.join(name);
159 let file = std::fs::File::create(&path)
160 .inspect_err(|err| warn!("Failed to create qlog file at {}: {err}", path.display()))
161 .ok()?;
162 trace!(
163 "Initialized qlog file for connection {initial_dst_cid} at {}",
164 path.display()
165 );
166 let writer = BufWriter::new(file);
167 let mut config = QlogConfig::new(Box::new(writer));
168 if let Some(instant) = self.start_instant {
169 config.start_time(instant);
170 }
171 Some(config)
172 }
173}