diff --git a/src/config.rs b/src/config.rs index 050dd90a..ab81450e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -7,6 +7,8 @@ use std::borrow::Cow; use termcolor::Color; pub use time::{format_description::FormatItem, macros::format_description, UtcOffset}; +use crate::format::*; + #[derive(Debug, Clone, Copy)] /// Padding to be used for logging the level pub enum LevelPadding { @@ -81,6 +83,7 @@ pub struct Config { pub(crate) location: LevelFilter, pub(crate) time_format: TimeFormat, pub(crate) time_offset: UtcOffset, + pub(crate) output_format: Format, pub(crate) filter_allow: Cow<'static, [Cow<'static, str>]>, pub(crate) filter_ignore: Cow<'static, [Cow<'static, str>]>, #[cfg(feature = "termcolor")] @@ -300,6 +303,31 @@ impl ConfigBuilder { self } + /// Sets the output format to a custom representation. + /// + /// # Usage + /// + /// ``` + /// use simplelog::ConfigBuilder; + /// use simplelog::FormatBuilder; + /// + /// let format = FormatBuilder::new() + /// .time() + /// .literal(" [") + /// .level() + /// .literal("] ") + /// .args() + /// .build(); + /// + /// let config = ConfigBuilder::new() + /// .set_output_format_custom(format) + /// .build(); + /// ``` + pub fn set_output_format_custom(&mut self, output_format: Format) -> &mut ConfigBuilder { + self.0.output_format = output_format; + self + } + /// Build new `Config` pub fn build(&mut self) -> Config { self.0.clone() @@ -326,6 +354,7 @@ impl Default for Config { location: LevelFilter::Trace, time_format: TimeFormat::Custom(format_description!("[hour]:[minute]:[second]")), time_offset: UtcOffset::UTC, + output_format: Default::default(), filter_allow: Cow::Borrowed(&[]), filter_ignore: Cow::Borrowed(&[]), write_log_enable_colors: false, diff --git a/src/format.rs b/src/format.rs new file mode 100644 index 00000000..0ee706c4 --- /dev/null +++ b/src/format.rs @@ -0,0 +1,382 @@ +use std::vec::Vec; + +use log::Level; +#[cfg(feature = "termcolor")] +use termcolor::Color; + +use crate::LevelFilter; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub(crate) enum FormatPartType { + Time, + Level, + Thread, + // ThreadId, + // ThreadName, + Target, + // File, + // Line, + Location, + ModulePath, + Args, + Literal(&'static str), +} + +#[derive(Clone, Debug)] +pub(crate) struct FormatPart { + pub(crate) part_type: FormatPartType, + pub(crate) wrap_space: bool, + pub(crate) level_filter: LevelFilter, + #[cfg(feature = "termcolor")] + pub(crate) level_color: [Option; 6], +} + +impl FormatPart { + pub fn new(type_: FormatPartType) -> Self { + Self { + part_type: type_, + wrap_space: false, + // TODO: Confirm the default level. + level_filter: LevelFilter::Trace, + #[cfg(feature = "termcolor")] + level_color: [None; 6], + } + } +} + +/// output format. +#[derive(Clone, Debug)] +pub struct Format { + pub(crate) format_parts: Vec, +} + +impl Format { + fn new() -> Self { + Self { + format_parts: Vec::new(), + } + } +} + +impl Default for Format { + fn default() -> Self { + // try to be consistent with the original format. + FormatBuilder::new() + .begin_time() + .filter_level(LevelFilter::Error) + .wrap_space(true) + .end() + .literal("[") + .level() + .literal("]") + .begin_thread() + .filter_level(LevelFilter::Debug) + .wrap_space(true) + .end() + .begin_target() + .filter_level(LevelFilter::Debug) + .end() + .begin_literal(": ") + .filter_level(LevelFilter::Debug) + .end() + .literal("[") + .begin_location() + .filter_level(LevelFilter::Trace) + .end() + .literal("]") + .begin_args() + .wrap_space(true) + .end() + .build() + } +} + +/// output format builder. +pub struct FormatBuilder { + pub(crate) format: Format, + current_part: Option, +} + +#[allow(dead_code)] +impl FormatBuilder { + /// new builder. + pub fn new() -> Self { + Self { + format: Format::new(), + current_part: None, + } + } + + fn push_part(&mut self, part: FormatPart) { + self.format.format_parts.push(part) + } + + /// Set whether the part wraps spaces. + /// + /// Can only be used between begin_xxx() and end(). + /// + /// # Usage + /// + /// ``` + /// use simplelog::FormatBuilder; + /// + /// let format = FormatBuilder::new() + /// .begin_time() + /// .wrap_space(true) + /// .end() + /// .args() + /// .build(); + /// ``` + pub fn wrap_space(&mut self, wrap_space: bool) -> &mut Self { + if let Some(ref mut part) = self.current_part.as_mut() { + part.wrap_space = wrap_space; + } else { + // TODO: panic ? + } + self + } + + /// Set the filter level for part. + /// + /// Can only be used between begin_xxx() and end(). + /// + /// # Usage + /// + /// ``` + /// use simplelog::FormatBuilder; + /// use simplelog::LevelFilter; + /// + /// let format = FormatBuilder::new() + /// .begin_time() + /// .filter_level(LevelFilter::Info) + /// .end() + /// .args() + /// .build(); + /// ``` + pub fn filter_level(&mut self, level_filter: LevelFilter) -> &mut Self { + if let Some(ref mut part) = self.current_part.as_mut() { + part.level_filter = level_filter; + } else { + // TODO: panic ? + } + self + } + + /// Set the color of the part at level + /// + /// Can only be used between begin_xxx() and end(). + /// + /// # Usage + /// + /// ``` + /// use simplelog::FormatBuilder; + /// use log::Level; + /// use termcolor::Color; + /// + /// let format = FormatBuilder::new() + /// .begin_time() + /// .level_color(Level::Error, Color::Red) + /// .end() + /// .args() + /// .build(); + /// ``` + #[cfg(feature = "termcolor")] + pub fn level_color(&mut self, level: Level, color: Color) -> &mut Self { + if let Some(part) = self.current_part.as_mut() { + debug_assert!((level as usize) < part.level_color.len()); + part.level_color[level as usize] = Some(color); + } else { + // TODO: panic ? + } + self + } + + /// begin time part. + pub fn begin_time(&mut self) -> &mut Self { + self.current_part = Some(FormatPart::new(FormatPartType::Time)); + self + } + + /// begin level part. + pub fn begin_level(&mut self) -> &mut Self { + self.current_part = Some(FormatPart::new(FormatPartType::Level)); + self + } + + /// begin thread part. + pub fn begin_thread(&mut self) -> &mut Self { + self.current_part = Some(FormatPart::new(FormatPartType::Thread)); + self + } + + /// begin target part. + pub fn begin_target(&mut self) -> &mut Self { + self.current_part = Some(FormatPart::new(FormatPartType::Target)); + self + } + + /// begin location part. + pub fn begin_location(&mut self) -> &mut Self { + self.current_part = Some(FormatPart::new(FormatPartType::Location)); + self + } + + /// begin module path part. + pub fn begin_module_path(&mut self) -> &mut Self { + self.current_part = Some(FormatPart::new(FormatPartType::ModulePath)); + self + } + + /// begin args part. + pub fn begin_args(&mut self) -> &mut Self { + self.current_part = Some(FormatPart::new(FormatPartType::Args)); + self + } + + /// begin literal part. + pub fn begin_literal(&mut self, literal: &'static str) -> &mut Self { + self.current_part = Some(FormatPart::new(FormatPartType::Literal(literal))); + self + } + + /// end part. + pub fn end(&mut self) -> &mut Self { + if let Some(part) = self.current_part.take() { + self.push_part(part) + } else { + // TODO: panic ? + } + self + } + + /// add time part. + /// + /// Equivalent to: + /// ``` + /// # use simplelog::FormatBuilder; + /// # let mut builder = FormatBuilder::new(); + /// builder.begin_time().end(); + /// ``` + pub fn time(&mut self) -> &mut Self { + self.begin_time().end() + } + + /// add level part. + /// + /// Equivalent to: + /// ``` + /// # use simplelog::FormatBuilder; + /// # let mut builder = FormatBuilder::new(); + /// #[cfg(not(feature = "termcolor"))] + /// { + /// builder.begin_level().end(); + /// } + /// #[cfg(feature = "termcolor")] + /// { + /// use log::Level; + /// use termcolor::Color; + /// + /// builder.begin_level() + /// .level_color(Level::Error, Color::Red) + /// .level_color(Level::Warn, Color::Yellow) + /// .level_color(Level::Info, Color::Blue) + /// .level_color(Level::Debug, Color::Cyan) + /// .level_color(Level::Trace, Color::White) + /// .end(); + /// } + /// ``` + pub fn level(&mut self) -> &mut Self { + #[cfg(feature = "termcolor")] + { + self.begin_level() + .level_color(Level::Error, Color::Red) + .level_color(Level::Warn, Color::Yellow) + .level_color(Level::Info, Color::Blue) + .level_color(Level::Debug, Color::Cyan) + .level_color(Level::Trace, Color::White) + .end() + } + #[cfg(not(feature = "termcolor"))] + { + self.begin_level().end() + } + } + + /// add thread part. + /// + /// Equivalent to: + /// ``` + /// # use simplelog::FormatBuilder; + /// # let mut builder = FormatBuilder::new(); + /// builder.begin_thread().end(); + /// ``` + pub fn thread(&mut self) -> &mut Self { + self.begin_thread().end() + } + + /// add target part. + /// + /// Equivalent to: + /// ``` + /// # use simplelog::FormatBuilder; + /// # let mut builder = FormatBuilder::new(); + /// builder.begin_target().end(); + /// ``` + pub fn target(&mut self) -> &mut Self { + self.begin_target().end() + } + + /// add location part. + /// + /// Equivalent to: + /// ``` + /// # use simplelog::FormatBuilder; + /// # let mut builder = FormatBuilder::new(); + /// builder.begin_location().end(); + /// ``` + pub fn location(&mut self) -> &mut Self { + self.begin_location().end() + } + + /// add module path part. + /// + /// Equivalent to: + /// ``` + /// # use simplelog::FormatBuilder; + /// # let mut builder = FormatBuilder::new(); + /// builder.begin_module_path().end(); + /// ``` + pub fn module_path(&mut self) -> &mut Self { + self.begin_module_path().end() + } + + /// add args part. + /// + /// Equivalent to: + /// ``` + /// # use simplelog::FormatBuilder; + /// # let mut builder = FormatBuilder::new(); + /// builder.begin_args().end(); + /// ``` + pub fn args(&mut self) -> &mut Self { + self.begin_args().end() + } + + /// add literal part. + /// + /// Equivalent to: + /// ``` + /// # use simplelog::FormatBuilder; + /// # let mut builder = FormatBuilder::new(); + /// # let literal = "literal"; + /// builder.begin_literal(literal).end(); + /// ``` + pub fn literal(&mut self, literal: &'static str) -> &mut Self { + self.begin_literal(literal).end() + } + + /// build. + pub fn build(&self) -> Format { + self.format.clone() + } +} diff --git a/src/lib.rs b/src/lib.rs index 51c38787..2540c64a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,12 +22,15 @@ #![deny(missing_docs, rust_2018_idioms)] mod config; +mod format; mod loggers; pub use self::config::{ format_description, Config, ConfigBuilder, FormatItem, LevelPadding, TargetPadding, ThreadLogMode, ThreadPadding, }; +pub use self::format::Format; +pub use self::format::FormatBuilder; #[cfg(feature = "test")] pub use self::loggers::TestLogger; pub use self::loggers::{CombinedLogger, SimpleLogger, WriteLogger}; diff --git a/src/loggers/logging.rs b/src/loggers/logging.rs index a09bff3d..a4108951 100644 --- a/src/loggers/logging.rs +++ b/src/loggers/logging.rs @@ -1,6 +1,6 @@ use crate::config::{TargetPadding, TimeFormat}; use crate::{Config, LevelPadding, ThreadLogMode, ThreadPadding}; -use log::{LevelFilter, Record}; +use log::{Level, LevelFilter, Record}; use std::io::{Error, Write}; use std::thread; #[cfg(all(feature = "termcolor", feature = "ansi_term"))] @@ -22,46 +22,106 @@ pub fn termcolor_to_ansiterm(color: &Color) -> Option { } #[inline(always)] -pub fn try_log(config: &Config, record: &Record<'_>, write: &mut W) -> Result<(), Error> +pub(crate) fn try_log( + config: &Config, + record: &Record<'_>, + write: &mut W, + mut set_color: SF, + mut reset_color: RF, +) -> Result<(), Error> where W: Write + Sized, + SF: FnMut(&mut W, Level, &FormatPart) -> Result<(), Error>, + RF: FnMut(&mut W, Level, &FormatPart) -> Result<(), Error>, { if should_skip(config, record) { return Ok(()); } - if config.time <= record.level() && config.time != LevelFilter::Off { - write_time(write, config)?; - } + let (mut need_space, parts) = if config.output_format.format_parts.len() >= 2 { + let part = &config.output_format.format_parts[0]; - if config.level <= record.level() && config.level != LevelFilter::Off { - write_level(record, write, config)?; - } + if record.level() <= part.level_filter && part.level_filter != LevelFilter::Off { + write_part( + record, + write, + config, + &part, + &mut set_color, + &mut reset_color, + )?; + (part.wrap_space, &config.output_format.format_parts[1..]) + } else { + (false, &config.output_format.format_parts[1..]) + } + } else { + (false, &config.output_format.format_parts[..]) + }; - if config.thread <= record.level() && config.thread != LevelFilter::Off { - match config.thread_log_mode { - ThreadLogMode::IDs => { - write_thread_id(write, config)?; - } - ThreadLogMode::Names | ThreadLogMode::Both => { - write_thread_name(write, config)?; - } + for part in parts { + if record.level() > part.level_filter || part.level_filter == LevelFilter::Off { + continue; } - } - if config.target <= record.level() && config.target != LevelFilter::Off { - write_target(record, write, config)?; - } + if part.wrap_space || need_space { + write!(write, " ")?; + } + need_space = part.wrap_space; - if config.location <= record.level() && config.location != LevelFilter::Off { - write_location(record, write)?; + write_part( + record, + write, + config, + part, + &mut set_color, + &mut reset_color, + )?; } - write_args(record, write) + Ok(()) +} + +use crate::format::FormatPart; + +#[inline(always)] +fn write_part( + record: &Record<'_>, + write: &mut W, + config: &Config, + part: &FormatPart, + mut set_color: SF, + mut reset_color: RF, +) -> Result<(), Error> +where + W: Write + Sized, + SF: FnMut(&mut W, Level, &FormatPart) -> Result<(), Error>, + RF: FnMut(&mut W, Level, &FormatPart) -> Result<(), Error>, +{ + use crate::format::FormatPartType as FP; + + set_color(write, record.level(), part)?; + + let res = match part.part_type { + FP::Time => write_time(write, config), + FP::Level => write_level(record, write, config), + FP::Thread => match config.thread_log_mode { + ThreadLogMode::IDs => write_thread_id(write, config), + ThreadLogMode::Names | ThreadLogMode::Both => write_thread_name(write, config), + }, + FP::Target => write_target(record, write, config), + FP::Location => write_location(record, write), + FP::ModulePath => write_module_path(record, write), + FP::Args => write_args(record, write), + FP::Literal(literal) => write!(write, "{}", literal), + }; + + reset_color(write, record.level(), part)?; + + res } #[inline(always)] -pub fn write_time(write: &mut W, config: &Config) -> Result<(), Error> +fn write_time(write: &mut W, config: &Config) -> Result<(), Error> where W: Write + Sized, { @@ -80,12 +140,12 @@ where _ => {} }; - write!(write, " ")?; + // write!(write, " ")?; Ok(()) } #[inline(always)] -pub fn write_level(record: &Record<'_>, write: &mut W, config: &Config) -> Result<(), Error> +fn write_level(record: &Record<'_>, write: &mut W, config: &Config) -> Result<(), Error> where W: Write + Sized, { @@ -102,48 +162,38 @@ where }; let level = match config.level_padding { - LevelPadding::Left => format!("[{: >5}]", record.level()), - LevelPadding::Right => format!("[{: <5}]", record.level()), - LevelPadding::Off => format!("[{}]", record.level()), + LevelPadding::Left => format!("{: >5}", record.level()), + LevelPadding::Right => format!("{: <5}", record.level()), + LevelPadding::Off => format!("{}", record.level()), }; #[cfg(all(feature = "termcolor", feature = "ansi_term"))] match color { - Some(c) => write!(write, "{} ", c.paint(level))?, - None => write!(write, "{} ", level)?, + Some(c) => write!(write, "{}", c.paint(level))?, + None => write!(write, "{}", level)?, }; #[cfg(not(feature = "ansi_term"))] - write!(write, "{} ", level)?; + write!(write, "{}", level)?; Ok(()) } #[inline(always)] -pub fn write_target(record: &Record<'_>, write: &mut W, config: &Config) -> Result<(), Error> +fn write_target(record: &Record<'_>, write: &mut W, config: &Config) -> Result<(), Error> where W: Write + Sized, { // dbg!(&config.target_padding); match config.target_padding { TargetPadding::Left(pad) => { - write!( - write, - "{target:>pad$}: ", - pad = pad, - target = record.target() - )?; + write!(write, "{target:>pad$}", pad = pad, target = record.target())?; } TargetPadding::Right(pad) => { - write!( - write, - "{target: { - write!(write, "{}: ", record.target())?; + write!(write, "{}", record.target())?; } } @@ -151,33 +201,33 @@ where } #[inline(always)] -pub fn write_location(record: &Record<'_>, write: &mut W) -> Result<(), Error> +fn write_location(record: &Record<'_>, write: &mut W) -> Result<(), Error> where W: Write + Sized, { let file = record.file().unwrap_or(""); if let Some(line) = record.line() { - write!(write, "[{}:{}] ", file, line)?; + write!(write, "{}:{}", file, line)?; } else { - write!(write, "[{}:] ", file)?; + write!(write, "{}:", file)?; } Ok(()) } -pub fn write_thread_name(write: &mut W, config: &Config) -> Result<(), Error> +fn write_thread_name(write: &mut W, config: &Config) -> Result<(), Error> where W: Write + Sized, { if let Some(name) = thread::current().name() { match config.thread_padding { ThreadPadding::Left { 0: qty } => { - write!(write, "({name:>0$}) ", qty, name = name)?; + write!(write, "({name:>0$})", qty, name = name)?; } ThreadPadding::Right { 0: qty } => { - write!(write, "({name:<0$}) ", qty, name = name)?; + write!(write, "({name:<0$})", qty, name = name)?; } ThreadPadding::Off => { - write!(write, "({}) ", name)?; + write!(write, "({})", name)?; } } } else if config.thread_log_mode == ThreadLogMode::Both { @@ -187,7 +237,7 @@ where Ok(()) } -pub fn write_thread_id(write: &mut W, config: &Config) -> Result<(), Error> +fn write_thread_id(write: &mut W, config: &Config) -> Result<(), Error> where W: Write + Sized, { @@ -196,20 +246,29 @@ where let id = id.replace(")", ""); match config.thread_padding { ThreadPadding::Left { 0: qty } => { - write!(write, "({id:>0$}) ", qty, id = id)?; + write!(write, "({id:>0$} ", qty, id = id)?; } ThreadPadding::Right { 0: qty } => { - write!(write, "({id:<0$}) ", qty, id = id)?; + write!(write, "({id:<0$})", qty, id = id)?; } ThreadPadding::Off => { - write!(write, "({}) ", id)?; + write!(write, "({})", id)?; } } Ok(()) } #[inline(always)] -pub fn write_args(record: &Record<'_>, write: &mut W) -> Result<(), Error> +fn write_module_path(record: &Record<'_>, write: &mut W) -> Result<(), Error> +where + W: Write + Sized, +{ + writeln!(write, "{}", record.module_path().unwrap_or(""))?; + Ok(()) +} + +#[inline(always)] +fn write_args(record: &Record<'_>, write: &mut W) -> Result<(), Error> where W: Write + Sized, { diff --git a/src/loggers/simplelog.rs b/src/loggers/simplelog.rs index 23490f91..76bbe64b 100644 --- a/src/loggers/simplelog.rs +++ b/src/loggers/simplelog.rs @@ -79,12 +79,24 @@ impl Log for SimpleLogger { Level::Error => { let stderr = stderr(); let mut stderr_lock = stderr.lock(); - let _ = try_log(&self.config, record, &mut stderr_lock); + let _ = try_log( + &self.config, + record, + &mut stderr_lock, + |_, _, _| Ok(()), + |_, _, _| Ok(()), + ); } _ => { let stdout = stdout(); let mut stdout_lock = stdout.lock(); - let _ = try_log(&self.config, record, &mut stdout_lock); + let _ = try_log( + &self.config, + record, + &mut stdout_lock, + |_, _, _| Ok(()), + |_, _, _| Ok(()), + ); } } } diff --git a/src/loggers/termlog.rs b/src/loggers/termlog.rs index 9bf13f9a..01c26f76 100644 --- a/src/loggers/termlog.rs +++ b/src/loggers/termlog.rs @@ -11,7 +11,7 @@ use termcolor::{ColorSpec, WriteColor}; use super::logging::*; -use crate::{Config, SharedLogger, ThreadLogMode}; +use crate::{Config, SharedLogger}; struct OutputStreams { err: BufferedStandardStream, @@ -126,60 +126,60 @@ impl TermLogger { }) } - fn try_log_term( - &self, - record: &Record<'_>, - term_lock: &mut BufferedStandardStream, - ) -> Result<(), Error> { - #[cfg(not(feature = "ansi_term"))] - let color = self.config.level_color[record.level() as usize]; + // fn try_log_term( + // &self, + // record: &Record<'_>, + // term_lock: &mut BufferedStandardStream, + // ) -> Result<(), Error> { + // #[cfg(not(feature = "ansi_term"))] + // let color = self.config.level_color[record.level() as usize]; - if self.config.time <= record.level() && self.config.time != LevelFilter::Off { - write_time(term_lock, &self.config)?; - } + // if self.config.time <= record.level() && self.config.time != LevelFilter::Off { + // write_time(term_lock, &self.config)?; + // } - if self.config.level <= record.level() && self.config.level != LevelFilter::Off { - #[cfg(not(feature = "ansi_term"))] - if !self.config.write_log_enable_colors { - term_lock.set_color(ColorSpec::new().set_fg(color))?; - } + // if self.config.level <= record.level() && self.config.level != LevelFilter::Off { + // #[cfg(not(feature = "ansi_term"))] + // if !self.config.write_log_enable_colors { + // term_lock.set_color(ColorSpec::new().set_fg(color))?; + // } - write_level(record, term_lock, &self.config)?; + // write_level(record, term_lock, &self.config)?; - #[cfg(not(feature = "ansi_term"))] - if !self.config.write_log_enable_colors { - term_lock.reset()?; - } - } + // #[cfg(not(feature = "ansi_term"))] + // if !self.config.write_log_enable_colors { + // term_lock.reset()?; + // } + // } - if self.config.thread <= record.level() && self.config.thread != LevelFilter::Off { - match self.config.thread_log_mode { - ThreadLogMode::IDs => { - write_thread_id(term_lock, &self.config)?; - } - ThreadLogMode::Names | ThreadLogMode::Both => { - write_thread_name(term_lock, &self.config)?; - } - } - } + // if self.config.thread <= record.level() && self.config.thread != LevelFilter::Off { + // match self.config.thread_log_mode { + // ThreadLogMode::IDs => { + // write_thread_id(term_lock, &self.config)?; + // } + // ThreadLogMode::Names | ThreadLogMode::Both => { + // write_thread_name(term_lock, &self.config)?; + // } + // } + // } - if self.config.target <= record.level() && self.config.target != LevelFilter::Off { - write_target(record, term_lock, &self.config)?; - } + // if self.config.target <= record.level() && self.config.target != LevelFilter::Off { + // write_target(record, term_lock, &self.config)?; + // } - if self.config.location <= record.level() && self.config.location != LevelFilter::Off { - write_location(record, term_lock)?; - } + // if self.config.location <= record.level() && self.config.location != LevelFilter::Off { + // write_location(record, term_lock)?; + // } - write_args(record, term_lock)?; + // write_args(record, term_lock)?; - // The log crate holds the logger as a `static mut`, which isn't dropped - // at program exit: https://doc.rust-lang.org/reference/items/static-items.html - // Sadly, this means we can't rely on the BufferedStandardStreams flushing - // themselves on the way out, so to avoid the Case of the Missing 8k, - // flush each entry. - term_lock.flush() - } + // // The log crate holds the logger as a `static mut`, which isn't dropped + // // at program exit: https://doc.rust-lang.org/reference/items/static-items.html + // // Sadly, this means we can't rely on the BufferedStandardStreams flushing + // // themselves on the way out, so to avoid the Case of the Missing 8k, + // // flush each entry. + // term_lock.flush() + // } fn try_log(&self, record: &Record<'_>) -> Result<(), Error> { if self.enabled(record.metadata()) { @@ -189,11 +189,52 @@ impl TermLogger { let mut streams = self.streams.lock().unwrap(); + use crate::format::FormatPart; + + // #[cfg(not(feature = "ansi_term"))] + // let color = self.config.level_color[record.level() as usize]; + let set_color = |term_lock: &mut BufferedStandardStream, + level: Level, + part: &FormatPart| + -> Result<(), Error> { + #[cfg(not(feature = "ansi_term"))] + if !self.config.write_log_enable_colors { + term_lock + .set_color(ColorSpec::new().set_fg(part.level_color[level as usize]))?; + } + Ok(()) + }; + let reset_color = |term_lock: &mut BufferedStandardStream, + _level: Level, + _part: &FormatPart| + -> Result<(), Error> { + #[cfg(not(feature = "ansi_term"))] + if !self.config.write_log_enable_colors { + term_lock.reset()?; + } + Ok(()) + }; + if record.level() == Level::Error { - self.try_log_term(record, &mut streams.err) + try_log( + &self.config, + record, + &mut streams.err, + set_color, + reset_color, + )?; + streams.err.flush()?; } else { - self.try_log_term(record, &mut streams.out) + try_log( + &self.config, + record, + &mut streams.out, + set_color, + reset_color, + )?; + streams.out.flush()?; } + Ok(()) } else { Ok(()) } diff --git a/src/loggers/writelog.rs b/src/loggers/writelog.rs index 48705e11..e5811d6e 100644 --- a/src/loggers/writelog.rs +++ b/src/loggers/writelog.rs @@ -74,7 +74,13 @@ impl Log for WriteLogger { fn log(&self, record: &Record<'_>) { if self.enabled(record.metadata()) { let mut write_lock = self.writable.lock().unwrap(); - let _ = try_log(&self.config, record, &mut *write_lock); + let _ = try_log( + &self.config, + record, + &mut *write_lock, + |_, _, _| Ok(()), + |_, _, _| Ok(()), + ); } }