use std::str::FromStr;
use tracing::Level;
use tracing::level_filters::LevelFilter;
use tracing_subscriber::EnvFilter;
use tracing_subscriber::fmt::format::FmtSpan;
use tracing_subscriber::layer::SubscriberExt as _;
pub mod progress;
#[derive(Debug, Clone, Copy)]
pub enum LogFormat {
Full,
Compact,
Bare,
Pretty,
Json,
}
impl LogFormat {
pub fn init(self, env_filter: EnvFilter) {
let dispatch = match self {
Self::Full => tracing_subscriber::fmt()
.with_span_events(FmtSpan::NONE)
.with_env_filter(env_filter)
.finish()
.into(),
Self::Compact => tracing_subscriber::fmt()
.compact()
.with_span_events(FmtSpan::NONE)
.with_env_filter(env_filter)
.finish()
.into(),
Self::Pretty => tracing_subscriber::fmt()
.pretty()
.with_env_filter(env_filter)
.finish()
.into(),
Self::Bare => tracing_subscriber::fmt()
.compact()
.with_span_events(FmtSpan::NONE)
.without_time()
.with_target(false)
.with_ansi(false)
.with_env_filter(env_filter)
.finish()
.into(),
Self::Json => tracing_subscriber::fmt()
.json()
.with_span_events(FmtSpan::NONE)
.with_env_filter(env_filter)
.finish()
.into(),
};
tracing::dispatcher::set_global_default(dispatch)
.expect("failed to set global default subscriber");
}
pub fn init_with_progress(self, env_filter: EnvFilter) {
use tracing_subscriber::fmt::layer as fmt_layer;
let registry = tracing_subscriber::registry().with(env_filter);
let dispatch = match self {
Self::Full => {
let indicatif_layer = tracing_indicatif::IndicatifLayer::new();
registry
.with(
fmt_layer()
.with_span_events(FmtSpan::NONE)
.with_writer(indicatif_layer.get_stderr_writer()),
)
.with(indicatif_layer)
.into()
}
Self::Compact => {
let indicatif_layer = tracing_indicatif::IndicatifLayer::new();
registry
.with(
fmt_layer()
.compact()
.with_span_events(FmtSpan::NONE)
.with_writer(indicatif_layer.get_stderr_writer()),
)
.with(indicatif_layer)
.into()
}
Self::Pretty => {
let indicatif_layer = tracing_indicatif::IndicatifLayer::new();
registry
.with(
fmt_layer()
.pretty()
.with_writer(indicatif_layer.get_stderr_writer()),
)
.with(indicatif_layer)
.into()
}
Self::Bare => {
let indicatif_layer = tracing_indicatif::IndicatifLayer::new();
registry
.with(
fmt_layer()
.compact()
.with_span_events(FmtSpan::NONE)
.without_time()
.with_target(false)
.with_ansi(false)
.with_writer(indicatif_layer.get_stderr_writer()),
)
.with(indicatif_layer)
.into()
}
Self::Json => {
let indicatif_layer = tracing_indicatif::IndicatifLayer::new();
registry
.with(
fmt_layer()
.json()
.with_span_events(FmtSpan::NONE)
.with_writer(indicatif_layer.get_stderr_writer()),
)
.with(indicatif_layer)
.into()
}
};
tracing::dispatcher::set_global_default(dispatch)
.expect("failed to set global default subscriber");
}
}
impl Default for LogFormat {
fn default() -> Self {
if cfg!(debug_assertions) {
Self::Pretty
} else {
Self::Compact
}
}
}
impl FromStr for LogFormat {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"full" => Ok(Self::Full),
"compact" => Ok(Self::Compact),
"pretty" | "verbose" => Ok(Self::Pretty),
"bare" => Ok(Self::Bare),
"json" | "jsonl" => Ok(Self::Json),
_ => Err(format!(
"Invalid log format '{s}'. Valid options: json, full, compact, bare or pretty"
)),
}
}
}
fn init_log_bridge(env_filter: &EnvFilter) {
let mut log_builder = tracing_log::LogTracer::builder()
.with_interest_cache(tracing_log::InterestCacheConfig::default());
if let Some(Some(max_level)) = env_filter.max_level_hint().map(LevelFilter::into_level) {
let max_level = match max_level {
Level::DEBUG => log::LevelFilter::Debug,
Level::INFO => log::LevelFilter::Info,
Level::WARN => log::LevelFilter::Warn,
Level::ERROR => log::LevelFilter::Error,
Level::TRACE => log::LevelFilter::Trace,
};
log_builder = log_builder.with_max_level(max_level);
}
log_builder
.init()
.expect("failed to initialize log -> tracing bridge: LogTracer already set");
}
pub fn init_tracing(filter: &str, format: Option<String>, use_progress: bool) {
let env_filter = EnvFilter::from_str(filter).unwrap_or_else(|_| {
eprintln!("Warning: Invalid filter string '{filter}' passed. Since you passed a filter, you likely want to debug us, so we set the filter to debug");
EnvFilter::new("debug")
});
let log_format = format
.and_then(|s| {
s.parse::<LogFormat>()
.map_err(|e| {
eprintln!("Warning: {e}");
eprintln!(
"Falling back to default format ({:?})",
LogFormat::default()
);
})
.ok()
})
.unwrap_or_default();
init_log_bridge(&env_filter);
if use_progress {
log_format.init_with_progress(env_filter);
} else {
log_format.init(env_filter);
}
}
#[must_use]
pub fn ensure_martin_core_log_level_matches(
env_filter: Option<String>,
replacement: &'static str,
) -> String {
if let Some(rust_log) = env_filter {
if rust_log.contains(replacement) && !rust_log.contains("martin_core=") {
if let Some(level) = rust_log
.split(',')
.find_map(|s| s.strip_prefix(replacement))
{
format!("{rust_log},martin_core={level}")
} else {
rust_log
}
} else {
rust_log
}
} else {
format!("{replacement}info,martin_core=info")
}
}