feat: Rust CLI — log subcommand, byte-compatible port of the Python logger (LEO-334) (#23)
* feat: Rust CLI — log subcommand, byte-compatible port of the Python logger (LEO-334) Port the Python logger loop to `sensorwatch log` (visible alias `run`) on the existing CLI crate: same config schema with the same lenient warn-and-fall-back coercions, warn-once-and-retry when HWiNFO shared memory is unavailable, daily rotation on local-day rollover with retention pruning on startup and rollover, and graceful shutdown on console signals (ctrlc termination set; condvar sleep wakes instantly). Output is byte-compatible with the Python logger — Python's JSON separators and key order, pendulum's local-offset ISO 8601 rendering, and CRLF line endings on Windows — locked in by a golden fixture generated once by the frozen Python logger and byte-compared in tests. Three documented divergences: bare "unknown" type labels, always-six fractional digits, and NaN as null. No new runtime dependencies (toml/jiff/ctrlc were pre-declared in LEO-333); Cargo.lock is untouched and the 1.82 MSRV locked check passes. The config and logger unit suites are ported from the Python side with an injectable clock and temp dirs, plus new coverage for retention-on-rollover and the strict cutoff boundary. Docs updated in lockstep: SKILL.md Recipe 2 is Rust-first, README usage/output/config, the CLI README gains a `log` section, and the ROADMAP marks Phase-1 step 2 shipped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: cfg-gate the stderr test helper used only off-Windows The Windows clippy job (-D warnings) flags it as dead code, since its only caller is the cfg(not(windows)) fast-exit test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: degrade an absurd interval_seconds to sleep-until-shutdown, not a panic interval_seconds = i64::MAX is a valid TOML integer the config floor accepts, and Instant::now() + interval panicked on the first tick. checked_add now yields an open-ended condvar wait instead (woken only by the shutdown signal), matching how the retention path already degrades absurd values to a no-op — and how the Python loop survives the same config. Tests cover the timeout, already-flagged-overflow, and no-deadline-wake paths. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: make an explicit --verbose take precedence over RUST_LOG --verbose only set the env_logger default, so RUST_LOG=error suppressed the debug output the user explicitly asked for. The verbose branch now pins the filter to debug without consulting the environment; without the flag, RUST_LOG still overrides the info (log) / error (snapshot) defaults as before. Help text and the CLI README document the precedence. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: name the default config lookup in the missing --config warning The fallback chain can still load ./config.toml, so claiming "falling back to defaults" could misstate which config was used. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: recover from a poisoned shutdown lock instead of panicking The ctrlc handler, the loop-top check, and wait_for_shutdown now use PoisonError::into_inner — the flag is a plain bool, always safe to read, and a shutdown request must stay deliverable even if a panic ever occurs while the lock is held (a panic inside the signal-handler thread being the worst case). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: make the README snapshot snippet's working directory explicit The snippet silently depended on the cd from the logger build block above. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: include signal-handler-install failure in the skill's exit-1 causes Keeps the skill's exit-code summary consistent with the CLI README's table. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * perf: lowercase sensor filter patterns once, outside the sampling loop matches_sensor re-lowercased every pattern per reading per tick. Config now builds a SensorFilter (patterns normalized once) that the loop constructs before sampling; the config keeps the patterns as configured so the startup log still prints them verbatim, like Python. Per-reading matching now allocates only the sensor name's lowercase form. Raised by two reviewers (lcj-kimi-coder round 1, Copilot round 2) — the display-copy shape answers the original objection. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Agent for Leonard Janke <leonard.janke.agent@proton.me> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
L
lcj-claude-coder committed
de15106582868de4f6bc42cbc0a8612556a8fc89
Parent: c35f16f
Committed by GitHub <noreply@github.com>
on 7/3/2026, 12:52:02 AM