use std::env;
use color_eyre::eyre::Result;
use eyre::WrapErr;
use tracing_appender::non_blocking::WorkerGuard;
use tracing_log::LogTracer;
use tracing_subscriber::filter::{Directive, EnvFilter};
use tracing_subscriber::fmt::Subscriber;
pub const ENV_KEY: &str = "NAMADA_LOG";
const COLOR_ENV_KEY: &str = "NAMADA_LOG_COLOR";
const FMT_ENV_KEY: &str = "NAMADA_LOG_FMT";
const DIR_ENV_KEY: &str = "NAMADA_LOG_DIR";
const ROLLING_ENV_KEY: &str = "NAMADA_LOG_ROLLING";
const LOG_FILE_NAME_PREFIX: &str = "namada.log";
#[derive(Clone, Debug)]
enum Fmt {
Full,
Json,
Pretty,
}
impl Default for Fmt {
fn default() -> Self {
Self::Full
}
}
pub fn init_from_env_or(
default: impl Into<Directive>,
) -> Result<Option<WorkerGuard>> {
let filter = filter_from_env_or(default);
let guard = set_subscriber(filter)?;
init_log_tracer()?;
Ok(guard)
}
pub fn filter_from_env_or(default: impl Into<Directive>) -> EnvFilter {
env::var(ENV_KEY)
.map(EnvFilter::new)
.unwrap_or_else(|_| EnvFilter::default().add_directive(default.into()))
}
pub fn init_log_tracer() -> Result<()> {
LogTracer::init().wrap_err("Failed to initialize log adapter")
}
pub fn set_subscriber(filter: EnvFilter) -> Result<Option<WorkerGuard>> {
let with_color = if let Ok(val) = env::var(COLOR_ENV_KEY) {
!val.eq_ignore_ascii_case("false")
} else {
true
};
let format = env::var(FMT_ENV_KEY)
.ok()
.and_then(|val| match val.to_ascii_lowercase().as_str() {
"full" => Some(Fmt::Full),
"json" => Some(Fmt::Json),
"pretty" => Some(Fmt::Pretty),
_ => None,
})
.unwrap_or_default();
let log_dir = env::var(DIR_ENV_KEY).ok();
let builder = Subscriber::builder()
.with_ansi(with_color)
.with_env_filter(filter);
macro_rules! finish {
($($builder:tt)*) => {
{
let my_collector = $($builder)*.finish();
tracing::subscriber::set_global_default(my_collector)
.wrap_err("Failed to set log subscriber")
}
}
}
macro_rules! select_format {
($($builder:tt)*) => {
{
match format {
Fmt::Full => finish!($($builder)*),
Fmt::Json => finish!($($builder)*.json()),
Fmt::Pretty => finish!($($builder)*.pretty()),
}
}
}
}
if let Some(dir) = log_dir {
use tracing_appender::rolling::{self, RollingFileAppender};
let rolling_fn: fn(_, _) -> RollingFileAppender = match rolling_freq() {
RollingFreq::Never => rolling::never,
RollingFreq::Minutely => rolling::minutely,
RollingFreq::Hourly => rolling::hourly,
RollingFreq::Daily => rolling::daily,
};
let file_appender = rolling_fn(dir, LOG_FILE_NAME_PREFIX);
let (non_blocking, guard) =
tracing_appender::non_blocking(file_appender);
let builder = builder.with_writer(non_blocking);
select_format!(builder)?;
Ok(Some(guard))
} else {
select_format!(builder)?;
Ok(None)
}
}
enum RollingFreq {
Never,
Minutely,
Hourly,
Daily,
}
fn rolling_freq() -> RollingFreq {
if let Ok(freq) = env::var(ROLLING_ENV_KEY) {
match freq.to_ascii_lowercase().as_str() {
"never" => RollingFreq::Never,
"minutely" => RollingFreq::Minutely,
"hourly" => RollingFreq::Hourly,
"daily" => RollingFreq::Daily,
_ => {
panic!(
"Unrecognized option set for {ROLLING_ENV_KEY}. Expecting \
one of: never, minutely, hourly, daily. Default is never."
);
}
}
} else {
RollingFreq::Never
}
}