1use 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#[derive(Debug, Clone)]
17pub struct LoggingConfig {
18 pub level: Level,
20 pub color: bool,
22 pub show_timestamps: bool,
24 pub show_target: bool,
26 pub json_format: bool,
28 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 pub fn for_mode(mode: ApplicationMode) -> Self {
48 match mode {
49 ApplicationMode::McpServer => Self {
50 level: Level::INFO,
51 color: false, show_timestamps: true,
53 show_target: true,
54 json_format: true, enable_spans: false, },
57 ApplicationMode::Dashboard => Self {
58 level: Level::INFO,
59 color: false, show_timestamps: true,
61 show_target: true,
62 json_format: false,
63 enable_spans: true, },
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 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#[derive(Debug, Clone, Copy)]
107pub enum ApplicationMode {
108 McpServer,
110 Dashboard,
112 Cli,
114 Test,
116}
117
118pub fn init_logging(config: LoggingConfig) -> io::Result<()> {
120 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 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 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
156pub 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#[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#[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#[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}