use std::{
io,
net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket},
sync::Mutex,
};
use crate::xray_exporter::types::SegmentDocument;
use super::SegmentDocumentExporter;
struct BufferResetGuard<'a> {
buf: &'a mut Vec<u8>,
len: usize,
}
impl<'a> Drop for BufferResetGuard<'a> {
fn drop(&mut self) {
self.buf.truncate(self.len);
}
}
#[derive(Debug)]
pub struct XrayDaemonClient {
socket: UdpSocket,
inner_buf: Mutex<Vec<u8>>,
}
impl XrayDaemonClient {
const DAEMON_HEADER: &'static [u8] = "{\"format\": \"json\", \"version\": 1}\n".as_bytes();
const DEFAULT_DAEMON_PORT: u16 = 2000;
const AWS_XRAY_DAEMON_ADDRESS_ENV_VAR: &'static str = "AWS_XRAY_DAEMON_ADDRESS";
pub fn new(addr: SocketAddr) -> Result<Self, io::Error> {
let socket = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, 0))?;
socket.set_nonblocking(true)?;
socket.connect(addr)?;
let mut buf = Vec::with_capacity(65507); buf.extend_from_slice(Self::DAEMON_HEADER);
Ok(Self {
socket,
inner_buf: Mutex::new(buf),
})
}
fn send_segment_document(
&self,
segment_document: SegmentDocument<'_>,
) -> Result<(), io::Error> {
#[cfg(feature = "internal-logs")]
tracing::trace!("Exporting segment");
let mut buf = self.inner_buf.lock().unwrap();
let mut guard = BufferResetGuard {
buf: &mut buf,
len: Self::DAEMON_HEADER.len(),
};
segment_document.to_writer(&mut guard.buf);
self.socket.send(guard.buf)?;
Ok(())
}
}
impl Default for XrayDaemonClient {
fn default() -> Self {
Self::new(
std::env::var(Self::AWS_XRAY_DAEMON_ADDRESS_ENV_VAR)
.ok()
.and_then(|s| s.parse::<SocketAddr>().ok())
.unwrap_or_else(|| {
#[cfg(feature = "internal-logs")]
tracing::warn!(
"No valid {} env variable detected, falling back on default",
Self::AWS_XRAY_DAEMON_ADDRESS_ENV_VAR
);
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), Self::DEFAULT_DAEMON_PORT)
}),
)
.expect("could not bind daemon")
}
}
impl SegmentDocumentExporter for XrayDaemonClient {
type Error = io::Error;
#[cfg_attr(feature = "internal-logs", tracing::instrument(skip(self, batch)))]
async fn export_segment_documents(
&self,
batch: Vec<SegmentDocument<'_>>,
) -> Result<(), Self::Error> {
#[cfg(feature = "internal-logs")]
tracing::debug!("Received {} segments to export", batch.len());
for segment_document in batch {
self.send_segment_document(segment_document)?;
}
Ok(())
}
}