diff --git a/Cargo.lock b/Cargo.lock index 2f45655..ac73fb4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "ansi-control-codes" version = "1.0.1" @@ -237,6 +252,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + [[package]] name = "byteorder" version = "1.5.0" @@ -249,6 +270,15 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +[[package]] +name = "cc" +version = "1.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e" +dependencies = [ + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -261,6 +291,18 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +[[package]] +name = "chrono" +version = "0.4.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "windows-targets 0.52.6", +] + [[package]] name = "clap" version = "4.5.20" @@ -337,6 +379,12 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cpufeatures" version = "0.2.14" @@ -474,7 +522,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" dependencies = [ "log", - "regex", ] [[package]] @@ -486,7 +533,6 @@ dependencies = [ "anstream", "anstyle", "env_filter", - "humantime", "log", ] @@ -524,6 +570,19 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "flexi_logger" +version = "0.29.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a5a6882b2e137c4f2664562995865084eb5a00611fba30c582ef10354c4ad8" +dependencies = [ + "chrono", + "log", + "nu-ansi-term 0.50.1", + "regex", + "thiserror 2.0.9", +] + [[package]] name = "fnv" version = "1.0.7" @@ -768,12 +827,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - [[package]] name = "hyper" version = "1.5.0" @@ -809,6 +862,29 @@ dependencies = [ "tower-service", ] +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "indexmap" version = "2.6.0" @@ -866,6 +942,16 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "js-sys" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "keccak" version = "0.1.5" @@ -931,7 +1017,7 @@ dependencies = [ "colored", "crossterm", "directories", - "env_logger", + "flexi_logger", "futures", "futures-core", "glob", @@ -1096,6 +1182,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "nu-ansi-term" +version = "0.50.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1257,7 +1352,7 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom", "libredox", - "thiserror", + "thiserror 1.0.68", ] [[package]] @@ -1478,6 +1573,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook" version = "0.3.17" @@ -1651,7 +1752,16 @@ version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.68", +] + +[[package]] +name = "thiserror" +version = "2.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" +dependencies = [ + "thiserror-impl 2.0.9", ] [[package]] @@ -1665,6 +1775,17 @@ dependencies = [ "syn", ] +[[package]] +name = "thiserror-impl" +version = "2.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.1.8" @@ -1864,7 +1985,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "matchers", - "nu-ansi-term", + "nu-ansi-term 0.46.0", "once_cell", "regex", "sharded-slab", @@ -1888,7 +2009,7 @@ dependencies = [ "log", "rand", "sha1", - "thiserror", + "thiserror 1.0.68", "utf-8", ] @@ -1976,6 +2097,60 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" + [[package]] name = "winapi" version = "0.3.9" @@ -2007,6 +2182,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/Cargo.toml b/Cargo.toml index 03d16ee..6df97d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,6 @@ futures = "0.3.30" tokio = { version = "1", features = ["full"] } tokio-util = "0.7" async-stream = "0.3" -env_logger = "0.11" log = "0.4" async-condvar-fair = { version = "1.0", features = [ "parking_lot_0_12" ] } # https://docs.rs/async-condvar-fair/latest/async_condvar_fair/#mutex-guard-sending-between-threads @@ -48,6 +47,7 @@ crossterm = {version = "0.28.1", features = ["event-stream"] } schemars = "0.8.21" sha3 = "0.10.8" hex = "0.4.3" +flexi_logger = "0.29.8" [dev-dependencies] test-case = "3.3" diff --git a/src/main.rs b/src/main.rs index 8fe7adc..d296b89 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ use clap::{arg, Parser as _, Subcommand, ValueEnum}; use config::{Config, ParsedConfig}; use dag::{Dag, GraphNode as _}; use database::{Database, DatabaseEntry, DatabaseOutput, LookupResult}; +use flexi_logger::{detailed_format, Cleanup, Criterion, FileSpec, Logger, Naming}; use futures::future::join_all; use futures::StreamExt; use git::{Commit, PersistentWorktree, TempWorktree}; @@ -20,7 +21,7 @@ use std::io::{stdout, Stdout}; use std::path::{absolute, PathBuf}; use std::pin::pin; use std::process::{ExitCode, Stdio}; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, LazyLock, Mutex}; use std::{env, fmt, fs, str}; use tempfile::TempDir; use test::{base_job_env, Manager, TestCase, TestCaseId, TestJob, TestJobBuilder, TestName}; @@ -93,13 +94,12 @@ struct WatchArgs { base: String, } +static PROJECT_DIRS: LazyLock = LazyLock::new(|| { + directories::ProjectDirs::from("", "", "limmat").expect("couldn't find user data dir") +}); + fn default_result_db() -> DisplayablePathBuf { - DisplayablePathBuf( - directories::ProjectDirs::from("", "", "limmat") - .expect("couldn't find user data dir") - .data_local_dir() - .to_owned(), - ) + DisplayablePathBuf(PROJECT_DIRS.data_local_dir().to_owned()) } fn default_hostname() -> String { @@ -636,11 +636,33 @@ async fn artifacts( Ok(ExitCode::SUCCESS) } +const MEGABYTE: u64 = 1024 * 1024; + // Hack so we can use anyhow::Result infrastructure for convenient coding but // also control the exit code directly in some cases: return a result - if it's // an error we just use the default error exit code. async fn do_main() -> anyhow::Result { - env_logger::init(); + let mut logger = Logger::try_with_env_or_str("debug")?.format(detailed_format); + logger = match env::var_os("LIMMAT_LOGFILE") { + Some(path) => logger.log_to_file( + FileSpec::try_from(&path) + .with_context(|| format!("configuring logging to {:?}", path))?, + ), + None => { + let log_dir = PROJECT_DIRS + .state_dir() + .unwrap_or(PROJECT_DIRS.data_local_dir()); + logger + .log_to_file(FileSpec::default().directory(log_dir)) + .create_symlink(log_dir.join("latest.log")) + .rotate( + Criterion::Size(64 * MEGABYTE), + Naming::TimestampsDirect, + Cleanup::KeepLogFiles(10), + ) + } + }; + logger.start()?; // Set up shutdown first, to ensure we correctly handle early signals. // It seems extremely difficult to use Tokio's ctrl_c API for this correctly diff --git a/tests/integration_test.rs b/tests/integration_test.rs index c0fd978..39b7146 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -22,7 +22,7 @@ use nix::{ sys::signal::{kill, Signal}, unistd::Pid, }; -use tempfile::TempDir; +use tempfile::{NamedTempFile, TempDir}; use test_bin::get_test_bin; use test_case::test_case; use tokio::{ @@ -85,11 +85,11 @@ impl<'a> LimmatChildBuilder { let db_dir = temp_dir.path().join("cache"); create_dir(&db_dir).unwrap(); Ok(Self { - temp_dir, repo_dir, db_dir, dump_output_on_panic: true, - env: HashMap::from([("RUST_LOG".into(), "debug".into())]), + env: HashMap::new(), + temp_dir, config: config.as_ref().to_string(), }) } @@ -139,6 +139,11 @@ impl<'a> LimmatChildBuilder { let stderr = File::create(self.temp_dir.path().join("stderr.txt"))?; let stdout = File::create(self.temp_dir.path().join("stdout.txt"))?; + let log_path = NamedTempFile::with_prefix_in("logs-", self.temp_dir.path()) + .unwrap() + .into_temp_path() + .keep() + .unwrap(); let mut cmd: Command = get_test_bin("limmat").into(); let cmd = cmd @@ -161,6 +166,7 @@ impl<'a> LimmatChildBuilder { .stderr(stderr) .stdout(stdout) .envs(&self.env) + .env("LIMMAT_LOGFILE", &log_path) .kill_on_drop(true); let mut child = cmd.spawn().unwrap(); let mut stdin = child.stdin.take().unwrap(); @@ -170,6 +176,7 @@ impl<'a> LimmatChildBuilder { builder: self, child, dump_output_on_panic: self.dump_output_on_panic, + log_path, }) } } @@ -179,6 +186,7 @@ struct LimmatChild<'a> { builder: &'a LimmatChildBuilder, child: Child, dump_output_on_panic: bool, + log_path: PathBuf, } impl<'a> LimmatChild<'a> { @@ -187,8 +195,8 @@ impl<'a> LimmatChild<'a> { let status = self.child.wait().await.expect("error waiting for child"); if !status.success() { if !self.dump_output_on_panic { - eprintln!("Child Limmat process failed, dumping stderr"); - self.dump_stderr(); + eprintln!("Child Limmat process failed, dumping log"); + self.dump_log(); } bail!("Child process didn't succed: {:?}", status); } @@ -200,11 +208,11 @@ impl<'a> LimmatChild<'a> { .context("reading child stdout") } - fn dump_stderr(&self) { - let file = match File::open(self.builder.temp_dir.path().join("stderr.txt")) { + fn dump_log(&self) { + let file = match File::open(&self.log_path) { // Don't panic while panicking, it's messy Err(err) => { - eprintln!("Failed to open stderr file while panicking: {}", err); + eprintln!("Failed to open log file while panicking: {}", err); return; } Ok(file) => file, @@ -227,8 +235,8 @@ impl Drop for LimmatChild<'_> { // subprocesses. So just dump the child's stderr to stdout when // we're panicking. if self.dump_output_on_panic && panicking() { - eprintln!("Panic happening, assuming its a test failure, dumping child stderr."); - self.dump_stderr(); + eprintln!("Panic happening, assuming its a test failure, dumping child log."); + self.dump_log(); } } } @@ -273,7 +281,7 @@ impl<'a> LimmatChild<'a> { Some(0) => return Ok(()), Some(exit_code) => { eprintln!("Dumping failed 'get' command stderr..."); - child.dump_stderr(); + child.dump_log(); bail!("get command failed with code {exit_code}") } }