use std::env::{self, VarError};
use std::io::{self, IsTerminal};
use tracing_subscriber::filter::{Directive, EnvFilter, LevelFilter};
use crate::{EventFormatter, FieldFormatter, TimeFormat};
#[derive(Debug, Clone, Copy)]
pub enum Output {
Stdout,
Stderr,
}
impl Default for Output {
fn default() -> Self {
Self::Stdout
}
}
impl Output {
fn is_terminal(&self) -> bool {
match self {
Output::Stdout => io::stdout().is_terminal(),
Output::Stderr => io::stderr().is_terminal(),
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum ColorMode {
Auto,
Always,
Never,
}
impl Default for ColorMode {
fn default() -> Self {
Self::Auto
}
}
impl ColorMode {
fn enable_for(&self, output: Output) -> bool {
match self {
Self::Auto => {
if env::var_os("NO_COLOR").map(|s| !s.is_empty()).unwrap_or(false) {
false
} else {
output.is_terminal()
}
}
Self::Always => true,
Self::Never => false,
}
}
}
#[derive(Debug, Clone)]
pub struct Config {
event_formatter: EventFormatter,
output: Output,
color: ColorMode,
default_directive: Directive,
}
impl Default for Config {
fn default() -> Self {
Self::new()
}
}
impl Config {
pub fn new() -> Self {
Self {
event_formatter: Default::default(),
output: Default::default(),
color: Default::default(),
default_directive: LevelFilter::INFO.into(),
}
}
pub fn with_output(self, output: Output) -> Self {
Self { output, ..self }
}
pub fn with_color(self, color: ColorMode) -> Self {
Self { color, ..self }
}
pub fn with_default(self, default: impl Into<Directive>) -> Self {
Self { default_directive: default.into(), ..self }
}
pub fn with_verbosity(self, verbosity: i32) -> Self {
let level = match verbosity.clamp(-3, 2) {
-3 => LevelFilter::OFF,
-2 => LevelFilter::ERROR,
-1 => LevelFilter::WARN,
0 => LevelFilter::INFO,
1 => LevelFilter::DEBUG,
2 => LevelFilter::TRACE,
_ => unreachable!(),
};
self.with_default(level)
}
pub fn with_timestamp(self, time_format: TimeFormat) -> Self {
Self { event_formatter: self.event_formatter.with_timestamp(time_format), ..self }
}
pub fn with_target(self, display_target: bool) -> Self {
Self { event_formatter: self.event_formatter.with_target(display_target), ..self }
}
pub fn with_scope(self, display_scope: bool) -> Self {
Self { event_formatter: self.event_formatter.with_scope(display_scope), ..self }
}
pub fn init(self) {
let builder = tracing_subscriber::fmt()
.with_env_filter(self.make_env_filter())
.with_ansi(self.color.enable_for(self.output))
.event_format(self.event_formatter)
.fmt_fields(FieldFormatter::new());
match self.output {
Output::Stdout => builder.with_writer(io::stdout).init(),
Output::Stderr => builder.with_writer(io::stderr).init(),
}
}
pub fn make_env_filter(&self) -> EnvFilter {
let env_str = match env::var("RUST_LOG") {
Ok(val) if val.is_empty() => None,
Ok(val) => Some(val),
Err(VarError::NotPresent) => None,
Err(VarError::NotUnicode(val)) => {
panic!("The RUST_LOG environment variable isn't valid unicode: {val:?}")
}
};
match &env_str {
Some(filter_str) => {
debug_assert!(!filter_str.is_empty());
EnvFilter::try_new(filter_str).unwrap_or_else(|err| {
panic!("Invalid RUST_LOG filter string '{filter_str}': {err}")
})
}
None => EnvFilter::default().add_directive(self.default_directive.clone()),
}
}
}