diff --git a/Cargo.lock b/Cargo.lock index 71a1a70..42c4cd2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1834,6 +1834,7 @@ name = "harmony_cli" version = "0.1.0" dependencies = [ "assert_cmd", + "chrono", "clap", "console", "env_logger", diff --git a/Cargo.toml b/Cargo.toml index 22645f3..b12a4b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ readme = "README.md" license = "GNU AGPL v3" [workspace.dependencies] -log = "0.4" +log = { version = "0.4", features = ["kv"] } env_logger = "0.11" derive-new = "0.7" async-trait = "0.1" diff --git a/harmony_cli/Cargo.toml b/harmony_cli/Cargo.toml index a887b60..248b702 100644 --- a/harmony_cli/Cargo.toml +++ b/harmony_cli/Cargo.toml @@ -22,6 +22,7 @@ indicatif = "0.18.0" lazy_static = "1.5.0" log.workspace = true indicatif-log-bridge = "0.2.3" +chrono.workspace = true [dev-dependencies] harmony = { path = "../harmony", features = ["testing"] } diff --git a/harmony_cli/src/cli_logger.rs b/harmony_cli/src/cli_logger.rs index 9078e5d..c2fc8d0 100644 --- a/harmony_cli/src/cli_logger.rs +++ b/harmony_cli/src/cli_logger.rs @@ -1,22 +1,17 @@ +use chrono::Local; +use console::style; use harmony::{ instrumentation::{self, HarmonyEvent}, modules::application::ApplicationFeatureStatus, topology::TopologyStatus, }; -use indicatif::MultiProgress; -use indicatif_log_bridge::LogWrapper; -use log::error; -use std::{ - sync::{Arc, Mutex}, - thread, - time::Duration, -}; - -use crate::progress::{IndicatifProgressTracker, ProgressTracker}; +use log::{error, info, log_enabled}; +use std::io::Write; +use std::sync::{Arc, Mutex}; pub fn init() -> tokio::task::JoinHandle<()> { - let base_progress = configure_logger(); - let handle = tokio::spawn(handle_events(base_progress)); + configure_logger(); + let handle = tokio::spawn(handle_events()); loop { if instrumentation::instrument(HarmonyEvent::HarmonyStarted).is_ok() { @@ -27,28 +22,76 @@ pub fn init() -> tokio::task::JoinHandle<()> { handle } -fn configure_logger() -> MultiProgress { - let logger = - env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).build(); - let level = logger.filter(); - let progress = MultiProgress::new(); +fn configure_logger() { + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")) + .format(|buf, record| { + let debug_mode = log_enabled!(log::Level::Debug); + let timestamp = Local::now().format("%Y-%m-%d %H:%M:%S"); - LogWrapper::new(progress.clone(), logger) - .try_init() - .unwrap(); - log::set_max_level(level); - - progress + let level = match record.level() { + log::Level::Error => style("ERROR").red(), + log::Level::Warn => style("WARN").yellow(), + log::Level::Info => style("INFO").green(), + log::Level::Debug => style("DEBUG").blue(), + log::Level::Trace => style("TRACE").magenta(), + }; + if let Some(status) = record.key_values().get(log::kv::Key::from("status")) { + let status = status.to_borrowed_str().unwrap(); + let emoji = match status { + "finished" => style(crate::theme::EMOJI_SUCCESS.to_string()).green(), + "skipped" => style(crate::theme::EMOJI_SKIP.to_string()).yellow(), + "failed" => style(crate::theme::EMOJI_ERROR.to_string()).red(), + _ => style("".into()), + }; + if debug_mode { + writeln!( + buf, + "[{} {:<5} {}] {} {}", + timestamp, + level, + record.target(), + emoji, + record.args() + ) + } else { + writeln!(buf, "[{:<5}] {} {}", level, emoji, record.args()) + } + } else if let Some(emoji) = record.key_values().get(log::kv::Key::from("emoji")) { + if debug_mode { + writeln!( + buf, + "[{} {:<5} {}] {} {}", + timestamp, + level, + record.target(), + emoji, + record.args() + ) + } else { + writeln!(buf, "[{:<5}] {} {}", level, emoji, record.args()) + } + } else if debug_mode { + writeln!( + buf, + "[{} {:<5} {}] {}", + timestamp, + level, + record.target(), + record.args() + ) + } else { + writeln!(buf, "[{:<5}] {}", level, record.args()) + } + }) + .init(); } -async fn handle_events(base_progress: MultiProgress) { - let progress_tracker = Arc::new(IndicatifProgressTracker::new(base_progress.clone())); +async fn handle_events() { let preparing_topology = Arc::new(Mutex::new(false)); let current_score: Arc>> = Arc::new(Mutex::new(None)); instrumentation::subscribe("Harmony CLI Logger", { move |event| { - let progress_tracker = Arc::clone(&progress_tracker); let preparing_topology = Arc::clone(&preparing_topology); let current_score = Arc::clone(¤t_score); @@ -59,90 +102,57 @@ async fn handle_events(base_progress: MultiProgress) { match event { HarmonyEvent::HarmonyStarted => {} HarmonyEvent::HarmonyFinished => { - progress_tracker.add_section( - "harmony-summary", - &format!("\n{} Harmony completed\n\n", crate::theme::EMOJI_HARMONY), - ); - progress_tracker.add_section("harmony-finished", "\n\n"); - thread::sleep(Duration::from_millis(200)); + let emoji = crate::theme::EMOJI_HARMONY.to_string(); + info!(emoji = emoji.as_str(); "Harmony completed"); return false; } HarmonyEvent::TopologyStateChanged { topology, status, message, - } => { - let section_key = topology_key(&topology); - - match status { - TopologyStatus::Queued => {} - TopologyStatus::Preparing => { - progress_tracker.add_section( - §ion_key, - &format!( - "\n{} Preparing environment: {topology}...", - crate::theme::EMOJI_TOPOLOGY - ), - ); - (*preparing_topology) = true; - } - TopologyStatus::Success => { - (*preparing_topology) = false; - progress_tracker.add_task(§ion_key, "topology-success", ""); - progress_tracker - .finish_task("topology-success", &message.unwrap_or("".into())); - } - TopologyStatus::Noop => { - (*preparing_topology) = false; - progress_tracker.add_task(§ion_key, "topology-skip", ""); - progress_tracker - .skip_task("topology-skip", &message.unwrap_or("".into())); - } - TopologyStatus::Error => { - progress_tracker.add_task(§ion_key, "topology-error", ""); - (*preparing_topology) = false; - progress_tracker - .fail_task("topology-error", &message.unwrap_or("".into())); + } => match status { + TopologyStatus::Queued => {} + TopologyStatus::Preparing => { + let emoji = format!("{}", style(crate::theme::EMOJI_TOPOLOGY.to_string()).yellow()); + info!(emoji = emoji.as_str(); "Preparing environment: {topology}..."); + (*preparing_topology) = true; + } + TopologyStatus::Success => { + (*preparing_topology) = false; + if let Some(message) = message { + info!(status = "finished"; "{message}"); } } - } + TopologyStatus::Noop => { + (*preparing_topology) = false; + if let Some(message) = message { + info!(status = "skipped"; "{message}"); + } + } + TopologyStatus::Error => { + (*preparing_topology) = false; + if let Some(message) = message { + error!(status = "failed"; "{message}"); + } + } + }, HarmonyEvent::InterpretExecutionStarted { - execution_id: task_key, - topology, + execution_id: _, + topology: _, interpret: _, score, message, } => { - let is_key_topology = (*preparing_topology) - && progress_tracker.contains_section(&topology_key(&topology)); - let is_key_current_score = current_score.is_some() - && progress_tracker - .contains_section(&score_key(¤t_score.clone().unwrap())); - let is_key_score = progress_tracker.contains_section(&score_key(&score)); - - let section_key = if is_key_topology { - topology_key(&topology) - } else if is_key_current_score { - score_key(¤t_score.clone().unwrap()) - } else if is_key_score { - score_key(&score) + if *preparing_topology || current_score.is_some() { + info!("{message}"); } else { (*current_score) = Some(score.clone()); - let key = score_key(&score); - progress_tracker.add_section( - &key, - &format!( - "{} Interpreting score: {score}...", - crate::theme::EMOJI_SCORE - ), - ); - key - }; - - progress_tracker.add_task(§ion_key, &task_key, &message); + let emoji = format!("{}", style(crate::theme::EMOJI_SCORE).blue()); + info!(emoji = emoji.as_str(); "Interpreting score: {score}..."); + } } HarmonyEvent::InterpretExecutionFinished { - execution_id: task_key, + execution_id: _, topology: _, interpret: _, score, @@ -155,16 +165,17 @@ async fn handle_events(base_progress: MultiProgress) { match outcome { Ok(outcome) => match outcome.status { harmony::interpret::InterpretStatus::SUCCESS => { - progress_tracker.finish_task(&task_key, &outcome.message); + info!(status = "finished"; "{}", outcome.message); } harmony::interpret::InterpretStatus::NOOP => { - progress_tracker.skip_task(&task_key, &outcome.message); + info!(status = "skipped"; "{}", outcome.message); + } + _ => { + error!(status = "failed"; "{}", outcome.message); } - _ => progress_tracker.fail_task(&task_key, &outcome.message), }, Err(err) => { - error!("Interpret error: {err}"); - progress_tracker.fail_task(&task_key, &err.to_string()); + error!(status = "failed"; "{}", err); } } } @@ -173,30 +184,17 @@ async fn handle_events(base_progress: MultiProgress) { application, feature, status, - } => { - if let Some(score) = &(*current_score) { - let section_key = score_key(score); - let task_key = app_feature_key(&application, &feature); - - match status { - ApplicationFeatureStatus::Installing => { - let message = format!("Feature '{}' installing...", feature); - progress_tracker.add_task(§ion_key, &task_key, &message); - } - ApplicationFeatureStatus::Installed => { - let message = format!("Feature '{}' installed", feature); - progress_tracker.finish_task(&task_key, &message); - } - ApplicationFeatureStatus::Failed { details } => { - let message = format!( - "Feature '{}' installation failed: {}", - feature, details - ); - progress_tracker.fail_task(&task_key, &message); - } - } + } => match status { + ApplicationFeatureStatus::Installing => { + info!("Installing feature '{}' for '{}'...", feature, application); } - } + ApplicationFeatureStatus::Installed => { + info!(status = "finished"; "Feature '{}' installed", feature); + } + ApplicationFeatureStatus::Failed { details } => { + error!(status = "failed"; "Feature '{}' installation failed: {}", feature, details); + } + }, } true } @@ -204,15 +202,3 @@ async fn handle_events(base_progress: MultiProgress) { }) .await; } - -fn topology_key(topology: &str) -> String { - format!("topology-{topology}") -} - -fn score_key(score: &str) -> String { - format!("score-{score}") -} - -fn app_feature_key(application: &str, feature: &str) -> String { - format!("app-{application}-{feature}") -}