From 1293ea92bdbf3eb2086ec117e355714b6ee54ae9 Mon Sep 17 00:00:00 2001 From: A Farzat Date: Mon, 22 Jun 2026 19:56:40 +0300 Subject: Make Logger's output destination customizable This aids in testing and might allow logging to a file at a later point. --- src/logger.rs | 119 +++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 109 insertions(+), 10 deletions(-) (limited to 'src/logger.rs') diff --git a/src/logger.rs b/src/logger.rs index 0352d61..0a0bdc2 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -1,5 +1,10 @@ //! Minimal stderr logging utilities. +use std::{ + cell::RefCell, + io::{self, Write}, +}; + /// Logging verbosity level. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum Verbosity { @@ -12,36 +17,130 @@ pub enum Verbosity { } /// Lightweight logger used by renderers and generators. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct Logger { +#[derive(Debug)] +pub struct Logger { verbosity: Verbosity, + writer: RefCell, } -impl Logger { +impl Logger { /// Creates a logger with the given verbosity. pub fn new(verbosity: Verbosity) -> Self { - Self { verbosity } + Self { + verbosity, + writer: RefCell::new(io::stderr()), + } + } +} + +impl Logger { + pub fn with_writer(verbosity: Verbosity, writer: W) -> Self { + Self { + verbosity, + writer: RefCell::new(writer), + } } /// Emits a warning unless verbosity is [`Verbosity::Quiet`]. pub fn warn(&self, message: impl AsRef) { if self.verbosity != Verbosity::Quiet { - eprintln!("Warning: {}", message.as_ref()); + let _ = writeln!(self.writer.borrow_mut(), "Warning: {}", message.as_ref()); } } /// Emits an informational message only when verbosity is [`Verbosity::Verbose`]. pub fn info(&self, message: impl AsRef) { if self.verbosity == Verbosity::Verbose { - eprintln!("Info: {}", message.as_ref()); + let _ = writeln!(self.writer.borrow_mut(), "Info: {}", message.as_ref()); } } } -impl Default for Logger { - fn default() -> Self { - Self { - verbosity: Verbosity::Quiet, +#[cfg(test)] +mod tests { + use std::io::{self, Write}; + + use super::{Logger, Verbosity}; + + fn logged_output(logger: &Logger>) -> String { + String::from_utf8(logger.writer.borrow().clone()).unwrap() + } + + #[test] + fn quiet_suppresses_warnings() { + let logger = Logger::with_writer(Verbosity::Quiet, Vec::new()); + logger.warn("skipped file"); + assert_eq!(logged_output(&logger), ""); + } + + #[test] + fn quiet_suppresses_info_messages() { + let logger = Logger::with_writer(Verbosity::Quiet, Vec::new()); + logger.info("rendering file"); + assert_eq!(logged_output(&logger), ""); + } + + #[test] + fn normal_writes_warnings() { + let logger = Logger::with_writer(Verbosity::Normal, Vec::new()); + logger.warn("skipped file"); + assert_eq!(logged_output(&logger), "Warning: skipped file\n"); + } + + #[test] + fn normal_suppresses_info_messages() { + let logger = Logger::with_writer(Verbosity::Normal, Vec::new()); + logger.info("rendering file"); + assert_eq!(logged_output(&logger), ""); + } + + #[test] + fn verbose_writes_warnings() { + let logger = Logger::with_writer(Verbosity::Verbose, Vec::new()); + logger.warn("skipped file"); + assert_eq!(logged_output(&logger), "Warning: skipped file\n"); + } + + #[test] + fn verbose_writes_info_messages() { + let logger = Logger::with_writer(Verbosity::Verbose, Vec::new()); + logger.info("rendering file"); + assert_eq!(logged_output(&logger), "Info: rendering file\n"); + } + + #[test] + fn verbose_preserves_log_order() { + let logger = Logger::with_writer(Verbosity::Verbose, Vec::new()); + + logger.info("rendering a.rs"); + logger.warn("skipping duplicate file: a.rs"); + logger.info("rendering b.rs"); + + assert_eq!( + logged_output(&logger), + "Info: rendering a.rs\n\ + Warning: skipping duplicate file: a.rs\n\ + Info: rendering b.rs\n" + ); + } + + struct FailingWriter; + + impl Write for FailingWriter { + fn write(&mut self, _buf: &[u8]) -> io::Result { + Err(io::Error::other("intentional write failure")) } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } + } + + #[test] + fn logging_ignores_writer_errors() { + let logger = Logger::with_writer(Verbosity::Verbose, FailingWriter); + + logger.warn("this should not panic"); + logger.info("this should not panic"); } } -- cgit v1.3.1