use crate::commands::{LogSource, LogsArgs};
use crate::context::CliContext;
use crate::paths;
use crate::services::{LogContentFilters, LogFileFilters, LogsService};
use anyhow::Result;
use std::io::Write;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;
pub async fn handle_logs(ctx: &mut CliContext, args: &LogsArgs) -> Result<()> {
println!();
println!("📋 Viewing Logs");
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
println!();
let should_follow = args.follow && !args.no_follow;
if !ctx.is_project_initialized() {
return Err(anyhow::anyhow!(
"Not in a Mecha10 project directory.\n\
Run 'mecha10 init' to create a new project."
));
}
let project = ctx.project()?;
let logs_dir = project.root().join(paths::project::LOGS_DIR);
let logs_service = LogsService::new(logs_dir);
let source = args.source.unwrap_or(LogSource::All);
display_filters(args, source);
let file_filters = LogFileFilters {
node: args.node.clone(),
source,
};
let log_sources = logs_service.collect_log_files(&file_filters)?;
if log_sources.is_empty() {
println!("No log files found.");
println!();
println!("Tip: Run 'mecha10 dev' to start nodes and generate logs");
println!();
return Ok(());
}
let content_filters = LogContentFilters {
pattern: args.filter.clone(),
level: args.level.clone(),
lines: args.lines,
};
if should_follow {
follow_logs(&logs_service, &log_sources, &content_filters, args.timestamps).await?;
} else {
display_logs(&logs_service, &log_sources, &content_filters, args.timestamps).await?;
}
Ok(())
}
fn display_filters(args: &LogsArgs, source: LogSource) {
let mut filters = Vec::new();
if let Some(node) = &args.node {
filters.push(format!("Node: {}", node));
}
filters.push(format!("Source: {:?}", source));
if let Some(pattern) = &args.filter {
filters.push(format!("Pattern: {}", pattern));
}
if let Some(level) = &args.level {
filters.push(format!("Level: {}", level));
}
if let Some(n) = args.lines {
filters.push(format!("Lines: {}", n));
}
if !filters.is_empty() {
println!("Filters: {}", filters.join(" | "));
println!();
}
}
async fn display_logs(
logs_service: &LogsService,
log_sources: &std::collections::HashMap<String, std::path::PathBuf>,
filters: &LogContentFilters,
_show_timestamps: bool,
) -> Result<()> {
let lines = logs_service.read_logs(log_sources, filters).await?;
let line_count = lines.len();
for log_line in lines {
if log_sources.len() > 1 {
println!("[{}] {}", log_line.source_name, log_line.content);
} else {
println!("{}", log_line.content);
}
}
println!();
println!("Showing {} lines from {} source(s)", line_count, log_sources.len());
println!();
Ok(())
}
async fn follow_logs(
logs_service: &LogsService,
log_sources: &std::collections::HashMap<String, std::path::PathBuf>,
filters: &LogContentFilters,
_show_timestamps: bool,
) -> Result<()> {
display_logs(logs_service, log_sources, filters, _show_timestamps).await?;
println!("Following logs (Ctrl+C to stop)...");
println!();
std::io::stdout().flush()?;
let running = Arc::new(AtomicBool::new(true));
let r = running.clone();
ctrlc::set_handler(move || {
r.store(false, Ordering::SeqCst);
})?;
let mut readers = logs_service.open_for_follow(log_sources)?;
while running.load(Ordering::SeqCst) {
let mut had_activity = false;
for (name, reader) in &mut readers {
let new_lines = logs_service.read_new_lines(reader, filters)?;
if !new_lines.is_empty() {
had_activity = true;
for line in new_lines {
if log_sources.len() > 1 {
print!("[{}] {}", name, line);
} else {
print!("{}", line);
}
let _ = std::io::stdout().flush();
}
}
}
if !had_activity {
std::thread::sleep(Duration::from_millis(100));
}
}
println!();
println!("Stopped following logs");
println!();
Ok(())
}