intent_engine/
logging.rs

1//! Intent-Engine Logging System
2//!
3//! Provides structured logging with configurable levels and output formats.
4//! Uses tracing crate for structured logging with spans and events.
5
6use std::io;
7use tracing::Level;
8use tracing_subscriber::{
9    fmt::{self, format::FmtSpan},
10    layer::SubscriberExt,
11    util::SubscriberInitExt,
12    EnvFilter, Layer, Registry,
13};
14
15/// Logging configuration options
16#[derive(Debug, Clone)]
17pub struct LoggingConfig {
18    /// Minimum log level to output
19    pub level: Level,
20    /// Enable colored output
21    pub color: bool,
22    /// Show timestamps
23    pub show_timestamps: bool,
24    /// Show target/module name
25    pub show_target: bool,
26    /// Enable JSON format for machine parsing
27    pub json_format: bool,
28    /// Enable span events for tracing
29    pub enable_spans: bool,
30}
31
32impl Default for LoggingConfig {
33    fn default() -> Self {
34        Self {
35            level: Level::INFO,
36            color: true,
37            show_timestamps: false,
38            show_target: false,
39            json_format: false,
40            enable_spans: false,
41        }
42    }
43}
44
45impl LoggingConfig {
46    /// Create config for different application modes
47    pub fn for_mode(mode: ApplicationMode) -> Self {
48        match mode {
49            ApplicationMode::McpServer => Self {
50                level: Level::INFO,
51                color: false, // MCP output should be clean
52                show_timestamps: true,
53                show_target: true,
54                json_format: true,   // Machine-readable for MCP
55                enable_spans: false, // Avoid noise in JSON-RPC
56            },
57            ApplicationMode::Dashboard => Self {
58                level: Level::INFO,
59                color: false, // Background service
60                show_timestamps: true,
61                show_target: true,
62                json_format: false,
63                enable_spans: true, // Good for debugging dashboard
64            },
65            ApplicationMode::Cli => Self {
66                level: Level::INFO,
67                color: true,
68                show_timestamps: false,
69                show_target: false,
70                json_format: false,
71                enable_spans: false,
72            },
73            ApplicationMode::Test => Self {
74                level: Level::DEBUG,
75                color: false,
76                show_timestamps: true,
77                show_target: true,
78                json_format: false,
79                enable_spans: true,
80            },
81        }
82    }
83
84    /// Create config from CLI arguments
85    pub fn from_args(quiet: bool, verbose: bool, json: bool) -> Self {
86        let level = if verbose {
87            Level::DEBUG
88        } else if quiet {
89            Level::ERROR
90        } else {
91            Level::INFO
92        };
93
94        Self {
95            level,
96            color: !quiet && !json && atty::is(atty::Stream::Stdout),
97            show_timestamps: verbose || json,
98            show_target: verbose,
99            json_format: json,
100            enable_spans: verbose,
101        }
102    }
103}
104
105/// Application modes with different logging requirements
106#[derive(Debug, Clone, Copy)]
107pub enum ApplicationMode {
108    /// MCP server mode - clean, structured output
109    McpServer,
110    /// Dashboard server mode - detailed for debugging
111    Dashboard,
112    /// CLI mode - user-friendly output
113    Cli,
114    /// Test mode - maximum detail for testing
115    Test,
116}
117
118/// Initialize the logging system
119pub fn init_logging(config: LoggingConfig) -> io::Result<()> {
120    // Create environment filter from config
121    let env_filter = EnvFilter::try_from_default_env()
122        .unwrap_or_else(|_| EnvFilter::new(format!("intent_engine={}", config.level)));
123
124    let registry = Registry::default().with(env_filter);
125
126    if config.json_format {
127        // JSON format for machine processing
128        let json_layer = tracing_subscriber::fmt::layer()
129            .json()
130            .with_current_span(config.enable_spans)
131            .with_span_events(FmtSpan::CLOSE)
132            .with_writer(io::stdout);
133
134        json_layer.with_subscriber(registry).init();
135    } else {
136        // Human-readable format
137        let fmt_layer = fmt::layer()
138            .with_target(config.show_target)
139            .with_level(true)
140            .with_ansi(config.color)
141            .with_writer(io::stdout);
142
143        if config.show_timestamps {
144            fmt_layer
145                .with_timer(fmt::time::ChronoUtc::rfc_3339())
146                .with_subscriber(registry)
147                .init();
148        } else {
149            fmt_layer.with_subscriber(registry).init();
150        }
151    }
152
153    Ok(())
154}
155
156/// Initialize logging from environment variables
157pub fn init_from_env() -> io::Result<()> {
158    let _level = match std::env::var("IE_LOG_LEVEL").as_deref() {
159        Ok("error") => Level::ERROR,
160        Ok("warn") => Level::WARN,
161        Ok("info") => Level::INFO,
162        Ok("debug") => Level::DEBUG,
163        Ok("trace") => Level::TRACE,
164        _ => Level::INFO,
165    };
166
167    let json = std::env::var("IE_LOG_JSON").as_deref() == Ok("true");
168    let verbose = std::env::var("IE_LOG_VERBOSE").as_deref() == Ok("true");
169    let quiet = std::env::var("IE_LOG_QUIET").as_deref() == Ok("true");
170
171    let config = LoggingConfig::from_args(quiet, verbose, json);
172    init_logging(config)
173}
174
175/// Log macros for common intent-engine operations
176#[macro_export]
177macro_rules! log_project_operation {
178    ($operation:expr, $project_path:expr) => {
179        tracing::info!(
180            operation = $operation,
181            project_path = %$project_path.display(),
182            "Project operation"
183        );
184    };
185    ($operation:expr, $project_path:expr, $details:expr) => {
186        tracing::info!(
187            operation = $operation,
188            project_path = %$project_path.display(),
189            details = $details,
190            "Project operation"
191        );
192    };
193}
194
195#[macro_export]
196macro_rules! log_mcp_operation {
197    ($operation:expr, $method:expr) => {
198        tracing::debug!(
199            operation = $operation,
200            mcp_method = $method,
201            "MCP operation"
202        );
203    };
204    ($operation:expr, $method:expr, $details:expr) => {
205        tracing::debug!(
206            operation = $operation,
207            mcp_method = $method,
208            details = $details,
209            "MCP operation"
210        );
211    };
212}
213
214#[macro_export]
215macro_rules! log_dashboard_operation {
216    ($operation:expr) => {
217        tracing::info!(operation = $operation, "Dashboard operation");
218    };
219    ($operation:expr, $details:expr) => {
220        tracing::info!(
221            operation = $operation,
222            details = $details,
223            "Dashboard operation"
224        );
225    };
226}
227
228#[macro_export]
229macro_rules! log_task_operation {
230    ($operation:expr, $task_id:expr) => {
231        tracing::info!(operation = $operation, task_id = $task_id, "Task operation");
232    };
233    ($operation:expr, $task_id:expr, $details:expr) => {
234        tracing::info!(
235            operation = $operation,
236            task_id = $task_id,
237            details = $details,
238            "Task operation"
239        );
240    };
241}
242
243#[macro_export]
244macro_rules! log_registry_operation {
245    ($operation:expr, $count:expr) => {
246        tracing::debug!(
247            operation = $operation,
248            project_count = $count,
249            "Registry operation"
250        );
251    };
252}
253
254/// Utility macro for structured error logging
255#[macro_export]
256macro_rules! log_error {
257    ($error:expr, $context:expr) => {
258        tracing::error!(
259            error = %$error,
260            context = $context,
261            "Operation failed"
262        );
263    };
264}
265
266/// Utility macro for structured warning logging
267#[macro_export]
268macro_rules! log_warning {
269    ($message:expr) => {
270        tracing::warn!($message);
271    };
272    ($message:expr, $details:expr) => {
273        tracing::warn!(message = $message, details = $details, "Warning");
274    };
275}