use std::path::PathBuf;
use std::str::FromStr;
use std::sync::atomic::AtomicU8;
use std::sync::atomic::Ordering;
use tracing::Level;
use tracing::Metadata;
use tracing::Subscriber;
use tracing_subscriber::Layer;
use tracing_subscriber::Registry;
use tracing_subscriber::layer::Context;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use super::constants::TRACE_LOG_FILENAME;
use super::constants::TRACING_FILTERED_TARGET_PREFIXES;
use super::lazy_file_writer::LazyFileWriter;
static CURRENT_LEVEL: AtomicU8 = AtomicU8::new(TracingLevel::Warn.code());
#[derive(Clone)]
struct DynamicFilter;
impl<S> Layer<S> for DynamicFilter
where
S: Subscriber,
{
fn enabled(&self, metadata: &Metadata<'_>, _: Context<'_, S>) -> bool {
let target = metadata.target();
if TRACING_FILTERED_TARGET_PREFIXES
.iter()
.any(|prefix| target.starts_with(prefix))
{
return false;
}
let current_level = TracingLevel::from(CURRENT_LEVEL.load(Ordering::Relaxed));
let metadata_level = TracingLevel::from(*metadata.level());
metadata_level.code() <= current_level.code()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TracingLevel {
Error,
Warn,
Info,
Debug,
Trace,
}
impl FromStr for TracingLevel {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"error" => Ok(Self::Error),
"warn" => Ok(Self::Warn),
"info" => Ok(Self::Info),
"debug" => Ok(Self::Debug),
"trace" => Ok(Self::Trace),
_ => Err(format!(
"Invalid tracing level '{s}'. Valid levels are: error, warn, info, debug, trace"
)),
}
}
}
impl From<u8> for TracingLevel {
fn from(level_code: u8) -> Self {
match level_code {
0 => Self::Error,
2 => Self::Info,
3 => Self::Debug,
4 => Self::Trace,
_ => Self::Warn,
}
}
}
impl From<Level> for TracingLevel {
fn from(level: Level) -> Self {
match level {
Level::ERROR => Self::Error,
Level::WARN => Self::Warn,
Level::INFO => Self::Info,
Level::DEBUG => Self::Debug,
Level::TRACE => Self::Trace,
}
}
}
impl TracingLevel {
const fn code(self) -> u8 {
match self {
Self::Error => 0,
Self::Warn => 1,
Self::Info => 2,
Self::Debug => 3,
Self::Trace => 4,
}
}
#[cfg(feature = "mcp-debug")]
pub const fn as_str(self) -> &'static str {
match self {
Self::Error => "error",
Self::Warn => "warn",
Self::Info => "info",
Self::Debug => "debug",
Self::Trace => "trace",
}
}
pub fn init_file_tracing() {
let log_path = Self::get_trace_log_path();
let lazy_file_writer = LazyFileWriter::new(log_path);
let file_layer = tracing_subscriber::fmt::layer()
.with_writer(lazy_file_writer)
.with_ansi(false)
.with_target(true)
.with_file(true)
.with_line_number(true);
let subscriber = Registry::default().with(DynamicFilter).with(file_layer);
subscriber.init();
}
pub fn get_current_tracing_level() -> Self { Self::from(CURRENT_LEVEL.load(Ordering::Relaxed)) }
#[cfg(feature = "mcp-debug")]
pub fn set_tracing_level(level: Self) {
CURRENT_LEVEL.store(level.code(), Ordering::Relaxed);
match level {
Self::Error => tracing::error!("Tracing level set to: error"),
Self::Warn => tracing::warn!("Tracing level set to: warn"),
Self::Info => tracing::info!("Tracing level set to: info"),
Self::Debug => tracing::debug!("Tracing level set to: debug"),
Self::Trace => tracing::trace!("Tracing level set to: trace"),
}
}
pub fn get_trace_log_path() -> PathBuf { std::env::temp_dir().join(TRACE_LOG_FILENAME) }
}