use std::cell::RefCell;
use std::collections::VecDeque;
use std::io::{self, Write};
use std::mem;
use std::sync::atomic::{AtomicU8, AtomicUsize, Ordering};
use std::sync::{Arc, Mutex, OnceLock, RwLock};
use std::time::Instant;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Level {
Off = 0,
Warn = 1,
Debug = 2,
}
const _: () = assert!(Level::Off as u8 == 0);
const _: () = assert!(Level::Warn as u8 == 1);
const _: () = assert!(Level::Debug as u8 == 2);
pub const ENV_VAR: &str = "LINESMITH_LOG";
const DEFAULT_LEVEL: Level = Level::Warn;
static LEVEL: AtomicU8 = AtomicU8::new(DEFAULT_LEVEL as u8);
pub fn apply(raw: Option<&str>, warn_sink: &mut dyn Write) {
match decide_init(raw) {
InitDecision::Keep => {}
InitDecision::Set(l) => set_level(l),
InitDecision::Warn(bad) => {
let _ = writeln!(
warn_sink,
"linesmith: {ENV_VAR}={bad:?} unrecognized; using default ({DEFAULT_LEVEL:?})"
);
set_level(DEFAULT_LEVEL);
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub(crate) enum InitDecision<'a> {
Keep,
Set(Level),
Warn(&'a str),
}
pub(crate) fn decide_init(raw: Option<&str>) -> InitDecision<'_> {
match raw {
None => InitDecision::Keep,
Some(s) => match Level::parse(s) {
Some(l) => InitDecision::Set(l),
None => InitDecision::Warn(s),
},
}
}
pub fn set_level(l: Level) {
LEVEL.store(l as u8, Ordering::Relaxed);
}
#[must_use]
pub fn level() -> Level {
from_u8(LEVEL.load(Ordering::Relaxed))
}
fn from_u8(n: u8) -> Level {
match n {
0 => Level::Off,
1 => Level::Warn,
2 => Level::Debug,
_ => {
debug_assert!(false, "logging::LEVEL holds out-of-range byte {n}");
Level::Debug
}
}
}
#[must_use]
pub fn is_enabled(at_least: Level) -> bool {
level() >= at_least
}
pub trait LogSink: Send + Sync {
fn emit(&self, lvl: Level, msg: &str);
fn emit_error(&self, msg: &str);
}
#[derive(Debug, Default)]
pub struct StderrSink;
impl LogSink for StderrSink {
fn emit(&self, lvl: Level, msg: &str) {
let tag = match lvl {
Level::Off => return,
Level::Warn => "warn",
Level::Debug => "debug",
};
let _ = writeln!(io::stderr().lock(), "linesmith [{tag}]: {msg}");
}
fn emit_error(&self, msg: &str) {
let _ = writeln!(io::stderr().lock(), "linesmith [error]: {msg}");
}
}
pub const CAPTURED_SINK_DEFAULT_CAP: usize = 256;
#[derive(Debug, Clone)]
pub struct CapturedEntry {
pub at: Instant,
pub text: String,
}
#[derive(Debug, Default)]
pub struct CapturedDrain {
pub entries: Vec<CapturedEntry>,
pub dropped: usize,
}
#[derive(Debug)]
pub struct CapturedSink {
entries: Mutex<VecDeque<CapturedEntry>>,
cap: usize,
dropped: AtomicUsize,
emit_seq: AtomicUsize,
last_drain_seq: AtomicUsize,
}
impl Default for CapturedSink {
fn default() -> Self {
Self::with_capacity(CAPTURED_SINK_DEFAULT_CAP)
}
}
impl CapturedSink {
#[must_use]
pub fn with_capacity(cap: usize) -> Self {
let cap = cap.max(1);
Self {
entries: Mutex::new(VecDeque::with_capacity(cap.min(64))),
cap,
dropped: AtomicUsize::new(0),
emit_seq: AtomicUsize::new(0),
last_drain_seq: AtomicUsize::new(0),
}
}
#[must_use]
pub fn drain(&self) -> Vec<String> {
let detailed = self.drain_detailed();
detailed.entries.into_iter().map(|e| e.text).collect()
}
#[must_use]
pub fn drain_detailed(&self) -> CapturedDrain {
let mut g = self.entries.lock().unwrap_or_else(|p| p.into_inner());
let entries: Vec<CapturedEntry> = g.drain(..).collect();
let seq_at_drain = self.emit_seq.load(Ordering::Acquire);
self.last_drain_seq.store(seq_at_drain, Ordering::Release);
let dropped = self.dropped.swap(0, Ordering::AcqRel);
CapturedDrain { entries, dropped }
}
#[must_use]
pub fn capacity(&self) -> usize {
self.cap
}
#[must_use]
pub fn emit_seq(&self) -> usize {
self.emit_seq.load(Ordering::Acquire)
}
#[must_use]
pub fn last_drain_seq(&self) -> usize {
self.last_drain_seq.load(Ordering::Acquire)
}
#[cfg(test)]
fn snapshot(&self) -> Vec<String> {
self.entries
.lock()
.unwrap_or_else(|p| p.into_inner())
.iter()
.map(|e| e.text.clone())
.collect()
}
fn push(&self, text: String) {
let entry = CapturedEntry {
at: Instant::now(),
text,
};
let mut g = self.entries.lock().unwrap_or_else(|p| p.into_inner());
if g.len() == self.cap {
g.pop_front();
self.dropped.fetch_add(1, Ordering::Release);
}
g.push_back(entry);
self.emit_seq.fetch_add(1, Ordering::Release);
}
}
impl LogSink for CapturedSink {
fn emit(&self, lvl: Level, msg: &str) {
let tag = match lvl {
Level::Off => return,
Level::Warn => "warn",
Level::Debug => "debug",
};
self.push(format!("[{tag}] {msg}"));
}
fn emit_error(&self, msg: &str) {
self.push(format!("[error] {msg}"));
}
}
static SINK: OnceLock<RwLock<Arc<dyn LogSink>>> = OnceLock::new();
#[doc(hidden)]
pub fn _test_serial_lock() -> std::sync::MutexGuard<'static, ()> {
static M: OnceLock<Mutex<()>> = OnceLock::new();
M.get_or_init(|| Mutex::new(()))
.lock()
.unwrap_or_else(|p| p.into_inner())
}
#[must_use = "binding to `_` drops the guard immediately, which restores the prior thread-local sink before the helper's window opens; bind to a real name to hold it"]
struct ThreadSinkGuard {
prior: Option<Arc<CapturedSink>>,
}
impl Drop for ThreadSinkGuard {
fn drop(&mut self) {
let prior = self.prior.take();
let _old = THREAD_SINK.with(|cell| cell.replace(prior));
}
}
#[doc(hidden)]
pub fn _test_capture_warns<F, T>(f: F) -> (T, Vec<String>)
where
F: FnOnce() -> T,
{
let _serial = _test_serial_lock();
let sink = Arc::new(CapturedSink::default());
let prior = THREAD_SINK.with(|cell| cell.replace(Some(sink.clone())));
let _restore = ThreadSinkGuard { prior };
let result = f();
let captured = sink.drain();
(result, captured)
}
fn sink_slot() -> &'static RwLock<Arc<dyn LogSink>> {
SINK.get_or_init(|| RwLock::new(Arc::new(StderrSink)))
}
pub(crate) fn install_sink(new_sink: Arc<dyn LogSink>) -> Arc<dyn LogSink> {
let mut g = sink_slot().write().unwrap_or_else(|p| p.into_inner());
mem::replace(&mut *g, new_sink)
}
#[must_use = "binding to `_` drops the guard immediately, which restores the prior sink right away; bind to `_g` (or any real name) to hold it for the scope"]
pub struct SinkGuard {
prior: Option<Arc<dyn LogSink>>,
}
impl SinkGuard {
pub fn install(new_sink: Arc<dyn LogSink>) -> Self {
Self {
prior: Some(install_sink(new_sink)),
}
}
}
impl Drop for SinkGuard {
fn drop(&mut self) {
if let Some(prior) = self.prior.take() {
install_sink(prior);
}
}
}
thread_local! {
static THREAD_SINK: RefCell<Option<Arc<CapturedSink>>> = const { RefCell::new(None) };
}
#[must_use = "callers must skip the global sink when the thread-local fired, or the emission double-routes"]
fn with_thread_sink<F: FnOnce(&CapturedSink)>(f: F) -> bool {
let sink = THREAD_SINK
.try_with(|cell| cell.borrow().clone())
.ok()
.flatten();
if let Some(sink) = sink {
f(&sink);
true
} else {
false
}
}
pub fn emit(lvl: Level, msg: &str) {
if with_thread_sink(|sink| sink.emit(lvl, msg)) {
return;
}
if !is_enabled(lvl) {
return;
}
let guard = sink_slot().read().unwrap_or_else(|p| p.into_inner());
guard.emit(lvl, msg);
}
pub fn emit_error(msg: &str) {
if with_thread_sink(|sink| sink.emit_error(msg)) {
return;
}
let guard = sink_slot().read().unwrap_or_else(|p| p.into_inner());
guard.emit_error(msg);
}
impl Level {
#[must_use]
pub fn parse(s: &str) -> Option<Self> {
match s.trim().to_ascii_lowercase().as_str() {
"off" | "none" | "0" => Some(Level::Off),
"warn" | "warning" => Some(Level::Warn),
"debug" | "trace" | "all" => Some(Level::Debug),
_ => None,
}
}
}
#[macro_export]
macro_rules! lsm_warn {
($($arg:tt)*) => {
$crate::logging::emit($crate::logging::Level::Warn, &format!($($arg)*))
};
}
#[macro_export]
macro_rules! lsm_debug {
($($arg:tt)*) => {
if $crate::logging::is_enabled($crate::logging::Level::Debug) {
$crate::logging::emit($crate::logging::Level::Debug, &format!($($arg)*));
}
};
}
#[macro_export]
macro_rules! lsm_error {
($($arg:tt)*) => {
$crate::logging::emit_error(&format!($($arg)*))
};
}
#[cfg(test)]
mod tests {
use super::*;
fn lock() -> std::sync::MutexGuard<'static, ()> {
super::_test_serial_lock()
}
#[test]
fn default_level_is_warn() {
let _g = lock();
set_level(DEFAULT_LEVEL);
assert_eq!(level(), Level::Warn);
assert!(is_enabled(Level::Warn));
assert!(!is_enabled(Level::Debug));
}
#[test]
fn debug_enables_every_lower_level() {
let _g = lock();
set_level(Level::Debug);
assert!(is_enabled(Level::Warn));
assert!(is_enabled(Level::Debug));
set_level(DEFAULT_LEVEL);
}
#[test]
fn off_suppresses_every_level() {
let _g = lock();
set_level(Level::Off);
assert!(!is_enabled(Level::Warn));
assert!(!is_enabled(Level::Debug));
set_level(DEFAULT_LEVEL);
}
#[test]
fn parse_accepts_common_aliases() {
assert_eq!(Level::parse("warn"), Some(Level::Warn));
assert_eq!(Level::parse("WARN"), Some(Level::Warn));
assert_eq!(Level::parse(" warn "), Some(Level::Warn));
assert_eq!(Level::parse("warning"), Some(Level::Warn));
assert_eq!(Level::parse("debug"), Some(Level::Debug));
assert_eq!(Level::parse("trace"), Some(Level::Debug));
assert_eq!(Level::parse("all"), Some(Level::Debug));
assert_eq!(Level::parse("off"), Some(Level::Off));
assert_eq!(Level::parse("none"), Some(Level::Off));
assert_eq!(Level::parse("0"), Some(Level::Off));
}
#[test]
fn parse_rejects_error_and_info_aliases() {
assert_eq!(Level::parse("error"), None);
assert_eq!(Level::parse("info"), None);
}
#[test]
fn parse_rejects_garbage() {
assert_eq!(Level::parse("verbose"), None);
assert_eq!(Level::parse(""), None);
assert_eq!(Level::parse("debug2"), None);
}
#[test]
fn decide_init_keeps_default_when_env_unset() {
assert_eq!(decide_init(None), InitDecision::Keep);
}
#[test]
fn decide_init_parses_recognized_levels() {
assert_eq!(decide_init(Some("debug")), InitDecision::Set(Level::Debug));
assert_eq!(decide_init(Some("warn")), InitDecision::Set(Level::Warn));
assert_eq!(decide_init(Some("off")), InitDecision::Set(Level::Off));
}
#[test]
fn decide_init_warns_on_garbage() {
assert_eq!(decide_init(Some("loud")), InitDecision::Warn("loud"));
assert_eq!(decide_init(Some("")), InitDecision::Warn(""));
}
#[test]
fn apply_writes_warning_to_injected_sink_and_resets_to_default() {
let _g = lock();
set_level(Level::Off);
let mut sink = Vec::<u8>::new();
apply(Some("loud"), &mut sink);
let written = String::from_utf8(sink).expect("utf8");
assert!(
written.contains("LINESMITH_LOG=\"loud\""),
"expected the unrecognized value echoed, got {written:?}"
);
assert!(written.contains("unrecognized"));
assert_eq!(level(), DEFAULT_LEVEL);
}
#[test]
fn apply_keeps_level_when_env_unset() {
let _g = lock();
set_level(Level::Debug);
let mut sink = Vec::<u8>::new();
apply(None, &mut sink);
assert!(sink.is_empty(), "no-env must not write: {sink:?}");
assert_eq!(level(), Level::Debug);
set_level(DEFAULT_LEVEL);
}
#[test]
fn apply_sets_recognized_level_without_writing() {
let _g = lock();
set_level(Level::Off);
let mut sink = Vec::<u8>::new();
apply(Some("debug"), &mut sink);
assert!(sink.is_empty());
assert_eq!(level(), Level::Debug);
set_level(DEFAULT_LEVEL);
}
#[test]
fn lsm_debug_skips_format_when_suppressed() {
use std::cell::Cell;
use std::fmt;
struct CountingDisplay<'a>(&'a Cell<u32>);
impl fmt::Display for CountingDisplay<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.set(self.0.get() + 1);
f.write_str("x")
}
}
let _g = lock();
let counter = Cell::new(0u32);
set_level(Level::Warn);
lsm_debug!("{}", CountingDisplay(&counter));
assert_eq!(counter.get(), 0, "format! must not run when suppressed");
set_level(Level::Debug);
lsm_debug!("{}", CountingDisplay(&counter));
assert_eq!(counter.get(), 1, "format! must run when enabled");
set_level(DEFAULT_LEVEL);
}
#[test]
fn from_u8_roundtrips_known_bytes() {
assert_eq!(from_u8(0), Level::Off);
assert_eq!(from_u8(1), Level::Warn);
assert_eq!(from_u8(2), Level::Debug);
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "out-of-range byte")]
fn from_u8_debug_panics_on_out_of_range() {
let _ = from_u8(99);
}
#[test]
#[cfg(not(debug_assertions))]
fn from_u8_saturates_out_of_range_to_debug_in_release() {
assert_eq!(from_u8(3), Level::Debug);
assert_eq!(from_u8(99), Level::Debug);
assert_eq!(from_u8(u8::MAX), Level::Debug);
}
#[test]
fn captured_sink_records_warn_emit_with_compact_format() {
let _g = lock();
set_level(Level::Warn);
let captured = Arc::new(CapturedSink::default());
let _restore = SinkGuard::install(captured.clone());
emit(Level::Warn, "hello");
assert_eq!(captured.snapshot(), vec!["[warn] hello".to_string()]);
set_level(DEFAULT_LEVEL);
}
#[test]
fn captured_sink_records_error_bypassing_off_level() {
let _g = lock();
set_level(Level::Off);
let captured = Arc::new(CapturedSink::default());
let _restore = SinkGuard::install(captured.clone());
emit_error("render panic");
assert_eq!(
captured.snapshot(),
vec!["[error] render panic".to_string()]
);
set_level(DEFAULT_LEVEL);
}
#[test]
fn captured_sink_skips_debug_emit_when_level_warn() {
let _g = lock();
set_level(Level::Warn);
let captured = Arc::new(CapturedSink::default());
let _restore = SinkGuard::install(captured.clone());
emit(Level::Debug, "verbose");
assert!(captured.snapshot().is_empty());
set_level(DEFAULT_LEVEL);
}
#[test]
fn emit_error_fires_at_every_level() {
let _g = lock();
for l in [Level::Off, Level::Warn, Level::Debug] {
set_level(l);
let captured = Arc::new(CapturedSink::default());
let _restore = SinkGuard::install(captured.clone());
emit_error("structural failure");
assert_eq!(
captured.snapshot(),
vec!["[error] structural failure".to_string()],
"emit_error must fire at level {l:?}",
);
}
set_level(DEFAULT_LEVEL);
}
#[test]
fn captured_sink_drain_returns_entries_and_empties_buffer() {
let _g = lock();
set_level(Level::Warn);
let captured = Arc::new(CapturedSink::default());
let _restore = SinkGuard::install(captured.clone());
emit(Level::Warn, "first");
emit(Level::Warn, "second");
let drained = captured.drain();
assert_eq!(
drained,
vec!["[warn] first".to_string(), "[warn] second".to_string()]
);
assert!(captured.drain().is_empty());
set_level(DEFAULT_LEVEL);
}
#[test]
fn captured_sink_evicts_oldest_when_full_and_counts_drops() {
let captured = CapturedSink::with_capacity(2);
captured.emit(Level::Warn, "first");
captured.emit(Level::Warn, "second");
captured.emit(Level::Warn, "third");
let detailed = captured.drain_detailed();
let texts: Vec<String> = detailed.entries.iter().map(|e| e.text.clone()).collect();
assert_eq!(
texts,
vec!["[warn] second".to_string(), "[warn] third".to_string()],
"drop-oldest must evict 'first', keep latest two",
);
assert_eq!(detailed.dropped, 1, "exactly one entry evicted");
}
#[test]
fn captured_sink_legacy_drain_also_resets_dropped_counter() {
let captured = CapturedSink::with_capacity(1);
captured.emit(Level::Warn, "a");
captured.emit(Level::Warn, "b"); let _ = captured.drain();
captured.emit(Level::Warn, "c");
let detailed = captured.drain_detailed();
assert_eq!(detailed.dropped, 0);
}
#[test]
fn captured_sink_dropped_counter_resets_on_detailed_drain() {
let captured = CapturedSink::with_capacity(1);
captured.emit(Level::Warn, "a");
captured.emit(Level::Warn, "b"); let first = captured.drain_detailed();
assert_eq!(first.dropped, 1);
captured.emit(Level::Warn, "c");
let second = captured.drain_detailed();
assert_eq!(second.dropped, 0, "counter must reset between drains");
}
#[test]
fn captured_sink_with_capacity_clamps_zero_to_one() {
let captured = CapturedSink::with_capacity(0);
captured.emit(Level::Warn, "only");
let detailed = captured.drain_detailed();
assert_eq!(detailed.entries.len(), 1);
assert_eq!(detailed.entries[0].text, "[warn] only");
}
#[test]
fn captured_sink_records_emit_timestamp() {
let captured = CapturedSink::default();
captured.emit(Level::Warn, "first");
std::thread::sleep(std::time::Duration::from_millis(2));
captured.emit(Level::Warn, "second");
let detailed = captured.drain_detailed();
assert_eq!(detailed.entries.len(), 2);
assert!(
detailed.entries[1].at > detailed.entries[0].at,
"second emit must have a later timestamp than the first",
);
}
#[test]
fn captured_sink_last_drain_seq_marks_in_frame_emits_as_seen() {
let captured = CapturedSink::default();
assert_eq!(captured.last_drain_seq(), 0);
captured.emit(Level::Warn, "in-frame-a");
captured.emit(Level::Warn, "in-frame-b");
let _ = captured.drain_detailed();
assert_eq!(captured.last_drain_seq(), 2, "drain marks both as seen");
assert_eq!(captured.emit_seq(), 2);
captured.emit(Level::Warn, "background");
assert_eq!(
captured.last_drain_seq(),
2,
"post-drain emit must NOT advance last_drain_seq",
);
assert_eq!(captured.emit_seq(), 3, "but emit_seq advances");
}
#[test]
fn captured_sink_emit_seq_is_monotonic_across_drains() {
let captured = CapturedSink::default();
assert_eq!(captured.emit_seq(), 0);
captured.emit(Level::Warn, "a");
captured.emit(Level::Warn, "b");
assert_eq!(captured.emit_seq(), 2);
let _ = captured.drain_detailed();
assert_eq!(captured.emit_seq(), 2, "drain must not reset emit_seq");
captured.emit(Level::Warn, "c");
assert_eq!(captured.emit_seq(), 3);
let _ = captured.drain();
assert_eq!(
captured.emit_seq(),
3,
"legacy drain must not reset emit_seq"
);
}
#[test]
fn sink_guard_restores_prior_sink_on_drop_lifo_three_deep() {
let _g = lock();
set_level(Level::Warn);
let outer = Arc::new(CapturedSink::default());
let _outer_g = SinkGuard::install(outer.clone());
{
let middle = Arc::new(CapturedSink::default());
let _middle_g = SinkGuard::install(middle.clone());
{
let inner = Arc::new(CapturedSink::default());
let _inner_g = SinkGuard::install(inner.clone());
emit(Level::Warn, "inner");
assert_eq!(inner.snapshot(), vec!["[warn] inner".to_string()]);
assert!(middle.snapshot().is_empty());
assert!(outer.snapshot().is_empty());
}
emit(Level::Warn, "middle");
assert_eq!(middle.snapshot(), vec!["[warn] middle".to_string()]);
assert!(outer.snapshot().is_empty());
}
emit(Level::Warn, "outer");
assert_eq!(outer.snapshot(), vec!["[warn] outer".to_string()]);
set_level(DEFAULT_LEVEL);
}
#[test]
fn install_sink_blocks_until_in_flight_emit_completes_and_emission_lands_on_prior_sink() {
run_install_race_test(EmitKind::Warn);
}
#[test]
fn install_sink_blocks_until_in_flight_emit_error_completes() {
run_install_race_test(EmitKind::Error);
}
#[derive(Clone, Copy)]
enum EmitKind {
Warn,
Error,
}
fn run_install_race_test(kind: EmitKind) {
use std::sync::atomic::AtomicBool;
use std::sync::Barrier;
use std::time::Duration;
struct BarrierSink {
inside: Arc<Barrier>,
release: Arc<Barrier>,
inner: Arc<CapturedSink>,
}
impl LogSink for BarrierSink {
fn emit(&self, lvl: Level, msg: &str) {
self.inside.wait();
self.release.wait();
self.inner.emit(lvl, msg);
}
fn emit_error(&self, msg: &str) {
self.inside.wait();
self.release.wait();
self.inner.emit_error(msg);
}
}
let _g = lock();
match kind {
EmitKind::Warn => set_level(Level::Warn),
EmitKind::Error => set_level(Level::Off),
}
let inside = Arc::new(Barrier::new(2));
let release = Arc::new(Barrier::new(2));
let prior_inner = Arc::new(CapturedSink::default());
let prior_sink: Arc<dyn LogSink> = Arc::new(BarrierSink {
inside: inside.clone(),
release: release.clone(),
inner: prior_inner.clone(),
});
let _restore = SinkGuard::install(prior_sink);
let emit_handle = std::thread::spawn(move || match kind {
EmitKind::Warn => emit(Level::Warn, "racy"),
EmitKind::Error => emit_error("racy"),
});
inside.wait();
let new_captured = Arc::new(CapturedSink::default());
let new_sink: Arc<dyn LogSink> = new_captured.clone();
let install_done = Arc::new(AtomicBool::new(false));
let install_done_clone = install_done.clone();
let install_handle = std::thread::spawn(move || {
let prior = install_sink(new_sink);
install_done_clone.store(true, Ordering::Release);
prior
});
struct ReleaseOnDrop {
release: Arc<Barrier>,
fired: std::cell::Cell<bool>,
}
impl ReleaseOnDrop {
fn fire(&self) {
if !self.fired.replace(true) {
self.release.wait();
}
}
}
impl Drop for ReleaseOnDrop {
fn drop(&mut self) {
self.fire();
}
}
let release_guard = ReleaseOnDrop {
release: release.clone(),
fired: std::cell::Cell::new(false),
};
std::thread::sleep(Duration::from_millis(75));
assert!(
!install_done.load(Ordering::Acquire),
"install_sink must block while {kind:?} emit is in progress",
kind = match kind {
EmitKind::Warn => "warn",
EmitKind::Error => "error",
},
);
release_guard.fire();
emit_handle.join().expect("emit thread");
let from_install = install_handle.join().expect("install thread");
assert!(install_done.load(Ordering::Acquire));
let expected = match kind {
EmitKind::Warn => "[warn] racy",
EmitKind::Error => "[error] racy",
};
assert_eq!(
prior_inner.snapshot(),
vec![expected.to_string()],
"emission must land on the prior sink",
);
assert!(
new_captured.snapshot().is_empty(),
"post-install sink must not see an emission issued before the swap, got {:?}",
new_captured.snapshot(),
);
drop(from_install);
set_level(DEFAULT_LEVEL);
}
#[test]
fn install_sink_returns_prior_for_manual_restore() {
let _g = lock();
set_level(Level::Warn);
let captured = Arc::new(CapturedSink::default());
let prior = install_sink(captured.clone());
emit(Level::Warn, "captured");
assert_eq!(captured.snapshot(), vec!["[warn] captured".to_string()]);
let _ = install_sink(prior);
}
#[test]
fn test_capture_warns_returns_function_result_and_captured_emissions() {
let (result, captured) = _test_capture_warns(|| {
emit(Level::Warn, "first");
emit(Level::Warn, "second");
42
});
assert_eq!(result, 42);
assert_eq!(
captured,
vec!["[warn] first".to_string(), "[warn] second".to_string()]
);
}
#[test]
fn test_capture_warns_captures_emit_error_regardless_of_level() {
let (_, captured) = _test_capture_warns(|| {
emit_error("structural failure");
});
assert_eq!(captured, vec!["[error] structural failure".to_string()]);
}
#[test]
fn emit_routes_to_thread_local_sink_even_when_global_level_is_off() {
let _g = lock();
set_level(Level::Off);
let sink = Arc::new(CapturedSink::default());
let prior = THREAD_SINK.with(|cell| cell.replace(Some(sink.clone())));
let _restore = ThreadSinkGuard { prior };
emit(Level::Warn, "still captured");
assert_eq!(sink.drain(), vec!["[warn] still captured".to_string()]);
set_level(DEFAULT_LEVEL);
}
#[test]
fn test_capture_warns_subsequent_call_starts_empty() {
let _ = _test_capture_warns(|| emit(Level::Warn, "first"));
let (_, second) = _test_capture_warns(|| {});
assert!(
second.is_empty(),
"second call must start with no captured entries, got {second:?}"
);
}
#[test]
fn concrete_sink_types_remain_thread_safe() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<StderrSink>();
assert_send_sync::<CapturedSink>();
}
}