diff --git a/docs/configuration.md b/docs/configuration.md index b3a69dbb41..daa8b5653d 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -566,6 +566,14 @@ to store the logs but not have them litter your display. Display HTTP requests/responses in the logs. +### `MISE_LOG_VERBOSE_DEPS=1` + +Debug and trace logs from noisy third-party crates (`h2`, `hyper`, +`reqwest`, `rustls`, etc., which emit a line per HTTP/2 frame or socket +read) are always dropped — they would otherwise overwhelm debug/trace +output. Set this to `1` to let those logs through; it is the only way to +see them, including under `--log-level=trace`/`-vv`. + ### `MISE_QUIET=1` Equivalent to `MISE_LOG_LEVEL=warn`. diff --git a/src/env.rs b/src/env.rs index d6fd2e15a5..acd4b6bc3f 100644 --- a/src/env.rs +++ b/src/env.rs @@ -355,6 +355,7 @@ pub static MISE_SELF_UPDATE_DISABLED_PATH: Lazy> = Lazy::new(|| ) }); pub static MISE_LOG_HTTP: Lazy = Lazy::new(|| var_is_true("MISE_LOG_HTTP")); +pub static MISE_LOG_VERBOSE_DEPS: Lazy = Lazy::new(|| var_is_true("MISE_LOG_VERBOSE_DEPS")); pub static __USAGE: Lazy> = Lazy::new(|| var("__USAGE").ok()); diff --git a/src/logger.rs b/src/logger.rs index 65bf33cca8..597cfa61d4 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -1,9 +1,10 @@ use crate::config::{Config, Settings}; use clx::progress; use eyre::Result; +use std::collections::HashSet; use std::fs::{File, OpenOptions, create_dir_all}; use std::path::Path; -use std::sync::Mutex; +use std::sync::{LazyLock, Mutex}; use std::thread; use std::{io::Write, sync::OnceLock}; @@ -18,12 +19,52 @@ struct Logger { log_file: Option>, } +/// Root crate names of third-party dependencies that emit very noisy debug +/// and trace logs (often per HTTP/2 frame, per socket read, etc.) and would +/// otherwise overwhelm `-v`/`-vv` output. Debug and Trace records from these +/// crates are dropped entirely unless `MISE_LOG_VERBOSE_DEPS=1` is set. +/// Info/Warn/Error still pass through — those are rare and worth seeing. +static NOISY_DEP_TARGETS: LazyLock> = LazyLock::new(|| { + [ + "h2", + "hyper", + "hyper_util", + "mio", + "reqwest", + "rustls", + "tokio_util", + "tower", + "want", + ] + .into_iter() + .collect() +}); + +fn is_noisy_dep_target(target: &str) -> bool { + // `log` targets default to the module path (e.g. "h2::proto::streams"). + // Match on the crate-root segment so we don't accidentally match an + // unrelated crate whose name happens to start with one of ours + // (e.g. "h2extra") — zero allocation: just splits the input slice. + let root = target.split_once("::").map_or(target, |(r, _)| r); + NOISY_DEP_TARGETS.contains(root) +} + impl log::Log for Logger { fn enabled(&self, metadata: &Metadata) -> bool { metadata.level() <= *self.level.lock().unwrap() } fn log(&self, record: &Record) { + // Drop Debug/Trace spam from noisy third-party crates (e.g. h2 logging + // every received DATA frame) regardless of terminal/file level. Opt + // back in with MISE_LOG_VERBOSE_DEPS=1. + if matches!(record.level(), Level::Debug | Level::Trace) + && !*env::MISE_LOG_VERBOSE_DEPS + && is_noisy_dep_target(record.target()) + { + return; + } + let term_level = *self.term_level.lock().unwrap(); let will_log_file = record.level() <= self.file_level && self.log_file.is_some(); let will_log_term = record.level() <= term_level;