fix(host/log): drop the wasapi crate's once-a-second DEBUG device-poll from the logs
apple / swift (push) Successful in 1m18s
apple / screenshots (push) Successful in 5m16s
ci / web (push) Successful in 58s
ci / docs-site (push) Successful in 1m15s
arch / build-publish (push) Has been cancelled
ci / rust (push) Has been cancelled
ci / bench (push) Has been cancelled
deb / build-publish (push) Has been cancelled
decky / build-publish (push) Has been cancelled
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Has been cancelled
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Has been cancelled
docker / deploy-docs (push) Has been cancelled
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Has been cancelled
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Has been cancelled
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Has been cancelled
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
windows-host / package (push) Has been cancelled
android / android (push) Successful in 16m4s

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 <noreply@anthropic.com>
This commit is contained in:
2026-07-17 22:06:14 +02:00
parent de651c0f1a
commit 3584b47fb8
4 changed files with 51 additions and 30 deletions
+3 -2
View File
@@ -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 # back to the real module path, so the console's target column and the ring's noise gate see
# `mdns_sd::…` instead of "log". # `mdns_sd::…` instead of "log".
tracing-log = "0.2" 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" axum = "0.8"
mdns-sd = "0.20" mdns-sd = "0.20"
# Wake-on-LAN: report the host's wake-capable NIC MAC(s) to clients via the mDNS `mac` TXT record. # 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" http-body-util = "0.1"
# Disposable directory fixtures for the Steam local-librarycache scan tests (library.rs). # Disposable directory fixtures for the Steam local-librarycache scan tests (library.rs).
tempfile = "3" 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 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 # (`opus::MSEncoder`, the safe multistream API the crate exposes; no `audiopus_sys` needed). The
+26 -6
View File
@@ -125,12 +125,13 @@ pub fn ring() -> &'static LogRing {
RING.get_or_init(LogRing::new) RING.get_or_init(LogRing::new)
} }
/// Targets whose DEBUG/TRACE output is steady-state per-packet chatter, not diagnostics — left /// Targets whose DEBUG/TRACE output is steady-state chatter, not diagnostics — left in, they evict
/// in, they evict the entire ring tail (mdns-sd DEBUG-logs every multicast packet it can't parse, /// the entire ring tail: `mdns_sd` DEBUG-logs every multicast packet it can't parse (one chatty
/// so one chatty AirPlay/HomePod device on the LAN floods thousands of entries per hour). The /// AirPlay/HomePod device on the LAN floods thousands of entries per hour), and `wasapi` DEBUG-logs
/// ring keeps their INFO-and-up; stderr under `RUST_LOG` is unaffected. Prefix-matched on module /// the default audio device once a second (the device-watchdog poll). The ring keeps their
/// path boundaries. /// INFO-and-up; the file/stderr filter caps them separately (see `main`'s EnvFilter directives).
const NOISY_DEBUG_TARGETS: &[&str] = &["mdns_sd"]; /// Prefix-matched on module path boundaries.
const NOISY_DEBUG_TARGETS: &[&str] = &["mdns_sd", "wasapi"];
fn is_noisy_debug(target: &str) -> bool { fn is_noisy_debug(target: &str) -> bool {
NOISY_DEBUG_TARGETS.iter().any(|t| { 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<S>(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 /// 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 /// 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). /// `info` (remote debugging must not require a restart with a different env).
+12 -11
View File
@@ -117,18 +117,19 @@ fn main() {
// A second layer tees DEBUG-and-up into the in-memory ring served by GET /api/v1/logs — // 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. // deliberately not gated by RUST_LOG, so console-side debugging never needs a restart.
use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::Layer; use tracing_subscriber::Layer;
tracing_subscriber::registry() log_capture::install_global(
.with( tracing_subscriber::registry()
log_capture::RingLayer.with_filter(tracing_subscriber::filter::LevelFilter::DEBUG), .with(
) log_capture::RingLayer
.with( .with_filter(tracing_subscriber::filter::LevelFilter::DEBUG),
tracing_subscriber::fmt::layer() )
.with_writer(std::io::stderr) .with(
.with_filter(filter), tracing_subscriber::fmt::layer()
) .with_writer(std::io::stderr)
.init(); .with_filter(filter),
),
);
} }
// Tee every panic through `tracing` BEFORE the default hook: a panicking thread otherwise // Tee every panic through `tracing` BEFORE the default hook: a panicking thread otherwise
+10 -11
View File
@@ -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. /// 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) { pub fn init_file_logging(filter: tracing_subscriber::EnvFilter) {
use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::Layer; use tracing_subscriber::Layer;
let ring = let ring =
crate::log_capture::RingLayer.with_filter(tracing_subscriber::filter::LevelFilter::DEBUG); crate::log_capture::RingLayer.with_filter(tracing_subscriber::filter::LevelFilter::DEBUG);
let log_path = service_log_path(); let log_path = service_log_path();
rotate_if_large(&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() match std::fs::OpenOptions::new()
.create(true) .create(true)
.append(true) .append(true)
.open(log_path) .open(log_path)
{ {
Ok(file) => { Ok(file) => {
tracing_subscriber::registry() crate::log_capture::install_global(
.with(ring) tracing_subscriber::registry().with(ring).with(
.with(
tracing_subscriber::fmt::layer() tracing_subscriber::fmt::layer()
.with_ansi(false) .with_ansi(false)
.with_writer(move || file.try_clone().expect("clone service log handle")) .with_writer(move || file.try_clone().expect("clone service log handle"))
.with_filter(filter), .with_filter(filter),
) ),
.init(); );
} }
Err(_) => { Err(_) => {
tracing_subscriber::registry() crate::log_capture::install_global(
.with(ring) tracing_subscriber::registry().with(ring).with(
.with(
tracing_subscriber::fmt::layer() tracing_subscriber::fmt::layer()
.with_writer(std::io::stderr) .with_writer(std::io::stderr)
.with_filter(filter), .with_filter(filter),
) ),
.init(); );
} }
} }
} }