From c5bab0405041fe74d53ee44c6e2afaa83e1d76d7 Mon Sep 17 00:00:00 2001 From: jdx <216188+jdx@users.noreply.github.com> Date: Sat, 18 Apr 2026 13:52:30 -0500 Subject: [PATCH 1/6] fix(log): suppress noisy third-party debug logs under -v `mise -v` enables debug logging globally, which caused crates like `h2` to emit a line per received HTTP/2 DATA frame (e.g. "received frame=Data { stream_id: StreamId(7) }" repeated hundreds of times for a single large download), drowning out mise's own output. Filter Debug-level records from known-noisy dependency targets (`h2`, `hyper`, `hyper_util`, `reqwest`, `rustls`, `tokio_util`, `tower`, `want`, `mio`) in the logger. Trace level still passes them through, and `MISE_LOG_VERBOSE_DEPS=1` re-enables them at debug level for when they're actually wanted. --- docs/configuration.md | 8 ++++++++ src/env.rs | 1 + src/logger.rs | 41 +++++++++++++++++++++++++++++++++++++++-- 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index b3a69dbb41..35fbf8d1bb 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` + +By default, `mise -v` (debug level) suppresses debug logs from noisy +third-party crates (`h2`, `hyper`, `reqwest`, `rustls`, etc.) that emit a line +per HTTP/2 frame or socket read. Set this to `1` to let those logs through at +debug level — or use `-vv`/`MISE_LOG_LEVEL=trace`, which always includes +them. + ### `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..d82ba4e97c 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -18,6 +18,28 @@ struct Logger { log_file: Option>, } +/// Third-party crate targets that emit very noisy debug logs (often per HTTP/2 +/// frame, per socket read, etc.) and would otherwise overwhelm `-v` output. +/// These are suppressed at Debug level unless `MISE_LOG_VERBOSE_DEPS=1` is set. +/// Trace level always lets them through. +const NOISY_DEP_TARGETS: &[&str] = &[ + "h2", + "hyper", + "hyper_util", + "mio", + "reqwest", + "rustls", + "tokio_util", + "tower", + "want", +]; + +fn is_noisy_dep_target(target: &str) -> bool { + NOISY_DEP_TARGETS + .iter() + .any(|t| target == *t || target.starts_with(&format!("{t}::"))) +} + impl log::Log for Logger { fn enabled(&self, metadata: &Metadata) -> bool { metadata.level() <= *self.level.lock().unwrap() @@ -25,8 +47,23 @@ impl log::Log for Logger { fn log(&self, record: &Record) { 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; + let mut will_log_file = record.level() <= self.file_level && self.log_file.is_some(); + let mut will_log_term = record.level() <= term_level; + + // Suppress Debug-level spam from noisy third-party crates (e.g. h2 + // logging every received DATA frame). Trace still passes through, as + // does any level when MISE_LOG_VERBOSE_DEPS=1. + if record.level() == Level::Debug + && !*env::MISE_LOG_VERBOSE_DEPS + && is_noisy_dep_target(record.target()) + { + if self.file_level < LevelFilter::Trace { + will_log_file = false; + } + if term_level < LevelFilter::Trace { + will_log_term = false; + } + } if !will_log_file && !will_log_term { return; From ce9d0ab36ef6f90d1d7ad5adc13acd06fc10950a Mon Sep 17 00:00:00 2001 From: jdx <216188+jdx@users.noreply.github.com> Date: Sat, 18 Apr 2026 13:56:28 -0500 Subject: [PATCH 2/6] perf(log): drop per-check String allocation in noisy-dep filter Address review feedback: `format!("{t}::")` allocated a String for every entry in NOISY_DEP_TARGETS on every Debug log record. Replace with a byte-offset check that's allocation-free while preserving the same semantics (`h2::proto` matches, `h2extra` does not). --- src/logger.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/logger.rs b/src/logger.rs index d82ba4e97c..e2e4af0269 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -35,9 +35,13 @@ const NOISY_DEP_TARGETS: &[&str] = &[ ]; fn is_noisy_dep_target(target: &str) -> bool { - NOISY_DEP_TARGETS - .iter() - .any(|t| target == *t || target.starts_with(&format!("{t}::"))) + // Allocation-free: check for exact match or a "::" prefix by + // looking at the byte just past the candidate. Since `starts_with(t)` + // already matched, a following `:` byte can only be the first `:` of the + // `::` module-path separator used by `log` targets. + NOISY_DEP_TARGETS.iter().any(|t| { + target == *t || (target.starts_with(t) && target.as_bytes().get(t.len()) == Some(&b':')) + }) } impl log::Log for Logger { From 2ae3dd01ccee12e073901d7b0973afefe3f2ddbd Mon Sep 17 00:00:00 2001 From: jdx <216188+jdx@users.noreply.github.com> Date: Sat, 18 Apr 2026 13:57:51 -0500 Subject: [PATCH 3/6] refactor(log): use HashSet for noisy-dep target lookup Replace the prefix-scan over a &[&str] with a HashSet<&str> lookup on the crate-root segment of the log target. Splitting on "::" is allocation-free (just slices the input), and this reads more clearly and scales if the list grows. --- src/logger.rs | 50 +++++++++++++++++++++++++++----------------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/src/logger.rs b/src/logger.rs index e2e4af0269..869db57aa5 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,30 +19,33 @@ struct Logger { log_file: Option>, } -/// Third-party crate targets that emit very noisy debug logs (often per HTTP/2 -/// frame, per socket read, etc.) and would otherwise overwhelm `-v` output. -/// These are suppressed at Debug level unless `MISE_LOG_VERBOSE_DEPS=1` is set. -/// Trace level always lets them through. -const NOISY_DEP_TARGETS: &[&str] = &[ - "h2", - "hyper", - "hyper_util", - "mio", - "reqwest", - "rustls", - "tokio_util", - "tower", - "want", -]; +/// Root crate names of third-party dependencies that emit very noisy debug +/// logs (often per HTTP/2 frame, per socket read, etc.) and would otherwise +/// overwhelm `-v` output. These are suppressed at Debug level unless +/// `MISE_LOG_VERBOSE_DEPS=1` is set. Trace level always lets them through. +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 { - // Allocation-free: check for exact match or a "::" prefix by - // looking at the byte just past the candidate. Since `starts_with(t)` - // already matched, a following `:` byte can only be the first `:` of the - // `::` module-path separator used by `log` targets. - NOISY_DEP_TARGETS.iter().any(|t| { - target == *t || (target.starts_with(t) && target.as_bytes().get(t.len()) == Some(&b':')) - }) + // `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 { From bce2a33eda22b97226cd26d2f222efbc2099bb17 Mon Sep 17 00:00:00 2001 From: jdx <216188+jdx@users.noreply.github.com> Date: Sat, 18 Apr 2026 13:59:27 -0500 Subject: [PATCH 4/6] fix(log): also drop noisy deps at trace, not just debug Previously Trace-level records and `--log-level=trace` let h2/hyper/rustls spam through unchanged. Drop Debug and Trace records from noisy dep targets entirely (unless MISE_LOG_VERBOSE_DEPS=1), regardless of the configured terminal/file level. Info/Warn/Error still pass. --- src/logger.rs | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/logger.rs b/src/logger.rs index 869db57aa5..597cfa61d4 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -20,9 +20,10 @@ struct Logger { } /// Root crate names of third-party dependencies that emit very noisy debug -/// logs (often per HTTP/2 frame, per socket read, etc.) and would otherwise -/// overwhelm `-v` output. These are suppressed at Debug level unless -/// `MISE_LOG_VERBOSE_DEPS=1` is set. Trace level always lets them through. +/// 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", @@ -54,25 +55,20 @@ impl log::Log for Logger { } fn log(&self, record: &Record) { - let term_level = *self.term_level.lock().unwrap(); - let mut will_log_file = record.level() <= self.file_level && self.log_file.is_some(); - let mut will_log_term = record.level() <= term_level; - - // Suppress Debug-level spam from noisy third-party crates (e.g. h2 - // logging every received DATA frame). Trace still passes through, as - // does any level when MISE_LOG_VERBOSE_DEPS=1. - if record.level() == Level::Debug + // 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()) { - if self.file_level < LevelFilter::Trace { - will_log_file = false; - } - if term_level < LevelFilter::Trace { - will_log_term = false; - } + 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; + if !will_log_file && !will_log_term { return; } From cdb055f36a376acf674f032accb2a3b89dd6e1ed Mon Sep 17 00:00:00 2001 From: jdx <216188+jdx@users.noreply.github.com> Date: Sat, 18 Apr 2026 13:59:48 -0500 Subject: [PATCH 5/6] docs: update MISE_LOG_VERBOSE_DEPS to cover trace level --- docs/configuration.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 35fbf8d1bb..bee1f230f7 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -568,11 +568,10 @@ Display HTTP requests/responses in the logs. ### `MISE_LOG_VERBOSE_DEPS=1` -By default, `mise -v` (debug level) suppresses debug logs from noisy -third-party crates (`h2`, `hyper`, `reqwest`, `rustls`, etc.) that emit a line -per HTTP/2 frame or socket read. Set this to `1` to let those logs through at -debug level — or use `-vv`/`MISE_LOG_LEVEL=trace`, which always includes -them. +By default, 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 dropped — they would otherwise overwhelm `-v`/`-vv` +output. Set this to `1` to let those logs through. ### `MISE_QUIET=1` From eb86ae2d20b962710bdb88672beb55d6db456b33 Mon Sep 17 00:00:00 2001 From: jdx <216188+jdx@users.noreply.github.com> Date: Sat, 18 Apr 2026 14:03:38 -0500 Subject: [PATCH 6/6] docs: clarify MISE_LOG_VERBOSE_DEPS is the only way to see noisy-dep logs --- docs/configuration.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index bee1f230f7..daa8b5653d 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -568,10 +568,11 @@ Display HTTP requests/responses in the logs. ### `MISE_LOG_VERBOSE_DEPS=1` -By default, 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 dropped — they would otherwise overwhelm `-v`/`-vv` -output. Set this to `1` to let those logs through. +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`