mod format;
mod level;
use crate::tracing::format::*;
use std::fs::File;
use std::io;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::Ordering;
use std::time::SystemTime;
use std::{env, fs};
use tracing::subscriber::set_global_default;
use tracing_chrome::{ChromeLayerBuilder, FlushGuard};
use tracing_subscriber::fmt::{self, SubscriberBuilder};
use tracing_subscriber::{EnvFilter, prelude::*};
pub use crate::tracing::level::LogLevel;
pub use tracing::{
debug, debug_span, enabled, error, error_span, event, event_enabled, info, info_span,
instrument, span, span_enabled, trace, trace_span, warn, warn_span,
};
pub struct TracingOptions {
pub default_level: LogLevel,
pub dump_trace: bool,
pub filter_modules: Vec<String>,
#[cfg(feature = "log-compat")]
pub intercept_log: bool,
pub log_env: String,
pub log_file: Option<PathBuf>,
pub show_spans: bool,
pub test_env: String,
}
impl Default for TracingOptions {
fn default() -> Self {
TracingOptions {
default_level: LogLevel::Info,
dump_trace: false,
filter_modules: vec![],
#[cfg(feature = "log-compat")]
intercept_log: true,
log_env: "STARBASE_LOG".into(),
log_file: None,
show_spans: false,
test_env: "STARBASE_TEST".into(),
}
}
}
pub struct TracingGuard {
chrome_guard: Option<FlushGuard>,
log_file: Option<Arc<File>>,
}
#[tracing::instrument(skip_all)]
pub fn setup_tracing(options: TracingOptions) -> TracingGuard {
TEST_ENV.store(env::var(options.test_env).is_ok(), Ordering::Release);
let level = env::var(&options.log_env).unwrap_or_else(|_| options.default_level.to_string());
unsafe {
env::set_var(
&options.log_env,
if options.filter_modules.is_empty()
|| level == "off"
|| level.contains(',')
|| level.contains('=')
{
level
} else {
options
.filter_modules
.iter()
.map(|prefix| format!("{prefix}={level}"))
.collect::<Vec<_>>()
.join(",")
},
)
};
#[cfg(feature = "log-compat")]
if options.intercept_log {
tracing_log::LogTracer::init().expect("Failed to initialize log interceptor.");
}
let subscriber = SubscriberBuilder::default()
.event_format(EventFormatter {
show_spans: options.show_spans,
})
.fmt_fields(FieldFormatter)
.with_env_filter(EnvFilter::from_env(options.log_env))
.with_writer(io::stderr)
.finish();
let mut guard = TracingGuard {
chrome_guard: None,
log_file: None,
};
let _ = set_global_default(
subscriber
.with(if let Some(log_file) = options.log_file {
if let Some(dir) = log_file.parent() {
fs::create_dir_all(dir).expect("Failed to create log directory.");
}
let file = Arc::new(File::create(log_file).expect("Failed to create log file."));
guard.log_file = Some(Arc::clone(&file));
Some(fmt::layer().with_ansi(false).with_writer(file))
} else {
None
})
.with(if options.dump_trace {
let (chrome_layer, chrome_guard) = ChromeLayerBuilder::new()
.include_args(true)
.include_locations(true)
.file(format!(
"./dump-{}.json",
SystemTime::UNIX_EPOCH.elapsed().unwrap().as_micros()
))
.build();
guard.chrome_guard = Some(chrome_guard);
Some(chrome_layer)
} else {
None
}),
);
guard
}