diff --git a/Cargo.lock b/Cargo.lock index cc55b4a..30f78ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -307,6 +307,7 @@ dependencies = [ "flate2", "futures-util", "libc", + "log", "nix", "reqwest", "serde", @@ -329,6 +330,7 @@ dependencies = [ "clap", "ecr", "indicatif", + "log", ] [[package]] diff --git a/crates/ecr-cli/Cargo.toml b/crates/ecr-cli/Cargo.toml index 60a4693..9f0d75b 100644 --- a/crates/ecr-cli/Cargo.toml +++ b/crates/ecr-cli/Cargo.toml @@ -22,5 +22,8 @@ clap = { version = "4", features = ["derive", "env"] } # Error handling anyhow = "1" +# Logging facade; the stderr sink lives in src/logger.rs +log = { version = "0.4", features = ["std"] } + # Progress bar rendering of library progress events indicatif = "0.18" diff --git a/crates/ecr-cli/src/logger.rs b/crates/ecr-cli/src/logger.rs new file mode 100644 index 0000000..512dbba --- /dev/null +++ b/crates/ecr-cli/src/logger.rs @@ -0,0 +1,47 @@ +//! stderr logging sink: `Warn`/`Error` records always, `Debug` +//! diagnostics only with -v. + +use log::{Level, LevelFilter, Log, Metadata, Record}; + +struct CliLogger { + max: LevelFilter, +} + +impl Log for CliLogger { + fn enabled(&self, metadata: &Metadata) -> bool { + if metadata.level() > self.max { + return false; + } + match metadata.level() { + Level::Warn | Level::Error => true, + // Dependencies (hyper, rustls, …) share the log facade and are + // far noisier than we are; -v follows only our own diagnostics. + _ => metadata.target().starts_with("ecr"), + } + } + + fn log(&self, record: &Record) { + if !self.enabled(record.metadata()) { + return; + } + match record.level() { + Level::Error => eprintln!("Error: {}", record.args()), + Level::Warn => eprintln!("Warning: {}", record.args()), + _ => eprintln!("{}", record.args()), + } + } + + fn flush(&self) {} +} + +/// Install the CLI logger: warnings and errors always, `ecr` debug +/// diagnostics only when `verbose` (-v). +pub fn init(verbose: bool) { + let max = if verbose { + LevelFilter::Debug + } else { + LevelFilter::Warn + }; + log::set_boxed_logger(Box::new(CliLogger { max })).ok(); + log::set_max_level(max); +} diff --git a/crates/ecr-cli/src/main.rs b/crates/ecr-cli/src/main.rs index 0e638cc..7a60dfd 100644 --- a/crates/ecr-cli/src/main.rs +++ b/crates/ecr-cli/src/main.rs @@ -1,4 +1,5 @@ mod cli; +mod logger; mod progress; use std::path::{Path, PathBuf}; @@ -11,14 +12,14 @@ use ecr::chroot; use ecr::config::Config; use ecr::exec::ExecOptions; use ecr::mount::BindTarget; -use ecr::{kernel, qemu, qemu_vm, utils, veprintln, verbose, PrepareRequest, RootfsCache}; +use ecr::{kernel, qemu, qemu_vm, utils, PrepareRequest, RootfsCache}; use progress::TermProgress; fn main() -> Result<()> { let args = Args::parse(); - // Initialise verbosity before anything else so all downstream code can use veprintln!. - verbose::set(args.verbose); + // Install the logger before anything else so all downstream code can log. + logger::init(args.verbose); // Render library progress (bars/spinners) on the terminal let progress = TermProgress::new(); @@ -63,11 +64,11 @@ fn main() -> Result<()> { // QEMU system mode let kernel_path = match kernel_opt { Some(path) => { - veprintln!("QEMU mode: using provided kernel {}", path.display()); + log::debug!("QEMU mode: using provided kernel {}", path.display()); path.clone() } None => { - veprintln!("QEMU mode: downloading default kernel..."); + log::debug!("QEMU mode: downloading default kernel..."); kernel::get_default_kernel(cache.dir(), &arch, &progress)? } }; @@ -83,6 +84,7 @@ fn main() -> Result<()> { kernel_path, rootfs_path: rootfs.dir().to_path_buf(), memory: args.memory.clone(), + quiet: !args.verbose, arch: arch.clone(), command, }, @@ -91,7 +93,7 @@ fn main() -> Result<()> { // Cleanup happens automatically via tempfile if result.is_ok() { - veprintln!("Cleanup complete."); + log::debug!("Cleanup complete."); } result @@ -123,7 +125,7 @@ fn namespace_mode(args: &Args, rootfs: &Path, config: &Config, arch: &str) -> Re // Cleanup happens automatically via tempfile if exit_code.is_ok() { - veprintln!("Cleanup complete."); + log::debug!("Cleanup complete."); } exit_code diff --git a/crates/ecr/Cargo.toml b/crates/ecr/Cargo.toml index 743d3e9..6fe4a45 100644 --- a/crates/ecr/Cargo.toml +++ b/crates/ecr/Cargo.toml @@ -21,6 +21,9 @@ flate2 = "1" xz2 = "0.1" zstd = "0.13" +# Logging facade (the CLI installs the sink) +log = "0.4" + # Unix syscall bindings nix = { version = "0.31", features = ["fs", "mount", "sched", "signal", "user", "process", "hostname"] } diff --git a/crates/ecr/src/chroot.rs b/crates/ecr/src/chroot.rs index 255e067..d6b1811 100644 --- a/crates/ecr/src/chroot.rs +++ b/crates/ecr/src/chroot.rs @@ -1,4 +1,3 @@ -use crate::veprintln; use anyhow::{anyhow, Context, Result}; use nix::unistd::{chroot, execve}; use std::os::unix::ffi::OsStrExt; @@ -39,7 +38,7 @@ pub fn run_chroot( ) -> Result<()> { // Set hostname in UTS namespace if let Err(e) = crate::namespace::set_hostname("chroot") { - eprintln!("Warning: Failed to set hostname: {}", e); + log::warn!("Failed to set hostname: {}", e); } // Detect shell before chroot (we're still outside) @@ -90,8 +89,8 @@ pub fn run_chroot( })?; // Print welcome message - veprintln!("Entering chroot at {}", rootfs.display()); - veprintln!("Working directory: {}", working_dir.display()); + log::debug!("Entering chroot at {}", rootfs.display()); + log::debug!("Working directory: {}", working_dir.display()); // execve(2) does not search PATH: resolve the program through the // caller-composed environment like execvp(3) would. diff --git a/crates/ecr/src/download.rs b/crates/ecr/src/download.rs index 4cece4c..0bea6d9 100644 --- a/crates/ecr/src/download.rs +++ b/crates/ecr/src/download.rs @@ -1,5 +1,4 @@ use crate::progress::{Progress, Unit}; -use crate::veprintln; use anyhow::{anyhow, Context, Result}; use futures_util::StreamExt; use reqwest::Client; @@ -88,7 +87,7 @@ pub fn download_image( match source { ImageSource::DirectTarball { distro, version } => { let url = crate::distro::resolve_distro_url(distro, version.as_deref(), arch)?; - veprintln!("Resolved URL: {}", url); + log::debug!("Resolved URL: {}", url); if !url.starts_with("http://") && !url.starts_with("https://") { return Err(anyhow!("Unsupported URL scheme: {}", url)); } @@ -100,7 +99,7 @@ pub fn download_image( tag, architecture, } => { - veprintln!("Pulling OCI image: {}/{}:{}", registry, repository, tag); + log::debug!("Pulling OCI image: {}/{}:{}", registry, repository, tag); rt.block_on(download_oci_image_async( registry, repository, @@ -114,7 +113,7 @@ pub fn download_image( } async fn download_file_async(url: &str, dest: &Path, progress: &dyn Progress) -> Result<()> { - veprintln!("Downloading: {}", url); + log::debug!("Downloading: {}", url); let client = Client::builder() .timeout(Duration::from_secs(300)) @@ -168,7 +167,7 @@ async fn download_file_async(url: &str, dest: &Path, progress: &dyn Progress) -> tokio::fs::rename(&temp_dest, dest) .await .context("Failed to move partial download to final destination")?; - veprintln!("Download complete: {}", dest.display()); + log::debug!("Download complete: {}", dest.display()); Ok(()) } @@ -230,7 +229,7 @@ async fn try_download_oci_image( format!("https://{}/v2/{}/manifests/{}", registry, repository, tag) }; - veprintln!("Fetching manifest from: {}", manifest_url); + log::debug!("Fetching manifest from: {}", manifest_url); // Request both manifest list and single manifest types (including OCI formats) let response = client @@ -265,7 +264,7 @@ async fn try_download_oci_image( // Check if this is a manifest list (multi-arch) let layers = if let Some(manifests) = manifest["manifests"].as_array() { // This is a manifest list - find the right architecture - veprintln!("Got manifest list with {} manifests", manifests.len()); + log::debug!("Got manifest list with {} manifests", manifests.len()); let manifest_entry = manifests .iter() @@ -291,7 +290,7 @@ async fn try_download_oci_image( .as_str() .ok_or_else(|| anyhow!("No digest in manifest entry"))?; - veprintln!("Found manifest digest: {}", manifest_digest); + log::debug!("Found manifest digest: {}", manifest_digest); // Now get the actual manifest for this architecture let arch_manifest_url = if registry == "docker.io" { @@ -338,7 +337,7 @@ async fn try_download_oci_image( .clone() }; - veprintln!("Found {} layers to download", layers.len()); + log::debug!("Found {} layers to download", layers.len()); // Download all layer blobs into a temp directory let temp_dir = tempfile::tempdir()?; @@ -358,7 +357,7 @@ async fn try_download_oci_image( let ext = media_type_to_extension(media_type); let layer_name = format!("layer_{}.{}", i, ext); - veprintln!( + log::debug!( "Fetching layer {}/{}: {}", i + 1, layers.len(), @@ -440,7 +439,7 @@ async fn get_auth_token(client: &Client, registry: &str, repository: &str) -> Re "https://auth.docker.io/token?service=registry.docker.io&scope=repository:{}:pull", repository ); - veprintln!("Getting auth token from: {}", url); + log::debug!("Getting auth token from: {}", url); return fetch_bearer_token(client, &url).await; } @@ -464,8 +463,8 @@ async fn get_auth_token(client: &Client, registry: &str, repository: &str) -> Re other => { // Unexpected status; proceed without a token and let the manifest // request fail with a more descriptive error. - eprintln!( - "Warning: registry probe returned HTTP {}; trying without auth", + log::warn!( + "registry probe returned HTTP {}; trying without auth", other ); return Ok(String::new()); @@ -492,7 +491,7 @@ async fn get_auth_token(client: &Client, registry: &str, repository: &str) -> Re "{}?service={}&scope=repository:{}:pull", realm, service, repository ); - veprintln!("Getting auth token from: {}", token_url); + log::debug!("Getting auth token from: {}", token_url); fetch_bearer_token(client, &token_url).await } diff --git a/crates/ecr/src/exec.rs b/crates/ecr/src/exec.rs index 4eedae8..4e14027 100644 --- a/crates/ecr/src/exec.rs +++ b/crates/ecr/src/exec.rs @@ -17,7 +17,6 @@ use crate::mount::{self, BindTarget}; use crate::namespace; use crate::qemu; use crate::utils::{self, Arch}; -use crate::veprintln; /// Options for [`exec`]. #[derive(Debug, Clone)] @@ -99,7 +98,7 @@ pub fn exec(rootfs: &Path, opts: &ExecOptions) -> Result { for bind in &opts.binds { if !bind.read_only { - veprintln!("Read-write mount: {}", bind.target.display()); + log::debug!("Read-write mount: {}", bind.target.display()); } } diff --git a/crates/ecr/src/extract.rs b/crates/ecr/src/extract.rs index d99b7df..872c92e 100644 --- a/crates/ecr/src/extract.rs +++ b/crates/ecr/src/extract.rs @@ -1,5 +1,4 @@ use crate::progress::{Progress, Unit}; -use crate::veprintln; use anyhow::{Context, Result}; use std::fs::File; use std::io::{BufReader, Read}; @@ -15,7 +14,7 @@ pub fn extract_tarball(tarball: &Path, dest: &Path, progress: &dyn Progress) -> // scanning the archive for a layers.manifest entry (which required // reading the entire compressed archive twice for large OCI images). if filename.starts_with("oci-") { - veprintln!("Detected multi-layer OCI image, extracting layers..."); + log::debug!("Detected multi-layer OCI image, extracting layers..."); return extract_multi_layer_oci(tarball, dest, progress); } @@ -115,7 +114,7 @@ fn extract_multi_layer_oci(tarball: &Path, dest: &Path, progress: &dyn Progress) // Not our layer-bundle layout: a provisioned rootfs persisted over // an OCI cache entry (the dispatch above keys on the "oci-" filename // prefix). The outer archive is a plain rootfs tarball. - veprintln!("No layers.manifest in bundle; extracting as plain rootfs tarball"); + log::debug!("No layers.manifest in bundle; extracting as plain rootfs tarball"); return extract_single_archive(tarball, dest, progress); } @@ -134,7 +133,7 @@ fn extract_multi_layer_oci(tarball: &Path, dest: &Path, progress: &dyn Progress) if !layer_path.exists() { continue; } - veprintln!("Extracting layer: {}", layer_name); + log::debug!("Extracting layer: {}", layer_name); extract_oci_layer(&layer_path, dest, &mut fixups, progress)?; } @@ -324,7 +323,7 @@ fn apply_hard_link(dest: &Path, dst: &Path, target: &Path) -> Result<()> { } std::os::unix::fs::symlink(target, dst) .with_context(|| format!("Failed to link {} -> {}", dst.display(), target.display()))?; - veprintln!( + log::debug!( "Warning: hard link target {} absent; extracted {} as a symlink", target.display(), dst.display() @@ -421,7 +420,7 @@ fn extract_archive( } progress.finish(); - veprintln!("Extracted {} files", count); + log::debug!("Extracted {} files", count); Ok(()) } diff --git a/crates/ecr/src/kernel.rs b/crates/ecr/src/kernel.rs index 6dc0ebd..eee7a97 100644 --- a/crates/ecr/src/kernel.rs +++ b/crates/ecr/src/kernel.rs @@ -18,7 +18,6 @@ //! only ELF, uImage and raw images. use crate::progress::{Progress, Unit}; -use crate::veprintln; use anyhow::{anyhow, Context, Result}; use std::io::{Read, Seek}; use std::path::{Path, PathBuf}; @@ -112,7 +111,7 @@ fn find_kernel_package(branch: &str, arch: &str) -> Result<(&'static str, String branch, arch ); - veprintln!("Fetching package index: {}", url); + log::debug!("Fetching package index: {}", url); let client = reqwest::blocking::Client::builder() .timeout(Duration::from_secs(30)) @@ -136,7 +135,7 @@ fn find_kernel_package(branch: &str, arch: &str) -> Result<(&'static str, String // Extract APKINDEX from the tar.gz // We need to own the bytes to avoid lifetime issues let bytes_owned = bytes.to_vec(); - veprintln!(" Downloaded {} bytes", bytes_owned.len()); + log::debug!(" Downloaded {} bytes", bytes_owned.len()); // First decompress gzip to memory, then parse tar // Note: Alpine's APKINDEX.tar.gz uses concatenated gzip members (multi-member gzip) @@ -148,7 +147,7 @@ fn find_kernel_package(branch: &str, arch: &str) -> Result<(&'static str, String .read_to_end(&mut decompressed) .context("Failed to decompress gzip")?; - veprintln!(" Decompressed {} bytes", decompressed.len()); + log::debug!(" Decompressed {} bytes", decompressed.len()); let tar_cursor = std::io::Cursor::new(decompressed); let mut archive = tar::Archive::new(tar_cursor); @@ -164,7 +163,7 @@ fn find_kernel_package(branch: &str, arch: &str) -> Result<(&'static str, String let mut entry = match entry_result { Ok(e) => e, Err(e) => { - veprintln!( + log::debug!( " Warning: failed to read tar entry #{}: {}", entry_count, e @@ -176,7 +175,7 @@ fn find_kernel_package(branch: &str, arch: &str) -> Result<(&'static str, String let path = entry.path().context("Failed to get entry path")?; let path_str = path.to_string_lossy(); - veprintln!(" Entry #{}: {}", entry_count, path_str); + log::debug!(" Entry #{}: {}", entry_count, path_str); if path_str == "APKINDEX" { let mut contents = String::new(); @@ -184,7 +183,7 @@ fn find_kernel_package(branch: &str, arch: &str) -> Result<(&'static str, String .read_to_string(&mut contents) .context("Failed to read APKINDEX contents")?; - veprintln!(" APKINDEX size: {} bytes", contents.len()); + log::debug!(" APKINDEX size: {} bytes", contents.len()); // Parse the APKINDEX to find the best kernel package // Format: @@ -228,7 +227,7 @@ fn find_kernel_package(branch: &str, arch: &str) -> Result<(&'static str, String } } - veprintln!(" Total entries processed: {}", entry_count); + log::debug!(" Total entries processed: {}", entry_count); Err(anyhow!( "APKINDEX file not found in tar.gz archive (processed {} entries)", @@ -244,7 +243,7 @@ fn download_alpine_kernel( progress: &dyn Progress, ) -> Result<()> { let (package, version) = find_kernel_package(branch, arch)?; - veprintln!("Found {} version: {}", package, version); + log::debug!("Found {} version: {}", package, version); // Construct the download URL for the kernel .apk // Format: https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/linux-virt-6.12.8-r0.apk @@ -253,7 +252,7 @@ fn download_alpine_kernel( branch, arch, package, version ); - veprintln!("Downloading kernel: {}", url); + log::debug!("Downloading kernel: {}", url); // Use async download via the existing download module pattern let rt = tokio::runtime::Runtime::new().context("Failed to create Tokio runtime")?; @@ -336,7 +335,7 @@ fn extract_kernel_from_apk(apk_path: &Path, dest: &Path, package: &str) -> Resul .ok_or_else(|| anyhow!("unexpected kernel package name: {}", package))?; let kernel_name = format!("boot/vmlinuz-{}", flavor); - veprintln!("Extracting kernel from APK..."); + log::debug!("Extracting kernel from APK..."); let file = std::fs::File::open(apk_path).context("Failed to open APK file")?; // Use MultiGzDecoder because Alpine APKs have concatenated gzip members @@ -348,12 +347,12 @@ fn extract_kernel_from_apk(apk_path: &Path, dest: &Path, package: &str) -> Resul let path = entry.path().context("Failed to get entry path")?; let path_str = path.to_string_lossy(); - veprintln!(" APK entry: {}", path_str); + log::debug!(" APK entry: {}", path_str); if path_str == kernel_name || path_str == format!("./{}", kernel_name) { // Extract to destination entry.unpack(dest).context("Failed to extract kernel")?; - veprintln!(" Extracted kernel to: {}", dest.display()); + log::debug!(" Extracted kernel to: {}", dest.display()); return Ok(()); } } @@ -379,7 +378,7 @@ fn decompress_kernel_if_gzipped(path: &Path) -> Result<()> { return Ok(()); } - veprintln!("Kernel is gzip-compressed, decompressing..."); + log::debug!("Kernel is gzip-compressed, decompressing..."); file.rewind().context("Failed to rewind kernel file")?; let mut decompressed = Vec::new(); flate2::read::MultiGzDecoder::new(file) @@ -407,7 +406,7 @@ pub fn get_default_kernel( // Check if already cached if kernel_path.exists() { - veprintln!("Using cached default kernel: {}", kernel_path.display()); + log::debug!("Using cached default kernel: {}", kernel_path.display()); return Ok(kernel_path); } @@ -416,7 +415,7 @@ pub fn get_default_kernel( // Determine Alpine branch let branch = get_alpine_branch()?; - veprintln!("Using Alpine branch: {}", branch); + log::debug!("Using Alpine branch: {}", branch); // Download and extract the kernel download_alpine_kernel(&branch, alpine_arch, &kernel_path, progress)?; diff --git a/crates/ecr/src/lib.rs b/crates/ecr/src/lib.rs index 50dfc2d..ea9c7bf 100644 --- a/crates/ecr/src/lib.rs +++ b/crates/ecr/src/lib.rs @@ -25,17 +25,6 @@ pub mod qemu; pub mod qemu_vm; pub mod rootfs; pub mod utils; -pub mod verbose; - -/// Print to stderr only when verbose mode is active (see [`verbose::set`]). -#[macro_export] -macro_rules! veprintln { - ($($arg:tt)*) => { - if $crate::verbose::is_verbose() { - eprintln!($($arg)*); - } - }; -} pub use exec::{exec, ExecOptions}; pub use mount::BindTarget; diff --git a/crates/ecr/src/mount.rs b/crates/ecr/src/mount.rs index 236b9cb..6f4d443 100644 --- a/crates/ecr/src/mount.rs +++ b/crates/ecr/src/mount.rs @@ -67,7 +67,7 @@ pub fn setup_mounts(rootfs: &Path, binds: &[BindTarget]) -> Result> MsFlags::MS_PRIVATE | MsFlags::MS_REC, None::<&str>, ) { - eprintln!("Warning: Failed to make mounts private: {}", e); + log::warn!("Failed to make mounts private: {}", e); } // Mount /proc @@ -81,7 +81,7 @@ pub fn setup_mounts(rootfs: &Path, binds: &[BindTarget]) -> Result> // Try to mount /sys (may fail in some environments) if let Err(e) = mount_sys(rootfs) { - eprintln!("Warning: Could not mount /sys: {}", e); + log::warn!("Could not mount /sys: {}", e); } for bind in binds { diff --git a/crates/ecr/src/qemu.rs b/crates/ecr/src/qemu.rs index cabebae..6e2610b 100644 --- a/crates/ecr/src/qemu.rs +++ b/crates/ecr/src/qemu.rs @@ -1,4 +1,3 @@ -use crate::veprintln; use anyhow::{anyhow, Result}; use std::path::Path; @@ -19,6 +18,6 @@ pub fn check_binfmt(arch: &str) -> Result<()> { )); } - veprintln!("QEMU binfmt_misc registered for {}", arch); + log::debug!("QEMU binfmt_misc registered for {}", arch); Ok(()) } diff --git a/crates/ecr/src/qemu_vm.rs b/crates/ecr/src/qemu_vm.rs index 101020d..0ad462c 100644 --- a/crates/ecr/src/qemu_vm.rs +++ b/crates/ecr/src/qemu_vm.rs @@ -1,5 +1,4 @@ use crate::progress::{Progress, Unit}; -use crate::veprintln; use anyhow::{anyhow, Context, Result}; use cpio::{newc, NewcBuilder}; use std::io::Write; @@ -15,6 +14,9 @@ pub struct QemuConfig { pub rootfs_path: PathBuf, /// Memory size for VM (e.g., "2G", "512M") pub memory: String, + /// Append `quiet` to the kernel command line to suppress kernel log + /// messages (the CLI leaves this off under -v). + pub quiet: bool, /// Target architecture pub arch: String, /// Optional command to run instead of default init @@ -65,9 +67,9 @@ pub fn launch_qemu(config: QemuConfig, progress: &dyn Progress) -> Result<()> { // Check if we can use KVM acceleration let use_kvm = can_use_kvm(&config.arch); if use_kvm { - veprintln!(" KVM: enabled (native acceleration)"); + log::debug!(" KVM: enabled (native acceleration)"); } else { - veprintln!(" KVM: disabled (using software emulation)"); + log::debug!(" KVM: disabled (using software emulation)"); } // Detect the best available shell in the rootfs @@ -89,13 +91,9 @@ pub fn launch_qemu(config: QemuConfig, progress: &dyn Progress) -> Result<()> { // Build kernel command line // For initramfs boot, use rdinit= instead of init= // No root= needed as initramfs becomes the rootfs - // 'quiet' suppresses kernel log messages for a cleaner console (removed with -v) + // 'quiet' suppresses kernel log messages for a cleaner console (see QemuConfig::quiet) // The init script (added to initramfs) handles hostname, shell, and poweroff - let quiet_flag = if crate::verbose::is_verbose() { - "" - } else { - " quiet" - }; + let quiet_flag = if config.quiet { " quiet" } else { "" }; let kernel_append = if let Some(ref cmd) = config.command { // The argv travels through the kernel cmdline, where quotes and @@ -119,11 +117,11 @@ pub fn launch_qemu(config: QemuConfig, progress: &dyn Progress) -> Result<()> { ) }; - veprintln!("Launching QEMU: {}", qemu_bin); - veprintln!(" Kernel: {}", config.kernel_path.display()); - veprintln!(" Initramfs: {}", initramfs.display()); - veprintln!(" Memory: {}", config.memory); - veprintln!(" Kernel append: {}", kernel_append); + log::debug!("Launching QEMU: {}", qemu_bin); + log::debug!(" Kernel: {}", config.kernel_path.display()); + log::debug!(" Initramfs: {}", initramfs.display()); + log::debug!(" Memory: {}", config.memory); + log::debug!(" Kernel append: {}", kernel_append); // Build QEMU arguments // -machine virt is selected explicitly on riscv64 (see machine_for_arch) @@ -173,7 +171,7 @@ pub fn launch_qemu(config: QemuConfig, progress: &dyn Progress) -> Result<()> { // Cleanup initramfs if let Err(e) = std::fs::remove_file(&initramfs) { - veprintln!("Warning: failed to cleanup initramfs: {}", e); + log::debug!("Warning: failed to cleanup initramfs: {}", e); } if !status.success() { @@ -308,7 +306,7 @@ fn create_initramfs(rootfs: &Path, progress: &dyn Progress) -> Result { let total_bytes = std::fs::metadata(&initramfs_path) .map(|m| m.len()) .unwrap_or(0); - veprintln!( + log::debug!( "Initramfs created: {} bytes, {} files", total_bytes, file_count @@ -345,7 +343,7 @@ fn write_cpio_archive( &mut total_data, )?; - veprintln!( + log::debug!( "Collected {} entries, {} bytes total data", written_names.len(), total_data @@ -508,7 +506,7 @@ fn write_dir_entries( let dir_entries: Vec<_> = match std::fs::read_dir(current) { Ok(entries) => entries.collect::>()?, Err(e) => { - veprintln!( + log::debug!( "Warning: cannot read directory {}: {}", current.display(), e @@ -526,7 +524,7 @@ fn write_dir_entries( let metadata = match std::fs::symlink_metadata(&path) { Ok(m) => m, Err(e) => { - veprintln!( + log::debug!( "Warning: skipping {} due to metadata error: {}", path.display(), e @@ -577,7 +575,7 @@ fn write_dir_entries( let data = match std::fs::read(&path) { Ok(data) => data, Err(e) => { - veprintln!("Warning: cannot read file {}: {}", path.display(), e); + log::debug!("Warning: cannot read file {}: {}", path.display(), e); continue; } }; @@ -589,7 +587,7 @@ fn write_dir_entries( let data = match std::fs::read(&path) { Ok(data) => data, Err(e) => { - veprintln!("Warning: cannot read file {}: {}", path.display(), e); + log::debug!("Warning: cannot read file {}: {}", path.display(), e); continue; } }; @@ -598,7 +596,7 @@ fn write_dir_entries( match std::fs::read_link(&path) { Ok(target) => (target.to_string_lossy().into_owned().into_bytes(), 1, 0), Err(e) => { - veprintln!("Warning: cannot read symlink {}: {}", path.display(), e); + log::debug!("Warning: cannot read symlink {}: {}", path.display(), e); continue; } } diff --git a/crates/ecr/src/rootfs.rs b/crates/ecr/src/rootfs.rs index 948d760..3f6c15f 100644 --- a/crates/ecr/src/rootfs.rs +++ b/crates/ecr/src/rootfs.rs @@ -48,7 +48,6 @@ use crate::download::{digest_sidecar, download_image, fetch_oci_digest}; use crate::exec::ExecOptions; use crate::extract::extract_tarball; use crate::progress::Progress; -use crate::veprintln; /// Sidecar suffix marking a cache entry that holds a provisioned rootfs. const PROVISIONED_SUFFIX: &str = "provisioned"; @@ -117,10 +116,7 @@ impl RootfsCache { stored.as_deref() != Some(current.trim()) } Err(e) => { - eprintln!( - "Warning: could not check image freshness ({}); using cache", - e - ); + log::warn!("could not check image freshness ({}); using cache", e); false } } @@ -142,7 +138,7 @@ impl RootfsCache { })?; download_image(&source, &cache_path, &arch, progress)?; } else { - veprintln!("Using cached tarball: {}", cache_path.display()); + log::debug!("Using cached tarball: {}", cache_path.display()); } extract_prepared(cache_path, arch, progress) @@ -175,7 +171,7 @@ impl RootfsCache { let marker = sidecar(&cache_path, PROVISIONED_SUFFIX); if !req.no_cache && cache_path.exists() && marker.exists() { - veprintln!("Using provisioned cache entry: {}", cache_path.display()); + log::debug!("Using provisioned cache entry: {}", cache_path.display()); return extract_prepared(cache_path, arch, progress); } @@ -278,7 +274,7 @@ impl PreparedRootfs { self.cache_path.display() ) })?; - veprintln!("Persisted rootfs to cache: {}", self.cache_path.display()); + log::debug!("Persisted rootfs to cache: {}", self.cache_path.display()); Ok(()) } } @@ -309,7 +305,7 @@ fn extract_prepared( let temp = tempfile::tempdir().context("Failed to create temporary directory")?; let rootfs = temp.path().to_path_buf(); - veprintln!("Extracting to: {}", rootfs.display()); + log::debug!("Extracting to: {}", rootfs.display()); extract_tarball(&cache_path, &rootfs, progress)?; Ok(PreparedRootfs { diff --git a/crates/ecr/src/verbose.rs b/crates/ecr/src/verbose.rs deleted file mode 100644 index efa6386..0000000 --- a/crates/ecr/src/verbose.rs +++ /dev/null @@ -1,11 +0,0 @@ -use std::sync::atomic::{AtomicBool, Ordering}; - -static VERBOSE: AtomicBool = AtomicBool::new(false); - -pub fn set(v: bool) { - VERBOSE.store(v, Ordering::Relaxed); -} - -pub fn is_verbose() -> bool { - VERBOSE.load(Ordering::Relaxed) -}