use anyhow::Result;
use mcp_common::{TracingConfig, TracingGuard};
use once_cell::sync::OnceCell;
use tracing_subscriber::{EnvFilter, fmt, prelude::*};
use super::args::{ConvertArgs, LoggingArgs};
static TRACING_GUARD: OnceCell<TracingGuard> = OnceCell::new();
pub fn init_logging(
args: &ConvertArgs,
mcp_name: Option<&str>,
quiet: bool,
verbose: bool,
) -> Result<()> {
init_logging_with_config(&args.logging, mcp_name, quiet, verbose)
}
pub fn init_logging_with_config(
logging: &LoggingArgs,
mcp_name: Option<&str>,
quiet: bool,
verbose: bool,
) -> Result<()> {
let enable_otlp = logging.diagnostic && logging.otlp_endpoint.is_some();
if enable_otlp {
init_otlp_tracing(logging, mcp_name, quiet)?;
}
let log_file_path = determine_log_file_path(logging, mcp_name)?;
match (log_file_path, enable_otlp) {
(Some(file_path), true) => {
init_with_file_and_otlp(logging, &file_path, quiet, verbose)?;
}
(Some(file_path), false) => {
init_with_file_only(logging, &file_path, quiet, verbose)?;
}
(None, true) => {
init_stderr_with_otlp(logging, quiet, verbose)?;
}
(None, false) => {
init_stderr_only(logging, quiet, verbose)?;
}
}
Ok(())
}
fn determine_log_file_path(
logging: &LoggingArgs,
mcp_name: Option<&str>,
) -> Result<Option<std::path::PathBuf>> {
if let Some(log_file) = &logging.log_file {
Ok(Some(log_file.clone()))
} else if let Some(log_dir) = &logging.log_dir {
let session_id = generate_session_id();
let date = chrono::Local::now().format("%Y%m%d");
let name_part = mcp_name.unwrap_or("unknown");
let filename = format!("mcp-proxy-{}-{}-{}.log", name_part, date, session_id);
std::fs::create_dir_all(log_dir)
.map_err(|e| anyhow::anyhow!("Failed to create log directory: {}", e))?;
Ok(Some(log_dir.join(filename)))
} else if logging.diagnostic {
let session_id = generate_session_id();
let date = chrono::Local::now().format("%Y%m%d");
let name_part = mcp_name.unwrap_or("unknown");
let filename = format!("mcp-proxy-{}-{}-{}.log", name_part, date, session_id);
let temp_dir = std::env::temp_dir();
let file_path = temp_dir.join(filename);
std::fs::File::create(&file_path).map_err(|e| {
anyhow::anyhow!(
"Failed to create log file: {} (path: {})",
e,
file_path.display()
)
})?;
Ok(Some(file_path))
} else {
Ok(None)
}
}
fn create_filter(logging: &LoggingArgs, verbose: bool) -> EnvFilter {
EnvFilter::try_from_default_env().unwrap_or_else(|_| {
if logging.diagnostic {
EnvFilter::new("debug")
} else if verbose {
EnvFilter::new("info")
} else {
EnvFilter::new("warn")
}
})
}
fn init_with_file_and_otlp(
logging: &LoggingArgs,
file_path: &std::path::Path,
quiet: bool,
verbose: bool,
) -> Result<()> {
let file = std::fs::File::create(file_path)
.map_err(|e| anyhow::anyhow!("Failed to create log file: {}", e))?;
if !quiet {
eprintln!("📝 Log file: {}", file_path.display());
eprintln!(
"📋 Diagnostic mode: {} (log level: {})",
if logging.diagnostic {
"enabled"
} else {
"disabled"
},
if logging.diagnostic { "DEBUG" } else { "WARN" }
);
}
let file_shared = std::sync::Arc::new(file);
let filter = create_filter(logging, verbose);
tracing_subscriber::registry()
.with(filter)
.with(
fmt::layer()
.with_writer(std::sync::Mutex::new(file_shared.clone()))
.with_ansi(false),
)
.with(fmt::layer().with_writer(std::io::stderr).with_ansi(true))
.with(tracing_opentelemetry::layer())
.init();
Ok(())
}
fn init_with_file_only(
logging: &LoggingArgs,
file_path: &std::path::Path,
quiet: bool,
verbose: bool,
) -> Result<()> {
let file = std::fs::File::create(file_path)
.map_err(|e| anyhow::anyhow!("Failed to create log file: {}", e))?;
if !quiet {
eprintln!("📝 Log file: {}", file_path.display());
eprintln!(
"📋 Diagnostic mode: {} (log level: {})",
if logging.diagnostic {
"enabled"
} else {
"disabled"
},
if logging.diagnostic { "DEBUG" } else { "WARN" }
);
}
let file_shared = std::sync::Arc::new(file);
let filter = create_filter(logging, verbose);
tracing_subscriber::registry()
.with(filter)
.with(
fmt::layer()
.with_writer(std::sync::Mutex::new(file_shared.clone()))
.with_ansi(false),
)
.with(fmt::layer().with_writer(std::io::stderr).with_ansi(true))
.init();
Ok(())
}
fn init_stderr_with_otlp(logging: &LoggingArgs, quiet: bool, verbose: bool) -> Result<()> {
if !quiet && !logging.diagnostic {
eprintln!("📋 Diagnostic mode: disabled (no log file will be created)");
}
let filter = create_filter(logging, verbose);
tracing_subscriber::registry()
.with(filter)
.with(fmt::layer().with_writer(std::io::stderr))
.with(tracing_opentelemetry::layer())
.init();
Ok(())
}
fn init_stderr_only(logging: &LoggingArgs, quiet: bool, verbose: bool) -> Result<()> {
if !quiet && !logging.diagnostic {
eprintln!("📋 Diagnostic mode: disabled (no log file will be created)");
}
let filter = create_filter(logging, verbose);
tracing_subscriber::registry()
.with(filter)
.with(fmt::layer().with_writer(std::io::stderr))
.init();
Ok(())
}
fn init_otlp_tracing(logging: &LoggingArgs, mcp_name: Option<&str>, quiet: bool) -> Result<()> {
if let Some(endpoint) = &logging.otlp_endpoint {
let service_name = mcp_name.unwrap_or(&logging.service_name);
let config = TracingConfig::new(service_name)
.with_otlp(endpoint)
.with_version(env!("CARGO_PKG_VERSION"));
let guard = mcp_common::init_tracing(&config)?;
let _ = TRACING_GUARD.set(guard);
if !quiet {
eprintln!("🔭 OTLP tracing: enabled");
eprintln!(" Endpoint: {}", endpoint);
eprintln!(" Service: {}", service_name);
}
}
Ok(())
}
pub fn generate_session_id() -> String {
use rand::Rng;
let mut rng = rand::rng();
format!("{:08x}", rng.random::<u32>())
}