//! Logging infrastructure for the ACP mocker. //! //! This module provides utilities for initializing and configuring structured logging //! using the `tracing` ecosystem. It supports multiple output formats and log levels //! configurable via environment variables. //! //! ## Important: stdio Mode //! //! When using stdio transport (the primary ACP communication method), ALL logs are //! automatically written to stderr to keep stdout clean for JSON-RPC messages. use std::fs::OpenOptions; use std::sync::Arc; use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; /// Output format for logs. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum LogFormat { /// Human-readable pretty format with colors (default for development). #[default] Pretty, /// JSON format for structured logging (recommended for production/CI). Json, /// Compact format for minimal output. Compact, } /// Initialize the logging system with the specified format. /// /// # Log Levels /// /// Log levels can be configured via the `RUST_LOG` environment variable: /// - `RUST_LOG=trace` - Most verbose, includes all internal details /// - `RUST_LOG=debug` - Detailed debugging information /// - `RUST_LOG=info` - General informational messages (default) /// - `RUST_LOG=warn` - Warning messages only /// - `RUST_LOG=error` - Error messages only /// /// You can also filter by module: /// ```bash /// RUST_LOG=dirigate=debug,axum=info /// ``` /// /// # Examples /// /// ```rust,no_run /// use dirigate::logging::{init_logging, LogFormat}; /// /// // Initialize with pretty format (default) /// init_logging(LogFormat::Pretty); /// /// // Initialize with JSON format for production /// init_logging(LogFormat::Json); /// ``` /// /// # Panics /// /// Panics if logging has already been initialized. This should be called /// exactly once at the start of the application. pub fn init_logging(format: LogFormat) { let env_filter = EnvFilter::try_from_default_env() .unwrap_or_else(|_| EnvFilter::new("dirigate=info,axum=info")); // Try to create log file in system temp directory // If this fails, we'll just log to stderr only let log_file_path = std::env::temp_dir().join("dirigate.log"); let file_writer = OpenOptions::new() .create(true) .write(true) .truncate(true) .open(&log_file_path) .ok() .map(Arc::new); // CRITICAL: All logs go to stderr (and optionally to file if available) // In stdio mode, stdout is reserved for JSON-RPC messages only match (format, file_writer) { (LogFormat::Pretty, Some(file_writer)) => { tracing_subscriber::registry() .with(env_filter) .with(fmt::layer().pretty().with_writer(std::io::stderr)) .with(fmt::layer().with_ansi(false).with_writer(file_writer)) .init(); tracing::info!( "Logging initialized with Pretty format, writing to {:?}", log_file_path ); } (LogFormat::Pretty, None) => { tracing_subscriber::registry() .with(env_filter) .with(fmt::layer().pretty().with_writer(std::io::stderr)) .init(); tracing::info!("Logging initialized with Pretty format (file logging unavailable)"); } (LogFormat::Json, Some(file_writer)) => { tracing_subscriber::registry() .with(env_filter) .with(fmt::layer().json().with_writer(std::io::stderr)) .with( fmt::layer() .json() .with_ansi(false) .with_writer(file_writer), ) .init(); tracing::info!( "Logging initialized with Json format, writing to {:?}", log_file_path ); } (LogFormat::Json, None) => { tracing_subscriber::registry() .with(env_filter) .with(fmt::layer().json().with_writer(std::io::stderr)) .init(); tracing::info!("Logging initialized with Json format (file logging unavailable)"); } (LogFormat::Compact, Some(file_writer)) => { tracing_subscriber::registry() .with(env_filter) .with(fmt::layer().compact().with_writer(std::io::stderr)) .with( fmt::layer() .compact() .with_ansi(false) .with_writer(file_writer), ) .init(); tracing::info!( "Logging initialized with Compact format, writing to {:?}", log_file_path ); } (LogFormat::Compact, None) => { tracing_subscriber::registry() .with(env_filter) .with(fmt::layer().compact().with_writer(std::io::stderr)) .init(); tracing::info!("Logging initialized with Compact format (file logging unavailable)"); } } } /// Initialize logging with default settings (pretty format, info level). /// /// This is a convenience function equivalent to `init_logging(LogFormat::Pretty)`. /// /// # Examples /// /// ```rust,no_run /// use dirigate::logging::init_default_logging; /// /// init_default_logging(); /// ``` pub fn init_default_logging() { init_logging(LogFormat::default()); } #[cfg(test)] mod tests { use super::*; #[test] fn test_log_format_default() { assert_eq!(LogFormat::default(), LogFormat::Pretty); } }