#[cfg(feature = "qlog")]
use std::io;
use std::{io::BufWriter, net::SocketAddr, path::PathBuf, time::SystemTime};
use tracing::{trace, warn};
use crate::{ConnectionId, Instant, Side};
pub trait QlogFactory: Send + Sync + 'static {
fn for_connection(
&self,
side: Side,
remote: SocketAddr,
initial_dst_cid: ConnectionId,
now: Instant,
) -> Option<QlogConfig>;
}
#[cfg(feature = "qlog")]
pub struct QlogConfig {
pub(crate) writer: Box<dyn io::Write + Send + Sync>,
pub(crate) title: Option<String>,
pub(crate) description: Option<String>,
pub(crate) start_time: Option<Instant>,
}
#[cfg(feature = "qlog")]
impl QlogConfig {
pub fn new(writer: Box<dyn io::Write + Send + Sync>) -> Self {
Self {
writer,
title: None,
description: None,
start_time: None,
}
}
pub fn title(&mut self, title: Option<String>) -> &mut Self {
self.title = title;
self
}
pub fn description(&mut self, description: Option<String>) -> &mut Self {
self.description = description;
self
}
pub fn start_time(&mut self, start_time: Instant) -> &mut Self {
self.start_time = Some(start_time);
self
}
}
#[derive(Debug)]
pub struct QlogFileFactory {
dir: Option<PathBuf>,
prefix: Option<String>,
start_instant: Option<Instant>,
}
impl QlogFileFactory {
pub fn new(dir: PathBuf) -> Self {
Self {
dir: Some(dir),
prefix: None,
start_instant: None,
}
}
pub fn from_env() -> Self {
let dir = match std::env::var("QLOGDIR") {
Ok(dir) => {
if let Err(err) = std::fs::create_dir_all(&dir) {
warn!("qlog not enabled: failed to create qlog directory at {dir}: {err}",);
None
} else {
Some(PathBuf::from(dir))
}
}
Err(_) => None,
};
Self {
dir,
prefix: None,
start_instant: None,
}
}
pub fn with_prefix(mut self, prefix: impl ToString) -> Self {
self.prefix = Some(prefix.to_string());
self
}
pub fn with_start_instant(mut self, start: Instant) -> Self {
self.start_instant = Some(start);
self
}
}
impl QlogFactory for QlogFileFactory {
fn for_connection(
&self,
side: Side,
_remote: SocketAddr,
initial_dst_cid: ConnectionId,
now: Instant,
) -> Option<QlogConfig> {
let dir = self.dir.as_ref()?;
let name = {
let timestamp = SystemTime::now()
.checked_sub(Instant::now().duration_since(now))?
.duration_since(SystemTime::UNIX_EPOCH)
.ok()?
.as_millis();
let prefix = self
.prefix
.as_ref()
.filter(|prefix| !prefix.is_empty())
.map(|prefix| format!("{prefix}-"))
.unwrap_or_default();
let side = format!("{side:?}").to_lowercase();
format!("{prefix}{timestamp}-{initial_dst_cid}-{side}.qlog")
};
let path = dir.join(name);
let file = std::fs::File::create(&path)
.inspect_err(|err| warn!("Failed to create qlog file at {}: {err}", path.display()))
.ok()?;
trace!(
"Initialized qlog file for connection {initial_dst_cid} at {}",
path.display()
);
let writer = BufWriter::new(file);
let mut config = QlogConfig::new(Box::new(writer));
if let Some(instant) = self.start_instant {
config.start_time(instant);
}
Some(config)
}
}