use std::io::{Error, Write};
use std::thread;
use log::Record;
use termcolor::{BufferedStandardStream, WriteColor};
use crate::config::{TimeFormat, Token};
use crate::Config;
#[inline(always)]
pub fn try_log<W>(config: &Config, record: &Record<'_>, write: &mut W) -> Result<(), Error>
where
W: Write + Sized,
{
for token in &config.tokens[record.level() as usize] {
match token {
Token::Time => write_time(write, config, record)?,
Token::Level => write!(write, "{}", record.level())?,
Token::Thread => write!(write, "{:?}", thread::current().id())?,
Token::Module => write!(write, "{}", record.module_path().unwrap_or("<unknown>"))?,
Token::File => write!(write, "{}", record.file().unwrap_or("<unknown>"))?,
Token::FileName => write_file_name(record, write)?,
Token::Line => write!(write, "{}", record.line().unwrap_or(0))?,
Token::Text(text) => write!(write, "{text}")?,
Token::Message => write_args(record, write)?,
Token::ColorStart | Token::ColorEnd => {}
}
}
writeln!(write)?;
Ok(())
}
#[inline(always)]
pub fn try_log_term(config: &Config, record: &Record<'_>, write: &mut BufferedStandardStream) -> Result<(), Error> {
for token in &config.tokens[record.level() as usize] {
match token {
Token::Time => write_time(write, config, record)?,
Token::Level => write!(write, "{}", record.level())?,
Token::Thread => write!(write, "{:?}", thread::current().id())?,
Token::Module => write!(write, "{}", record.module_path().unwrap_or("<unknown>"))?,
Token::File => write!(write, "{}", record.file().unwrap_or("<unknown>"))?,
Token::FileName => write_file_name(record, write)?,
Token::Line => write!(write, "{}", record.line().unwrap_or(0))?,
Token::Text(text) => write!(write, "{text}")?,
Token::Message => write_args(record, write)?,
Token::ColorStart => set_color(write, config, record, true)?,
Token::ColorEnd => set_color(write, config, record, false)?,
}
}
writeln!(write)?;
write.flush()
}
#[inline(always)]
pub fn write_file_name<W>(record: &Record<'_>, write: &mut W) -> Result<(), Error>
where
W: Write + Sized,
{
match record.file() {
Some(file) => {
let mut file = file.rsplitn(2, '/');
if let Some(file) = file.next() {
write!(write, "{file}")?;
} else {
write!(write, "<unknown>")?;
}
}
None => write!(write, "<unknown>")?,
}
Ok(())
}
#[inline(always)]
pub fn write_time<W>(write: &mut W, config: &Config, record: &Record<'_>) -> Result<(), Error>
where
W: Write + Sized,
{
use time::error::Format;
use time::format_description::well_known::*;
let time = time::OffsetDateTime::now_utc().to_offset(config.time_offset);
let res = match config.time_format[record.level() as usize] {
TimeFormat::Rfc2822 => time.format_into(write, &Rfc2822),
TimeFormat::Rfc3339 => time.format_into(write, &Rfc3339),
TimeFormat::Custom(format) => time.format_into(write, &format),
};
match res {
Err(Format::StdIo(err)) => return Err(err),
Err(err) => panic!("Invalid time format: {err}"),
_ => {}
};
write!(write, "")?;
Ok(())
}
#[inline(always)]
pub fn set_color(write: &mut BufferedStandardStream, config: &Config, record: &Record<'_>, color_start: bool) -> Result<(), Error> {
if config.enabled_colors {
if color_start {
let color = &config.compiled_colors[record.level() as usize];
write.set_color(color)?;
} else {
write.reset()?;
}
}
Ok(())
}
#[inline(always)]
pub fn write_args<W>(record: &Record<'_>, write: &mut W) -> Result<(), Error>
where
W: Write + Sized,
{
write!(write, "{}", record.args())?;
Ok(())
}
#[cfg(test)]
mod tests {
use log::Level;
use termcolor::ColorChoice;
use crate::loggers::term_logger::OutputStreams;
use crate::ConfigBuilder;
use super::*;
#[test]
fn test_tokens_output() {
let mut config = ConfigBuilder::new().build();
let record = Record::builder().build();
let i = vec![
Token::File,
Token::Level,
Token::Module,
Token::Line,
Token::FileName,
Token::ColorEnd,
Token::ColorStart,
Token::Message,
Token::Text("test"),
];
config.tokens = [i.clone(), i.clone(), i.clone(), i.clone(), i.clone(), i.clone()];
let mut res_vec = Vec::new();
let res = try_log(&config, &record, &mut res_vec);
assert!(res.is_ok());
assert_eq!(String::from_utf8(res_vec).unwrap(), "<unknown>INFO<unknown>0<unknown>test\n".to_string());
let mut config = ConfigBuilder::new().build();
let record = Record::builder().build();
let i = vec![Token::Time];
config.tokens = [i.clone(), i.clone(), i.clone(), i.clone(), i.clone(), i.clone()];
let mut res_vec = Vec::new();
let res = try_log(&config, &record, &mut res_vec);
assert!(res.is_ok());
assert_eq!(String::from_utf8(res_vec).unwrap().len(), "20:24:46\n".len());
}
#[test]
fn test_colours() {
for level_filter in &[Level::Info, Level::Warn, Level::Error, Level::Debug, Level::Trace] {
test_colour_level(*level_filter);
}
}
fn test_colour_level(level: Level) {
let mut config = ConfigBuilder::new().set_enabled_colours(true).build();
let record = Record::builder().level(level).build();
let i = vec![Token::Text("RAR"), Token::Level, Token::Text("RAR")];
config.tokens = [i.clone(), i.clone(), i.clone(), i.clone(), i.clone(), i.clone()];
let mut streams = OutputStreams {
err: BufferedStandardStream::stderr(ColorChoice::Always),
out: BufferedStandardStream::stdout(ColorChoice::Always),
};
let res = try_log_term(&config, &record, &mut streams.out);
assert!(res.is_ok());
}
}