From 3ad8ba9581d7a0b857d2309b1e239521545c2d01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Kozlovsk=C3=BD?= Date: Tue, 27 Oct 2026 18:52:16 +0100 Subject: [PATCH] CLI: log warnings to stderr by default The CLI installed no logger, so GPU warnings (a declined frame, out of memory, fallback to the CPU) were dropped and an export silently rendered on the CPU. Install the engine logger with a `warn` default (RUST_LOG still overrides). Split out of #1415. --- apps/effectcraft-cli/src/main.rs | 3 +++ crates/engine/src/system/logging.rs | 37 ++++++++++++++++++++++++----- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/apps/effectcraft-cli/src/main.rs b/apps/effectcraft-cli/src/main.rs index 6e5b8d6cc..fb00fc90d 100644 --- a/apps/effectcraft-cli/src/main.rs +++ b/apps/effectcraft-cli/src/main.rs @@ -279,6 +279,9 @@ fn utf8_args(raw: impl IntoIterator) -> Result raw, Err(e) => fail_usage(&e), diff --git a/crates/engine/src/system/logging.rs b/crates/engine/src/system/logging.rs index ec5785f7e..857a3e90d 100644 --- a/crates/engine/src/system/logging.rs +++ b/crates/engine/src/system/logging.rs @@ -5,15 +5,15 @@ //! terminal, so that file is what a bug report can attach. The last few hundred warnings and //! errors are also kept in memory for the System Compatibility Report. //! -//! Levels: `info` for EffectCraft's own crates, `warn` for everything else (wgpu and naga are -//! chatty). `RUST_LOG` replaces that (a level such as `debug`, or per target: +//! Levels by default ([`DEFAULT_FILTER`]): `info` for EffectCraft's own crates, `warn` for +//! everything else (wgpu and naga are chatty). `RUST_LOG` replaces that (a level such as `debug`, or per target: //! `effectcraft=debug,wgpu=warn`). //! //! Help ▸ Enable Logging ([`set_enabled`], the `help.enableLogging` command) additionally appends //! every EffectCraft record to `Logs/EffectCraft Log.txt`, across launches. //! -//! Frontends call [`install`] once at startup and the desktop app [`attach_session_log`] once the -//! settings folder is known; records logged before that are kept (up to [`MAX_PENDING`]) and +//! Frontends call [`install`] (or [`install_with`] for another default, as the CLI does with `warn`) +//! once at startup and the desktop app [`attach_session_log`] once the settings folder is known; records logged before that are kept (up to [`MAX_PENDING`]) and //! written first. Writing never panics: a file that can't be created or written leaves standard //! error as the only sink. Never log the control-channel token. @@ -131,11 +131,21 @@ fn format_line(ts: &str, level: log::Level, thread: &str, target: &str, message: /// Install the logger (once; later calls and other installed loggers are left alone). pub fn install() { + install_with(DEFAULT_FILTER); +} + +/// The directive string: `RUST_LOG` when it is set and nonblank, otherwise `default`. +fn filter_spec<'a>(rust_log: Option<&'a str>, default: &'a str) -> &'a str { + rust_log.filter(|s| !s.trim().is_empty()).unwrap_or(default) +} + +/// [`install`] with `default` as the filter when `RUST_LOG` is unset or empty. +pub fn install_with(default: &str) { static LOGGER: Logger = Logger; if log::set_logger(&LOGGER).is_ok() { let filter = FILTER.get_or_init(|| { - let spec = std::env::var("RUST_LOG").ok().filter(|s| !s.trim().is_empty()); - parse_filter(spec.as_deref().unwrap_or(DEFAULT_FILTER)) + let spec = std::env::var("RUST_LOG").ok(); + parse_filter(filter_spec(spec.as_deref(), default)) }); log::set_max_level(filter.iter().map(|d| d.1).fold(LevelFilter::Info, Ord::max)); } @@ -357,6 +367,21 @@ mod tests { assert_eq!(level_for(&f, "naga"), LevelFilter::Warn); } + /// The CLI's default (`warn`): warnings from every crate, no info chatter on stderr, + /// while a nonblank `RUST_LOG` overrides `default`. + #[test] + fn the_cli_filter_shows_only_warnings() { + assert_eq!(filter_spec(None, "warn"), "warn"); + assert_eq!(filter_spec(Some(" "), "warn"), "warn"); + assert_eq!(filter_spec(Some("info"), "warn"), "info"); + let f = parse_filter(filter_spec(None, "warn")); + assert_eq!(level_for(&f, "effectcraft_gpu::context"), LevelFilter::Warn); + assert_eq!(level_for(&f, "effectcraft_engine::render_queue"), LevelFilter::Warn); + assert_eq!(level_for(&f, "wgpu_core::device"), LevelFilter::Warn); + let over = parse_filter(filter_spec(Some("info"), "warn")); + assert_eq!(level_for(&over, "effectcraft_gpu::context"), LevelFilter::Info); + } + #[test] fn timestamps_are_utc_with_milliseconds() { assert_eq!(timestamp(0), "1970-01-01T00:00:00.000Z");