use std::cell::RefCell;
use std::io::{self, Write};
use std::sync::{Arc, Mutex, Once};
use tempfile::TempDir;
#[derive(Clone, Default)]
pub(crate) struct CapturedLogs(Arc<Mutex<Vec<u8>>>);
thread_local! {
static ACTIVE_SINK: RefCell<Option<Arc<Mutex<Vec<u8>>>>> = const { RefCell::new(None) };
}
static INSTALL_CAPTURE_SUBSCRIBER: Once = Once::new();
static INSTALL_ERROR: Mutex<Option<String>> = Mutex::new(None);
pub(crate) struct CapturedWriter;
impl Write for CapturedWriter {
fn write(&mut self, buffer: &[u8]) -> io::Result<usize> {
ACTIVE_SINK.with(|sink| {
if let Some(bytes) = sink.borrow().as_ref() {
let mut bytes = bytes
.lock()
.map_err(|_| io::Error::other("captured log lock poisoned"))?;
bytes.extend_from_slice(buffer);
}
Ok(buffer.len())
})
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
struct SinkGuard;
impl Drop for SinkGuard {
fn drop(&mut self) {
ACTIVE_SINK.with(|sink| sink.borrow_mut().take());
}
}
impl CapturedLogs {
pub(crate) fn capture<T>(body: impl FnOnce() -> T) -> (Self, T) {
INSTALL_CAPTURE_SUBSCRIBER.call_once(|| {
let subscriber = tracing_subscriber::fmt()
.without_time()
.with_ansi(false)
.with_writer(|| CapturedWriter)
.finish();
if let Err(error) = tracing::subscriber::set_global_default(subscriber) {
if let Ok(mut slot) = INSTALL_ERROR.lock() {
*slot = Some(format!("capture subscriber not installed: {error}"));
}
}
});
let captured = Self::default();
if let Some(reason) = INSTALL_ERROR
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.as_deref()
&& let Ok(mut bytes) = captured.0.lock()
{
bytes.extend_from_slice(reason.as_bytes());
}
ACTIVE_SINK.with(|sink| {
*sink.borrow_mut() = Some(Arc::clone(&captured.0));
});
let guard = SinkGuard;
let value = body();
drop(guard);
(captured, value)
}
pub(crate) fn text(&self) -> Result<String, Box<dyn std::error::Error>> {
let bytes = self.0.lock().map_err(|_| "captured log lock poisoned")?;
Ok(String::from_utf8(bytes.clone())?)
}
}
pub(crate) fn make_private(path: &std::path::Path) -> std::io::Result<()> {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o700))?;
}
#[cfg(not(unix))]
{
let _ = path;
}
Ok(())
}
pub(crate) fn private_tempdir() -> std::io::Result<TempDir> {
let dir = tempfile::tempdir()?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(dir.path(), std::fs::Permissions::from_mode(0o700))?;
}
Ok(dir)
}