use chrono::Local; use console::style; use harmony::{ instrumentation::{self, HarmonyEvent}, modules::application::ApplicationFeatureStatus, topology::TopologyStatus, }; use log::{error, info, log_enabled}; use std::io::Write; use std::sync::{Arc, Mutex}; pub fn init() -> tokio::task::JoinHandle<()> { configure_logger(); let handle = tokio::spawn(handle_events()); loop { if instrumentation::instrument(HarmonyEvent::HarmonyStarted).is_ok() { break; } } handle } 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"); 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() { 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 preparing_topology = Arc::clone(&preparing_topology); let current_score = Arc::clone(¤t_score); async move { let mut preparing_topology = preparing_topology.lock().unwrap(); let mut current_score = current_score.lock().unwrap(); match event { HarmonyEvent::HarmonyStarted => {} HarmonyEvent::HarmonyFinished => { let emoji = crate::theme::EMOJI_HARMONY.to_string(); info!(emoji = emoji.as_str(); "Harmony completed"); return false; } HarmonyEvent::TopologyStateChanged { topology, status, message, } => 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: _, topology: _, interpret: _, score, message, } => { if *preparing_topology || current_score.is_some() { info!("{message}"); } else { (*current_score) = Some(score.clone()); let emoji = format!("{}", style(crate::theme::EMOJI_SCORE).blue()); info!(emoji = emoji.as_str(); "Interpreting score: {score}..."); } } HarmonyEvent::InterpretExecutionFinished { execution_id: _, topology: _, interpret: _, score, outcome, } => { if current_score.is_some() && current_score.clone().unwrap() == score { (*current_score) = None; } match outcome { Ok(outcome) => match outcome.status { harmony::interpret::InterpretStatus::SUCCESS => { info!(status = "finished"; "{}", outcome.message); } harmony::interpret::InterpretStatus::NOOP => { info!(status = "skipped"; "{}", outcome.message); } _ => { error!(status = "failed"; "{}", outcome.message); } }, Err(err) => { error!(status = "failed"; "{}", err); } } } HarmonyEvent::ApplicationFeatureStateChanged { topology: _, application, feature, status, } => 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 } } }) .await; }