From ed82c24664cb4211c58e629cb08464a049d21dd5 Mon Sep 17 00:00:00 2001 From: Adrien Cacciaguerra Date: Wed, 19 Jun 2024 18:56:15 +0000 Subject: [PATCH 1/8] feat(cli): create custom local logger with spinner --- Cargo.lock | 33 +++++++++++++ Cargo.toml | 1 + src/logger.rs | 98 +++++++++++++++++++++++++++++++------- src/run/runner/valgrind.rs | 5 +- 4 files changed, 119 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fa46816..eecf2e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -258,6 +258,7 @@ dependencies = [ "clap", "git2", "gql_client", + "indicatif", "insta", "itertools", "lazy_static", @@ -297,6 +298,7 @@ dependencies = [ "encode_unicode", "lazy_static", "libc", + "unicode-width", "windows-sys 0.45.0", ] @@ -794,6 +796,19 @@ dependencies = [ "hashbrown 0.14.3", ] +[[package]] +name = "indicatif" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" +dependencies = [ + "console", + "instant", + "number_prefix", + "portable-atomic", + "unicode-width", +] + [[package]] name = "insta" version = "1.34.0" @@ -1049,6 +1064,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + [[package]] name = "object" version = "0.32.1" @@ -1235,6 +1256,12 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +[[package]] +name = "portable-atomic" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" + [[package]] name = "powerfmt" version = "0.2.0" @@ -2013,6 +2040,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-width" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" + [[package]] name = "unsafe-libyaml" version = "0.2.11" diff --git a/Cargo.toml b/Cargo.toml index b251b72..7f0e586 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ nestify = "0.3.3" gql_client = { git = "https://github.com/adriencaccia/gql-client-rs" } serde_yaml = "0.9.34" sysinfo = { version = "0.30.12", features = ["serde"] } +indicatif = "0.17.8" [dev-dependencies] temp-env = { version = "0.3.6", features = ["async_closure"] } diff --git a/src/logger.rs b/src/logger.rs index bd3639a..3255d6b 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -1,6 +1,10 @@ -use std::env; +use std::{env, time::Duration}; -use simplelog::{ConfigBuilder, SharedLogger}; +use indicatif::{ProgressBar, ProgressStyle}; +use lazy_static::lazy_static; +use log::Log; +use simplelog::SharedLogger; +use std::io::Write; /// This target is used exclusively to handle group events. pub const GROUP_TARGET: &str = "codspeed::group"; @@ -76,20 +80,80 @@ pub(super) fn get_group_event(record: &log::Record) -> Option { } } +lazy_static! { + pub static ref SPINNER: ProgressBar = { + let spinner = ProgressBar::new_spinner(); + spinner.set_style(ProgressStyle::with_template("{spinner:.cyan} {wide_msg}").unwrap()); + spinner + }; +} +pub struct LocalLogger { + log_level: log::LevelFilter, +} + +impl LocalLogger { + pub fn new() -> Self { + let log_level = env::var("CODSPEED_LOG") + .ok() + .and_then(|log_level| log_level.parse::().ok()) + .unwrap_or(log::LevelFilter::Info); + + LocalLogger { log_level } + } +} + +impl Log for LocalLogger { + fn enabled(&self, metadata: &log::Metadata) -> bool { + metadata.level() <= self.log_level + } + + fn log(&self, record: &log::Record) { + if !self.enabled(record.metadata()) { + return; + } + + if let Some(group_event) = get_group_event(record) { + match group_event { + GroupEvent::Start(name) | GroupEvent::StartOpened(name) => { + SPINNER.set_message(format!("{}...", name)); + SPINNER.enable_steady_tick(Duration::from_millis(100)); + } + GroupEvent::End => { + SPINNER.reset(); + } + } + + return; + } + + SPINNER.suspend(|| { + if record.level() == log::Level::Error { + eprintln!("{}", record.args()); + } else { + println!("{}", record.args()); + } + }); + } + + fn flush(&self) { + std::io::stdout().flush().unwrap(); + } +} + +impl SharedLogger for LocalLogger { + fn level(&self) -> log::LevelFilter { + self.log_level + } + + fn config(&self) -> Option<&simplelog::Config> { + None + } + + fn as_log(self: Box) -> Box { + Box::new(*self) + } +} + pub fn get_local_logger() -> Box { - let log_level = env::var("CODSPEED_LOG") - .ok() - .and_then(|log_level| log_level.parse::().ok()) - .unwrap_or(log::LevelFilter::Info); - - let config = ConfigBuilder::new() - .set_time_level(log::LevelFilter::Debug) - .build(); - - simplelog::TermLogger::new( - log_level, - config, - simplelog::TerminalMode::Mixed, - simplelog::ColorChoice::Auto, - ) + Box::new(LocalLogger::new()) } diff --git a/src/run/runner/valgrind.rs b/src/run/runner/valgrind.rs index 962fd83..53ec0de 100644 --- a/src/run/runner/valgrind.rs +++ b/src/run/runner/valgrind.rs @@ -1,3 +1,4 @@ +use crate::logger::SPINNER; use crate::prelude::*; use crate::run::{ config::Config, instruments::mongo_tracer::MongoTracer, @@ -72,7 +73,9 @@ fn run_command_with_log_pipe(mut cmd: Command) -> Result { if bytes_read == 0 { break; } - writer.write_all(&buffer[..bytes_read])?; + SPINNER.suspend(|| { + writer.write_all(&buffer[..bytes_read]).unwrap(); + }); trace!(target: VALGRIND_EXECUTION_TARGET, "{}{}", prefix, String::from_utf8_lossy(&buffer[..bytes_read])); } Ok(()) From f7cd18ce48a3bab0e8d9b053febbd153a269785e Mon Sep 17 00:00:00 2001 From: Adrien Cacciaguerra Date: Thu, 20 Jun 2024 22:05:57 +0000 Subject: [PATCH 2/8] feat(cli): do not show spinner on non tty --- Cargo.lock | 21 +++++++++++++++++++++ Cargo.toml | 1 + src/logger.rs | 27 +++++++++++++++++++++++---- src/run/runner/valgrind.rs | 4 ++-- 4 files changed, 47 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eecf2e6..4b68676 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -120,6 +120,17 @@ dependencies = [ "syn 2.0.66", ] +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -254,6 +265,7 @@ version = "2.4.3" dependencies = [ "anyhow", "async-compression", + "atty", "base64", "clap", "git2", @@ -666,6 +678,15 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "hex" version = "0.4.3" diff --git a/Cargo.toml b/Cargo.toml index 7f0e586..4767ca9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ gql_client = { git = "https://github.com/adriencaccia/gql-client-rs" } serde_yaml = "0.9.34" sysinfo = { version = "0.30.12", features = ["serde"] } indicatif = "0.17.8" +atty = "0.2.14" [dev-dependencies] temp-env = { version = "0.3.6", features = ["async_closure"] } diff --git a/src/logger.rs b/src/logger.rs index 3255d6b..5fd3707 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -86,7 +86,20 @@ lazy_static! { spinner.set_style(ProgressStyle::with_template("{spinner:.cyan} {wide_msg}").unwrap()); spinner }; + pub static ref IS_TTY: bool = atty::is(atty::Stream::Stdout); } + +/// Hide the progress bar temporarily, execute `f`, then redraw the progress bar. +/// +/// If the output is not a TTY, `f` will be executed without hiding the progress bar. +pub fn suspend_progress_bar R, R>(f: F) -> R { + if *IS_TTY { + SPINNER.suspend(f) + } else { + f() + } +} + pub struct LocalLogger { log_level: log::LevelFilter, } @@ -115,18 +128,24 @@ impl Log for LocalLogger { if let Some(group_event) = get_group_event(record) { match group_event { GroupEvent::Start(name) | GroupEvent::StartOpened(name) => { - SPINNER.set_message(format!("{}...", name)); - SPINNER.enable_steady_tick(Duration::from_millis(100)); + if *IS_TTY { + SPINNER.set_message(format!("{}...", name)); + SPINNER.enable_steady_tick(Duration::from_millis(100)); + } else { + println!("{}...", name); + } } GroupEvent::End => { - SPINNER.reset(); + if *IS_TTY { + SPINNER.reset(); + } } } return; } - SPINNER.suspend(|| { + suspend_progress_bar(|| { if record.level() == log::Level::Error { eprintln!("{}", record.args()); } else { diff --git a/src/run/runner/valgrind.rs b/src/run/runner/valgrind.rs index 53ec0de..e7f8909 100644 --- a/src/run/runner/valgrind.rs +++ b/src/run/runner/valgrind.rs @@ -1,4 +1,4 @@ -use crate::logger::SPINNER; +use crate::logger::suspend_progress_bar; use crate::prelude::*; use crate::run::{ config::Config, instruments::mongo_tracer::MongoTracer, @@ -73,7 +73,7 @@ fn run_command_with_log_pipe(mut cmd: Command) -> Result { if bytes_read == 0 { break; } - SPINNER.suspend(|| { + suspend_progress_bar(|| { writer.write_all(&buffer[..bytes_read]).unwrap(); }); trace!(target: VALGRIND_EXECUTION_TARGET, "{}{}", prefix, String::from_utf8_lossy(&buffer[..bytes_read])); From 37b99e07c9562282b1d3f6e3b665fa8112df394b Mon Sep 17 00:00:00 2001 From: Adrien Cacciaguerra Date: Mon, 1 Jul 2024 12:57:17 +0000 Subject: [PATCH 3/8] feat(auth): add log groups --- src/auth.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/auth.rs b/src/auth.rs index bf51727..721d1b5 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -18,7 +18,6 @@ enum AuthCommands { Login, } -// TODO: tweak the logger to make it more user-friendly fn init_logger() -> Result<()> { let logger = get_local_logger(); CombinedLogger::init(vec![logger])?; @@ -38,14 +37,16 @@ const LOGIN_SESSION_MAX_DURATION: Duration = Duration::from_secs(60 * 5); // 5 m async fn login(api_client: &CodSpeedAPIClient) -> Result<()> { debug!("Login to CodSpeed"); - debug!("Creating login session..."); + start_group!("Creating login session"); let login_session_payload = api_client.create_login_session().await?; + end_group!(); + info!( - "Login session created, open the following URL in your browser: {}", + "Login session created, open the following URL in your browser: {}\n", login_session_payload.callback_url ); - info!("Waiting for the login to be completed..."); + start_group!("Waiting for the login to be completed"); let token; let start = Instant::now(); loop { @@ -65,7 +66,7 @@ async fn login(api_client: &CodSpeedAPIClient) -> Result<()> { None => sleep(Duration::from_secs(5)).await, } } - debug!("Login completed"); + end_group!(); let mut config = CodSpeedConfig::load()?; config.auth.token = Some(token); From 3ea75bee488380eb35656d1f4c2a707d7604fb41 Mon Sep 17 00:00:00 2001 From: Adrien Cacciaguerra Date: Mon, 1 Jul 2024 12:58:53 +0000 Subject: [PATCH 4/8] refactor(local): use a single spinner instance per group --- src/logger.rs | 43 +++++++++++++++++++++++++++++------------ src/run/poll_results.rs | 1 - 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/logger.rs b/src/logger.rs index 5fd3707..3775b49 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -1,4 +1,8 @@ -use std::{env, time::Duration}; +use std::{ + env, + sync::{Arc, Mutex}, + time::Duration, +}; use indicatif::{ProgressBar, ProgressStyle}; use lazy_static::lazy_static; @@ -81,11 +85,7 @@ pub(super) fn get_group_event(record: &log::Record) -> Option { } lazy_static! { - pub static ref SPINNER: ProgressBar = { - let spinner = ProgressBar::new_spinner(); - spinner.set_style(ProgressStyle::with_template("{spinner:.cyan} {wide_msg}").unwrap()); - spinner - }; + pub static ref SPINNER: Arc>> = Arc::new(Mutex::new(None)); pub static ref IS_TTY: bool = atty::is(atty::Stream::Stdout); } @@ -93,11 +93,17 @@ lazy_static! { /// /// If the output is not a TTY, `f` will be executed without hiding the progress bar. pub fn suspend_progress_bar R, R>(f: F) -> R { + // If the output is a TTY, and there is a spinner, suspend it if *IS_TTY { - SPINNER.suspend(f) - } else { - f() + if let Ok(mut spinner) = SPINNER.lock() { + if let Some(spinner) = spinner.as_mut() { + return spinner.suspend(f); + } + } } + + // Otherwise, just run the function + f() } pub struct LocalLogger { @@ -129,15 +135,28 @@ impl Log for LocalLogger { match group_event { GroupEvent::Start(name) | GroupEvent::StartOpened(name) => { if *IS_TTY { - SPINNER.set_message(format!("{}...", name)); - SPINNER.enable_steady_tick(Duration::from_millis(100)); + let spinner = ProgressBar::new_spinner(); + spinner.set_style( + ProgressStyle::with_template( + " {spinner:>.cyan} {wide_msg:.magenta.bold}", + ) + .unwrap(), + ); + spinner.set_message(format!("{}...", name)); + spinner.enable_steady_tick(Duration::from_millis(100)); + SPINNER.lock().unwrap().replace(spinner); } else { println!("{}...", name); } } GroupEvent::End => { if *IS_TTY { - SPINNER.reset(); + let mut spinner = SPINNER.lock().unwrap(); + if let Some(spinner) = spinner.as_mut() { + spinner.finish_and_clear(); + // Separate groups with a newline + println!(); + } } } } diff --git a/src/run/poll_results.rs b/src/run/poll_results.rs index 1ee72ba..3ee42c6 100644 --- a/src/run/poll_results.rs +++ b/src/run/poll_results.rs @@ -31,7 +31,6 @@ pub async fn poll_results( }; let run; - info!("Polling results..."); loop { if start.elapsed() > RUN_PROCESSING_MAX_DURATION { bail!("Polling results timed out"); From bd3bc0e27be7a0dee5adfc65e1be29f35e0a9e0a Mon Sep 17 00:00:00 2001 From: Adrien Cacciaguerra Date: Tue, 2 Jul 2024 10:01:50 +0000 Subject: [PATCH 5/8] feat(cli): add console colors in local logger --- Cargo.lock | 73 +++------------------------------------------------ Cargo.toml | 1 + src/logger.rs | 34 ++++++++++++++++++------ 3 files changed, 31 insertions(+), 77 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4b68676..cafa289 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -268,6 +268,7 @@ dependencies = [ "atty", "base64", "clap", + "console", "git2", "gql_client", "indicatif", @@ -303,15 +304,15 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "console" -version = "0.15.7" +version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" dependencies = [ "encode_unicode", "lazy_static", "libc", "unicode-width", - "windows-sys 0.45.0", + "windows-sys 0.52.0", ] [[package]] @@ -2280,15 +2281,6 @@ dependencies = [ "windows-targets 0.52.0", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -2307,21 +2299,6 @@ dependencies = [ "windows-targets 0.52.0", ] -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-targets" version = "0.48.5" @@ -2352,12 +2329,6 @@ dependencies = [ "windows_x86_64_msvc 0.52.0", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -2370,12 +2341,6 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -2388,12 +2353,6 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -2406,12 +2365,6 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -2424,12 +2377,6 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -2442,12 +2389,6 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -2460,12 +2401,6 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" diff --git a/Cargo.toml b/Cargo.toml index 4767ca9..3c0409e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,7 @@ serde_yaml = "0.9.34" sysinfo = { version = "0.30.12", features = ["serde"] } indicatif = "0.17.8" atty = "0.2.14" +console = "0.15.8" [dev-dependencies] temp-env = { version = "0.3.6", features = ["async_closure"] } diff --git a/src/logger.rs b/src/logger.rs index 3775b49..c87b9c0 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -4,6 +4,7 @@ use std::{ time::Duration, }; +use console::Style; use indicatif::{ProgressBar, ProgressStyle}; use lazy_static::lazy_static; use log::Log; @@ -138,7 +139,7 @@ impl Log for LocalLogger { let spinner = ProgressBar::new_spinner(); spinner.set_style( ProgressStyle::with_template( - " {spinner:>.cyan} {wide_msg:.magenta.bold}", + " {spinner:>.cyan} {wide_msg:.cyan.bold}", ) .unwrap(), ); @@ -164,13 +165,7 @@ impl Log for LocalLogger { return; } - suspend_progress_bar(|| { - if record.level() == log::Level::Error { - eprintln!("{}", record.args()); - } else { - println!("{}", record.args()); - } - }); + suspend_progress_bar(|| print_record(record)); } fn flush(&self) { @@ -178,6 +173,29 @@ impl Log for LocalLogger { } } +/// Print a log record to the console with the appropriate style +fn print_record(record: &log::Record) { + let error_style = Style::new().red(); + let info_style = Style::new().white(); + let warn_style = Style::new().yellow(); + let debug_style = Style::new().blue().dim(); + let trace_style = Style::new().black().dim(); + + match record.level() { + log::Level::Error => eprintln!("{}", error_style.apply_to(record.args())), + log::Level::Warn => eprintln!("{}", warn_style.apply_to(record.args())), + log::Level::Info => println!("{}", info_style.apply_to(record.args())), + log::Level::Debug => println!( + "{}", + debug_style.apply_to(format!("[DEBUG::{}] {}", record.target(), record.args())), + ), + log::Level::Trace => println!( + "{}", + trace_style.apply_to(format!("[TRACE::{}] {}", record.target(), record.args())) + ), + } +} + impl SharedLogger for LocalLogger { fn level(&self) -> log::LevelFilter { self.log_level From 4d387d54ceb8e198b8da99e5617fc5b6d406a24a Mon Sep 17 00:00:00 2001 From: Adrien Cacciaguerra Date: Tue, 2 Jul 2024 10:02:29 +0000 Subject: [PATCH 6/8] feat(auth): style auth link log --- src/auth.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/auth.rs b/src/auth.rs index 721d1b5..633fd21 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -3,6 +3,7 @@ use std::time::Duration; use crate::logger::get_local_logger; use crate::{api_client::CodSpeedAPIClient, config::CodSpeedConfig, prelude::*}; use clap::{Args, Subcommand}; +use console::style; use simplelog::CombinedLogger; use tokio::time::{sleep, Instant}; @@ -43,7 +44,10 @@ async fn login(api_client: &CodSpeedAPIClient) -> Result<()> { info!( "Login session created, open the following URL in your browser: {}\n", - login_session_payload.callback_url + style(login_session_payload.callback_url) + .blue() + .bold() + .underlined() ); start_group!("Waiting for the login to be completed"); From 3b0bc04cb26baeec9ec1c6e9635022f0eaae5da4 Mon Sep 17 00:00:00 2001 From: Adrien Cacciaguerra Date: Tue, 2 Jul 2024 10:03:37 +0000 Subject: [PATCH 7/8] feat(runner/poll_results): add regressions threshold, colors and better style to logs --- src/api_client.rs | 56 +++++++++++++++++++++++------ src/queries/FetchLocalRunReport.gql | 3 ++ src/run/poll_results.rs | 36 +++++++++++++------ 3 files changed, 74 insertions(+), 21 deletions(-) diff --git a/src/api_client.rs b/src/api_client.rs index cbaf8c1..13ec059 100644 --- a/src/api_client.rs +++ b/src/api_client.rs @@ -1,5 +1,8 @@ +use std::fmt::Display; + use crate::prelude::*; use crate::{app::Cli, config::CodSpeedConfig}; +use console::style; use gql_client::{Client as GQLClient, ClientConfig}; use nestify::nest; use serde::{Deserialize, Serialize}; @@ -77,6 +80,22 @@ pub enum ReportConclusion { MissingBaseRun, Success, } + +impl Display for ReportConclusion { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ReportConclusion::AcknowledgedFailure => { + write!(f, "{}", style("Acknowledged Failure").yellow().bold()) + } + ReportConclusion::Failure => write!(f, "{}", style("Failure").red().bold()), + ReportConclusion::MissingBaseRun => { + write!(f, "{}", style("Missing Base Run").yellow().bold()) + } + ReportConclusion::Success => write!(f, "{}", style("Success").green().bold()), + } + } +} + #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct FetchLocalRunReportHeadReport { @@ -103,11 +122,19 @@ nest! { #[serde(rename_all = "camelCase")]* struct FetchLocalRunReportData { repository: pub struct FetchLocalRunReportRepository { - pub runs: Vec, + settings: struct FetchLocalRunReportSettings { + allowed_regression: f64, + }, + runs: Vec, } } } +pub struct FetchLocalRunReportResponse { + pub allowed_regression: f64, + pub run: FetchLocalRunReportRun, +} + impl CodSpeedAPIClient { pub async fn create_login_session(&self) -> Result { let response = self @@ -142,7 +169,7 @@ impl CodSpeedAPIClient { pub async fn fetch_local_run_report( &self, vars: FetchLocalRunReportVars, - ) -> Result { + ) -> Result { let response = self .gql_client .query_with_vars_unwrap::( @@ -151,15 +178,22 @@ impl CodSpeedAPIClient { ) .await; match response { - Ok(response) => match response.repository.runs.into_iter().next() { - Some(run) => Ok(run), - None => bail!( - "No runs found for owner: {}, name: {}, run_id: {}", - vars.owner, - vars.name, - vars.run_id - ), - }, + Ok(response) => { + let allowed_regression = response.repository.settings.allowed_regression; + + match response.repository.runs.into_iter().next() { + Some(run) => Ok(FetchLocalRunReportResponse { + allowed_regression, + run, + }), + None => bail!( + "No runs found for owner: {}, name: {}, run_id: {}", + vars.owner, + vars.name, + vars.run_id + ), + } + } Err(err) => bail!("Failed to fetch local run report: {}", err), } } diff --git a/src/queries/FetchLocalRunReport.gql b/src/queries/FetchLocalRunReport.gql index 9f0959a..e0b1f63 100644 --- a/src/queries/FetchLocalRunReport.gql +++ b/src/queries/FetchLocalRunReport.gql @@ -1,5 +1,8 @@ query FetchLocalRunReport($owner: String!, $name: String!, $runId: String!) { repository(owner: $owner, name: $name) { + settings { + allowedRegression + } runs(where: { id: { equals: $runId } }) { id status diff --git a/src/run/poll_results.rs b/src/run/poll_results.rs index 3ee42c6..a17b789 100644 --- a/src/run/poll_results.rs +++ b/src/run/poll_results.rs @@ -1,10 +1,11 @@ use std::time::Duration; +use console::style; use tokio::time::{sleep, Instant}; use url::Url; use crate::api_client::{ - CodSpeedAPIClient, FetchLocalRunReportRun, FetchLocalRunReportVars, RunStatus, + CodSpeedAPIClient, FetchLocalRunReportResponse, FetchLocalRunReportVars, RunStatus, }; use crate::prelude::*; @@ -30,7 +31,7 @@ pub async fn poll_results( run_id: run_id.clone(), }; - let run; + let response; loop { if start.elapsed() > RUN_PROCESSING_MAX_DURATION { bail!("Polling results timed out"); @@ -40,17 +41,18 @@ pub async fn poll_results( .fetch_local_run_report(fetch_local_run_report_vars.clone()) .await? { - FetchLocalRunReportRun { status, .. } if status != RunStatus::Completed => { + FetchLocalRunReportResponse { run, .. } if run.status != RunStatus::Completed => { sleep(Duration::from_secs(5)).await; } - run_from_api => { - run = run_from_api; + reponse_from_api => { + response = reponse_from_api; break; } } } - let report = run + let report = response + .run .head_reports .into_iter() .next() @@ -58,14 +60,28 @@ pub async fn poll_results( info!("Report completed, here are the results:"); if let Some(impact) = report.impact { - info!("Impact: {}%", (impact * 100.0).round()); + let rounded_impact = (impact * 100.0).round(); + let impact_text = if impact > 0.0 { + style(format!("+{}%", rounded_impact)).green().bold() + } else { + style(format!("{}%", rounded_impact)).red().bold() + }; + + info!( + "Impact: {} (allowed regression: -{}%)", + impact_text, + (response.allowed_regression * 100.0).round() + ); } - info!("Conclusion: {:?}", report.conclusion); + info!("Conclusion: {}", report.conclusion); let mut report_url = Url::parse(config.frontend_url.as_str())?; - report_url.set_path(format!("{}/{}/runs/{}", owner, name, run.id).as_str()); + report_url.set_path(format!("{}/{}/runs/{}", owner, name, response.run.id).as_str()); - info!("\nTo see the full report, visit: {}", report_url); + info!( + "\nTo see the full report, visit: {}", + style(report_url).blue().bold().underlined() + ); Ok(()) } From 1d20e031f8e4a595dd9c7880e5453796e3480dd2 Mon Sep 17 00:00:00 2001 From: Adrien Cacciaguerra Date: Wed, 10 Jul 2024 08:29:41 +0200 Subject: [PATCH 8/8] feat(runner/poll_results): poll every second --- src/run/poll_results.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/run/poll_results.rs b/src/run/poll_results.rs index a17b789..d09dbe7 100644 --- a/src/run/poll_results.rs +++ b/src/run/poll_results.rs @@ -13,6 +13,7 @@ use super::ci_provider::CIProvider; use super::config::Config; const RUN_PROCESSING_MAX_DURATION: Duration = Duration::from_secs(60 * 5); // 5 minutes +const POLLING_INTERVAL: Duration = Duration::from_secs(1); #[allow(clippy::borrowed_box)] pub async fn poll_results( @@ -42,10 +43,10 @@ pub async fn poll_results( .await? { FetchLocalRunReportResponse { run, .. } if run.status != RunStatus::Completed => { - sleep(Duration::from_secs(5)).await; + sleep(POLLING_INTERVAL).await; } - reponse_from_api => { - response = reponse_from_api; + response_from_api => { + response = response_from_api; break; } }