use std::io::{self, Write};
use std::sync::Mutex;
static CAPTURE: Mutex<Option<Vec<u8>>> = Mutex::new(None);
struct Stderr;
impl Write for Stderr {
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
let mut capture = CAPTURE.lock().unwrap_or_else(|error| error.into_inner());
if let Some(buffer) = capture.as_mut() {
buffer.extend_from_slice(bytes);
Ok(bytes.len())
} else {
io::stderr().write(bytes)
}
}
fn flush(&mut self) -> io::Result<()> {
io::stderr().flush()
}
}
pub(crate) const DEFAULT_FILTER: &str = "info,portable_pty=off";
fn builder() -> env_logger::Builder {
let mut builder = env_logger::Builder::from_env(
env_logger::Env::default().filter_or("MBX_LOG", DEFAULT_FILTER),
);
builder
.format_target(false)
.format_timestamp(None)
.target(env_logger::Target::Pipe(Box::new(Stderr)));
builder
}
pub fn init() {
builder().init();
}
pub(crate) fn note(message: &str) {
let _ = writeln!(Stderr, "{message}");
}
pub(crate) struct Capture;
impl Capture {
pub(crate) fn start() -> Self {
*CAPTURE.lock().unwrap_or_else(|error| error.into_inner()) = Some(Vec::new());
Self
}
pub(crate) fn drain(&self) -> Vec<u8> {
CAPTURE
.lock()
.unwrap_or_else(|error| error.into_inner())
.as_mut()
.map(std::mem::take)
.unwrap_or_default()
}
}
impl Drop for Capture {
fn drop(&mut self) {
let mut capture = CAPTURE.lock().unwrap_or_else(|error| error.into_inner());
if let Some(bytes) = capture.take() {
let _ = io::stderr().write_all(&bytes);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use log::Log;
fn enabled(logger: &env_logger::Logger, target: &str, level: log::Level) -> bool {
logger.enabled(&log::Metadata::builder().target(target).level(level).build())
}
#[test]
fn a_pty_failure_mbx_recovers_from_is_not_announced_to_the_user() {
if std::env::var_os("MBX_LOG").is_some() {
return;
}
let logger = builder().build();
assert!(!enabled(&logger, "portable_pty", log::Level::Error));
assert!(enabled(&logger, "mbx::cli::cargo", log::Level::Info));
assert!(!enabled(&logger, "mbx::cli::cargo", log::Level::Debug));
}
#[test]
fn an_explicit_filter_can_ask_for_the_pty_report() {
let logger = env_logger::Builder::new().parse_filters("debug").build();
assert!(enabled(&logger, "portable_pty", log::Level::Error));
}
#[test]
fn background_notes_and_logger_writes_share_the_screen_queue() {
let capture = Capture::start();
std::thread::spawn(|| {
note("background warning");
Stderr.write_all(b"logger warning\n").unwrap();
})
.join()
.unwrap();
let output = String::from_utf8(capture.drain()).unwrap();
assert!(output.contains("background warning"));
assert!(output.contains("logger warning"));
}
}