use tracing_appender::non_blocking::WorkerGuard;
use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
#[derive(Debug, Clone)]
pub enum LogOutput {
Stdout,
File(std::path::PathBuf),
Both(std::path::PathBuf),
}
#[derive(Debug, Clone, Copy)]
pub enum LogFormat {
Pretty,
Compact,
}
#[derive(Debug, Clone)]
pub struct LogConfig {
pub level: String,
pub output: LogOutput,
pub format: LogFormat,
}
impl Default for LogConfig {
fn default() -> Self {
Self {
level: "info".to_string(),
output: LogOutput::Stdout,
format: LogFormat::Pretty,
}
}
}
impl LogConfig {
pub fn info() -> Self {
Self {
level: "info".to_string(),
..Default::default()
}
}
pub fn debug() -> Self {
Self {
level: "debug".to_string(),
..Default::default()
}
}
pub fn warn() -> Self {
Self {
level: "warn".to_string(),
..Default::default()
}
}
pub fn with_file<P: Into<std::path::PathBuf>>(mut self, path: P) -> Self {
self.output = LogOutput::File(path.into());
self
}
pub fn with_both<P: Into<std::path::PathBuf>>(mut self, path: P) -> Self {
self.output = LogOutput::Both(path.into());
self
}
pub fn with_format(mut self, format: LogFormat) -> Self {
self.format = format;
self
}
pub fn with_level<S: Into<String>>(mut self, level: S) -> Self {
self.level = level.into();
self
}
pub fn init(self) -> Option<WorkerGuard> {
let env_filter = EnvFilter::try_from_default_env()
.or_else(|_| EnvFilter::try_new(&self.level))
.expect("Invalid log level");
match self.output {
LogOutput::Stdout => {
match self.format {
LogFormat::Pretty => {
tracing_subscriber::registry()
.with(env_filter)
.with(fmt::layer().pretty())
.init();
}
LogFormat::Compact => {
tracing_subscriber::registry()
.with(env_filter)
.with(fmt::layer().compact())
.init();
}
}
None
}
LogOutput::File(path) => {
let file_appender = tracing_appender::rolling::daily(
path.parent().unwrap_or_else(|| std::path::Path::new(".")),
path.file_name()
.and_then(|n| n.to_str())
.unwrap_or("rustlite.log"),
);
let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);
match self.format {
LogFormat::Pretty => {
tracing_subscriber::registry()
.with(env_filter)
.with(fmt::layer().with_writer(non_blocking).pretty())
.init();
}
LogFormat::Compact => {
tracing_subscriber::registry()
.with(env_filter)
.with(fmt::layer().with_writer(non_blocking).compact())
.init();
}
}
Some(guard)
}
LogOutput::Both(path) => {
let file_appender = tracing_appender::rolling::daily(
path.parent().unwrap_or_else(|| std::path::Path::new(".")),
path.file_name()
.and_then(|n| n.to_str())
.unwrap_or("rustlite.log"),
);
let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);
tracing_subscriber::registry()
.with(env_filter)
.with(fmt::layer())
.with(fmt::layer().with_writer(non_blocking))
.init();
Some(guard)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_log_config_defaults() {
let config = LogConfig::default();
assert_eq!(config.level, "info");
}
#[test]
fn test_log_config_builders() {
let config = LogConfig::debug()
.with_file("/tmp/test.log")
.with_format(LogFormat::Compact);
assert_eq!(config.level, "debug");
assert!(matches!(config.output, LogOutput::File(_)));
assert!(matches!(config.format, LogFormat::Compact));
}
}