diff --git a/src/logging.rs b/src/logging.rs index ba544d2..13fb044 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -181,34 +181,43 @@ pub fn detect_network_from_url(rpc_url: &str) -> &'static str { /// /// # Arguments /// * `level` - Optional log level filter (defaults to "info") +/// * `use_stderr` - If true, logs to stderr instead of stdout. This is required for stdio transport mode. /// /// # Examples /// ``` /// use solana_mcp_server::logging::init_logging; /// -/// // Initialize with default level (info) -/// init_logging(None); +/// // Initialize with default level (info) for web/websocket mode +/// init_logging(None, false); /// -/// // Initialize with debug level -/// init_logging(Some("debug")); +/// // Initialize for stdio mode (logs to stderr) +/// init_logging(Some("debug"), true); /// ``` -pub fn init_logging(level: Option<&str>) -> Result<(), Box> { +pub fn init_logging(level: Option<&str>, use_stderr: bool) -> Result<(), Box> { let filter = EnvFilter::try_from_default_env() .or_else(|_| EnvFilter::try_new(level.unwrap_or("info")))?; - tracing_subscriber::registry() - .with(filter) - .with( - fmt::layer() - .json() - .with_current_span(false) - .with_thread_ids(true) - .with_thread_names(true) - .with_target(true) - .with_line_number(true) - .with_file(true) - ) - .try_init()?; + let fmt_layer = fmt::layer() + .json() + .with_current_span(false) + .with_thread_ids(true) + .with_thread_names(true) + .with_target(true) + .with_line_number(true) + .with_file(true); + + let subscriber = tracing_subscriber::registry() + .with(filter); + + if use_stderr { + subscriber + .with(fmt_layer.with_writer(std::io::stderr)) + .try_init()?; + } else { + subscriber + .with(fmt_layer) + .try_init()?; + } info!("Structured logging initialized"); Ok(()) @@ -639,7 +648,7 @@ mod tests { fn init_test_logging() { INIT.call_once(|| { - let _ = init_logging(Some("debug")); + let _ = init_logging(Some("debug"), false); }); } diff --git a/src/main.rs b/src/main.rs index 5a75603..937c801 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,24 +32,33 @@ enum Commands { #[tokio::main] async fn main() -> Result<()> { - // Initialize structured logging - if let Err(e) = init_logging(Some("info")) { - eprintln!("Failed to initialize logging: {e}"); - std::process::exit(1); - } - let cli = Cli::parse(); match cli.command.unwrap_or(Commands::Stdio) { Commands::Stdio => { + // For stdio mode, logging MUST go to stderr to avoid corrupting the JSON-RPC protocol on stdout + if let Err(e) = init_logging(Some("info"), true) { + eprintln!("Failed to initialize logging: {e}"); + std::process::exit(1); + } tracing::info!("Starting Solana MCP server in stdio mode..."); start_server().await } Commands::Web { port } => { + // For web mode, logging can go to stdout since it doesn't interfere with HTTP protocol + if let Err(e) = init_logging(Some("info"), false) { + eprintln!("Failed to initialize logging: {e}"); + std::process::exit(1); + } tracing::info!("Starting Solana MCP server in web service mode on port {}...", port); start_web_service(port).await } Commands::Websocket { port } => { + // For WebSocket mode, logging can go to stdout since it doesn't interfere with WebSocket protocol + if let Err(e) = init_logging(Some("info"), false) { + eprintln!("Failed to initialize logging: {e}"); + std::process::exit(1); + } tracing::info!("Starting Solana MCP server in WebSocket mode on port {}...", port); start_websocket_service(port).await } diff --git a/tests/error_handling.rs b/tests/error_handling.rs index cecca82..3e0c02b 100644 --- a/tests/error_handling.rs +++ b/tests/error_handling.rs @@ -8,7 +8,7 @@ use uuid::Uuid; #[tokio::test] async fn test_error_handling_integration() { // Initialize logging for test - let _ = init_logging(Some("debug")); + let _ = init_logging(Some("debug"), false); // Initialize prometheus metrics for test let _ = solana_mcp_server::init_prometheus_metrics();