use crate::{
filters::Filters,
options::{Options, StyleConfig, TimeConfig},
};
use termcolor::ColorSpec;
pub struct TermLogger {
options: Options,
filters: Filters,
color_choice: termcolor::ColorChoice,
}
impl Default for TermLogger {
fn default() -> Self {
Self {
options: Options::default(),
filters: Filters::from_env(),
color_choice: determine_color_choice(),
}
}
}
impl TermLogger {
pub fn init(self) -> Result<(), crate::Error> {
crate::init(self)
}
pub fn new(options: impl Into<Options>) -> Result<Self, crate::Error> {
let options = options.into();
Ok(Self {
options,
filters: Filters::from_env(),
color_choice: determine_color_choice(),
})
}
fn print(&self, record: &log::Record<'_>) {
let buf_writer = termcolor::BufferWriter::stdout(self.color_choice);
let mut buffer = buf_writer.buffer();
self.render_level(&record, &mut buffer);
self.render_timestamp(&record, &mut buffer);
self.render_target(&record, &mut buffer);
self.render_payload(&record, &mut buffer);
let _ = buf_writer.print(&buffer);
}
fn render_level(
&self,
record: &log::Record<'_>,
buffer: &mut (impl std::io::Write + termcolor::WriteColor),
) {
let color = &self.options.color;
let level_color = match record.level() {
log::Level::Error => color.level_error,
log::Level::Warn => color.level_warn,
log::Level::Info => color.level_info,
log::Level::Debug => color.level_debug,
log::Level::Trace => color.level_trace,
};
let _ = buffer.set_color(ColorSpec::new().set_fg(level_color.into()));
let _ = write!(buffer, "{:<5}", record.level());
let _ = buffer.reset();
}
fn render_timestamp(
&self,
_record: &log::Record<'_>,
buffer: &mut (impl std::io::Write + termcolor::WriteColor),
) {
let Options { color, time, .. } = &self.options;
match time {
TimeConfig::None => {}
TimeConfig::Unix => {
let elapsed = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("time should not go backwards");
let _ = buffer.set_color(ColorSpec::new().set_fg(color.timestamp.into()));
let _ = write!(buffer, " {:04}", elapsed.as_secs());
let _ = buffer.reset();
}
TimeConfig::Relative(start) => {
let elapsed = start.elapsed();
let _ = buffer.set_color(ColorSpec::new().set_fg(color.timestamp.into()));
let _ = write!(
buffer,
" {:04}.{:09}s",
elapsed.as_secs(),
elapsed.subsec_nanos()
);
let _ = buffer.reset();
}
TimeConfig::Timing(inner) => {
let inner = &mut *inner.lock().unwrap();
if let Some(start) = &*inner {
let elapsed = start.elapsed();
let _ = buffer.set_color(ColorSpec::new().set_fg(color.timestamp.into()));
let _ = write!(
buffer,
" {:04}.{:09}s",
elapsed.as_secs(),
elapsed.subsec_nanos()
);
let _ = buffer.reset();
} else {
let _ = buffer.set_color(ColorSpec::new().set_fg(color.timestamp.into()));
let _ = write!(buffer, " {:04}.{:09}s", 0, 0);
let _ = buffer.reset();
}
inner.replace(std::time::Instant::now());
}
#[cfg(feature = "time")]
TimeConfig::DateTime(format) => {
if let Ok(now) = time::OffsetDateTime::now_utc().format(&&format) {
let _ = buffer.set_color(ColorSpec::new().set_fg(color.timestamp.into()));
let _ = write!(buffer, " {}", now);
let _ = buffer.reset();
}
}
}
}
fn render_target(
&self,
record: &log::Record<'_>,
buffer: &mut (impl std::io::Write + termcolor::WriteColor),
) {
let color = &self.options.color;
let _ = write!(buffer, " [");
let _ = buffer.set_color(ColorSpec::new().set_fg(color.target.into()));
let _ = write!(buffer, "{}", record.target());
let _ = buffer.reset();
let _ = write!(buffer, "]");
}
fn render_payload(
&self,
record: &log::Record<'_>,
buffer: &mut (impl std::io::Write + termcolor::WriteColor),
) {
let Options { style, color, .. } = &self.options;
if let StyleConfig::MultiLine = style {
let _ = writeln!(buffer);
let _ = buffer.set_color(ColorSpec::new().set_fg(color.continuation.into()));
let _ = write!(buffer, "⤷");
let _ = buffer.reset();
}
let _ = buffer.set_color(ColorSpec::new().set_fg(color.message.into()));
let _ = write!(buffer, " {}", record.args());
let _ = buffer.reset();
let _ = writeln!(buffer);
}
}
impl log::Log for TermLogger {
#[inline]
fn enabled(&self, metadata: &log::Metadata<'_>) -> bool {
self.filters.is_enabled(metadata)
}
#[inline]
fn log(&self, record: &log::Record<'_>) {
if self.enabled(record.metadata()) {
self.print(record);
}
}
#[inline]
fn flush(&self) {}
}
fn determine_color_choice() -> termcolor::ColorChoice {
if std::env::var("NO_COLOR").is_ok() {
termcolor::ColorChoice::Never
} else {
termcolor::ColorChoice::Auto
}
}