From 3584b47fb8b90dde104124448dac03bb3c91288e Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Fri, 17 Jul 2026 22:06:14 +0200 Subject: [PATCH] fix(host/log): drop the wasapi crate's once-a-second DEBUG device-poll from the logs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `wasapi` audio crate polls the default output device ~1×/s and `log::debug!`s it (`wasapi::api default device Ok(...)`), flooding host.log even at RUST_LOG=info: those `log`-crate records reach tracing through the tracing-log bridge, and at *filter* time they carry the bridge's "log" shim target, so neither a bare-info EnvFilter nor a `wasapi=warn` directive on the file layer matches them (the ring already drops them post-hoc via normalized_metadata + NOISY_DEBUG_TARGETS — the fmt layer filters pre-event and can't). Fix at the source: a shared `log_capture::install_global` replaces SubscriberInitExt::init in both init paths (service file log + interactive stderr) and inits the tracing-log bridge with `ignore_crate("wasapi")`, so the poll records are dropped before they ever become tracing events — both sinks go quiet, while every other log-crate dependency still reaches the console ring (bridge max-level stays DEBUG). `wasapi` also added to NOISY_DEBUG_TARGETS as belt-and-suspenders, and `log` moved dev-dep -> dep for LevelFilter. Validated: .21 clippy (host --features nvenc) clean. Co-Authored-By: Claude Fable 5 --- crates/punktfunk-host/Cargo.toml | 5 +-- crates/punktfunk-host/src/log_capture.rs | 32 ++++++++++++++++---- crates/punktfunk-host/src/main.rs | 23 +++++++------- crates/punktfunk-host/src/windows/service.rs | 21 ++++++------- 4 files changed, 51 insertions(+), 30 deletions(-) diff --git a/crates/punktfunk-host/Cargo.toml b/crates/punktfunk-host/Cargo.toml index bff0368b..97a6a7f4 100644 --- a/crates/punktfunk-host/Cargo.toml +++ b/crates/punktfunk-host/Cargo.toml @@ -54,6 +54,9 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] } # back to the real module path, so the console's target column and the ring's noise gate see # `mdns_sd::…` instead of "log". tracing-log = "0.2" +# `log::LevelFilter` for the bridge's max-level in `log_capture::install_global` (which inits the +# tracing-log bridge with `ignore_crate("wasapi")`). Already in the tree transitively. +log = "0.4" axum = "0.8" mdns-sd = "0.20" # Wake-on-LAN: report the host's wake-capable NIC MAC(s) to clients via the mDNS `mac` TXT record. @@ -114,8 +117,6 @@ tower = { version = "0.5", features = ["util"] } http-body-util = "0.1" # Disposable directory fixtures for the Steam local-librarycache scan tests (library.rs). tempfile = "3" -# Emit `log`-crate records through the tracing-log bridge in the log_capture tests. -log = "0.4" # Opus encode for the host->client audio plane — stereo (`opus::Encoder`) AND 5.1/7.1 surround # (`opus::MSEncoder`, the safe multistream API the crate exposes; no `audiopus_sys` needed). The diff --git a/crates/punktfunk-host/src/log_capture.rs b/crates/punktfunk-host/src/log_capture.rs index 31076f5e..f0e489f2 100644 --- a/crates/punktfunk-host/src/log_capture.rs +++ b/crates/punktfunk-host/src/log_capture.rs @@ -125,12 +125,13 @@ pub fn ring() -> &'static LogRing { RING.get_or_init(LogRing::new) } -/// Targets whose DEBUG/TRACE output is steady-state per-packet chatter, not diagnostics — left -/// in, they evict the entire ring tail (mdns-sd DEBUG-logs every multicast packet it can't parse, -/// so one chatty AirPlay/HomePod device on the LAN floods thousands of entries per hour). The -/// ring keeps their INFO-and-up; stderr under `RUST_LOG` is unaffected. Prefix-matched on module -/// path boundaries. -const NOISY_DEBUG_TARGETS: &[&str] = &["mdns_sd"]; +/// Targets whose DEBUG/TRACE output is steady-state chatter, not diagnostics — left in, they evict +/// the entire ring tail: `mdns_sd` DEBUG-logs every multicast packet it can't parse (one chatty +/// AirPlay/HomePod device on the LAN floods thousands of entries per hour), and `wasapi` DEBUG-logs +/// the default audio device once a second (the device-watchdog poll). The ring keeps their +/// INFO-and-up; the file/stderr filter caps them separately (see `main`'s EnvFilter directives). +/// Prefix-matched on module path boundaries. +const NOISY_DEBUG_TARGETS: &[&str] = &["mdns_sd", "wasapi"]; fn is_noisy_debug(target: &str) -> bool { NOISY_DEBUG_TARGETS.iter().any(|t| { @@ -140,6 +141,25 @@ fn is_noisy_debug(target: &str) -> bool { }) } +/// Init the `log`→`tracing` bridge and install `subscriber` as the global default. Replaces +/// `SubscriberInitExt::init()` (which auto-inits the bridge with no crate filtering) so we can +/// **drop the `wasapi` crate's records at the bridge**: it polls the default audio device ~1×/s +/// and `log::debug!`s it, and those bridged events carry the bridge shim target at *filter* time, +/// so a downstream level/target filter on the file layer can't catch them (the ring can, in +/// `on_event`, via `normalized_metadata` — but the fmt layer filters pre-event). `ignore_crate` +/// stops them at the source, before they ever become tracing events, so neither sink sees them. +/// The bridge max-level stays DEBUG so every *other* `log`-crate dependency still reaches the ring. +pub fn install_global(subscriber: S) +where + S: tracing::Subscriber + Send + Sync + 'static, +{ + let _ = tracing_log::LogTracer::builder() + .with_max_level(log::LevelFilter::Debug) + .ignore_crate("wasapi") + .init(); + let _ = tracing::subscriber::set_global_default(subscriber); +} + /// The tee: a `tracing_subscriber` layer pushing every event into [`ring`]. Install with a /// per-layer `LevelFilter::DEBUG` so the ring sees DEBUG even when `RUST_LOG` keeps stderr at /// `info` (remote debugging must not require a restart with a different env). diff --git a/crates/punktfunk-host/src/main.rs b/crates/punktfunk-host/src/main.rs index d60137bc..04cf568c 100644 --- a/crates/punktfunk-host/src/main.rs +++ b/crates/punktfunk-host/src/main.rs @@ -117,18 +117,19 @@ fn main() { // A second layer tees DEBUG-and-up into the in-memory ring served by GET /api/v1/logs — // deliberately not gated by RUST_LOG, so console-side debugging never needs a restart. use tracing_subscriber::layer::SubscriberExt; - use tracing_subscriber::util::SubscriberInitExt; use tracing_subscriber::Layer; - tracing_subscriber::registry() - .with( - log_capture::RingLayer.with_filter(tracing_subscriber::filter::LevelFilter::DEBUG), - ) - .with( - tracing_subscriber::fmt::layer() - .with_writer(std::io::stderr) - .with_filter(filter), - ) - .init(); + log_capture::install_global( + tracing_subscriber::registry() + .with( + log_capture::RingLayer + .with_filter(tracing_subscriber::filter::LevelFilter::DEBUG), + ) + .with( + tracing_subscriber::fmt::layer() + .with_writer(std::io::stderr) + .with_filter(filter), + ), + ); } // Tee every panic through `tracing` BEFORE the default hook: a panicking thread otherwise diff --git a/crates/punktfunk-host/src/windows/service.rs b/crates/punktfunk-host/src/windows/service.rs index 6f483225..da647565 100644 --- a/crates/punktfunk-host/src/windows/service.rs +++ b/crates/punktfunk-host/src/windows/service.rs @@ -153,37 +153,36 @@ fn rotate_if_large(path: &std::path::Path) { /// supervisor serves no mgmt API itself, but the layer is harmless and keeps both inits uniform. pub fn init_file_logging(filter: tracing_subscriber::EnvFilter) { use tracing_subscriber::layer::SubscriberExt; - use tracing_subscriber::util::SubscriberInitExt; use tracing_subscriber::Layer; let ring = crate::log_capture::RingLayer.with_filter(tracing_subscriber::filter::LevelFilter::DEBUG); let log_path = service_log_path(); rotate_if_large(&log_path); + // `install_global` inits the log bridge with `ignore_crate("wasapi")` (see its doc) and sets + // the subscriber global — replacing `SubscriberInitExt::init`, which would bridge every crate. match std::fs::OpenOptions::new() .create(true) .append(true) .open(log_path) { Ok(file) => { - tracing_subscriber::registry() - .with(ring) - .with( + crate::log_capture::install_global( + tracing_subscriber::registry().with(ring).with( tracing_subscriber::fmt::layer() .with_ansi(false) .with_writer(move || file.try_clone().expect("clone service log handle")) .with_filter(filter), - ) - .init(); + ), + ); } Err(_) => { - tracing_subscriber::registry() - .with(ring) - .with( + crate::log_capture::install_global( + tracing_subscriber::registry().with(ring).with( tracing_subscriber::fmt::layer() .with_writer(std::io::stderr) .with_filter(filter), - ) - .init(); + ), + ); } } }