fix(dash): wire client-side log file and instrument chat lifecycle#103
fix(dash): wire client-side log file and instrument chat lifecycle#103michaelroy-amd wants to merge 8 commits into
Conversation
tracing-subscriber is a declared dependency of rocm-dash-tui and rocm-dash-daemon but tracing_subscriber:: is never called anywhere, so every info!/warn!/debug! across those crates is silently dropped and there is no client-side log file at all. Add apps/rocm/src/logging.rs: a file-only, non-blocking tracing subscriber (tracing_appender::non_blocking) writing daily-rotated files under AppPaths::client_log_dir() (~/.rocm/logs), respecting RUST_LOG via EnvFilter with an info-for-our-crates/warn-otherwise default. It never writes to stdout/stderr, since the TUI owns the raw-mode terminal and a stray write there would corrupt the display. Old rotated files beyond a retention cap are pruned on startup so a long-lived install's logs directory stays bounded. Instrument the chat request path (crates/rocm-dash-tui/src/agent.rs): finish_agent_request, the shared complete() tail for all three live backends (Rig/OpenAI, ChatGPT OAuth, Anthropic), now logs request-start, completion (elapsed ms, reply length, skills fired), error, and timeout, tagged with the backend name — so a hung chat request is traceable in the log file. Relates to EAI-7357. Signed-off-by: Michael Roy <michael.roy@amd.com>
init_creates_log_dir_and_a_log_file asserted that the guard returned by logging::init() was always Some, but tracing::subscriber::set_global_default can only succeed once per process. When the full test suite ran unfiltered/multi-threaded, another test could win that race first, making the assertion flaky. The rolling file appender opens its current-period file eagerly at construction time, before the subscriber install is attempted, so the directory/file existence checks hold regardless of which test wins the race. Only assert on filesystem state, and skip the info! log line when the guard is None. Signed-off-by: Michael Roy <michael.roy@amd.com>
…ering the tree Adversarial-review follow-up for EAI-7357: - Retention off-by-one: prune_old_logs() ran before the daily appender opened today's file, so the effective on-disk bound was MAX_RETAINED_LOGS + 1 (8), not 7. Reorder init() to construct the appender first (which opens today's file eagerly), then prune, so today's active file is counted within the retention window. Update the MAX_RETAINED_LOGS doc to state it is the total including today's file, and add a regression test asserting the ordering keeps the total at MAX_RETAINED_LOGS. - Tests wrote fixtures under the repo tree (.rocm-work/tests/logging), cluttering the working copy. Switch temp_paths to std::env::temp_dir(). Relates to EAI-7357 Signed-off-by: Michael Roy <michael.roy@amd.com>
CI follow-up for EAI-7357: - Regenerate MANIFEST.md and THIRD_PARTY_NOTICES.txt for the new tracing-appender dependency (and its transitive crossbeam-channel / symlink additions). Verified with `cargo xtask manifest --check` and `cargo xtask tpn --check`. - Harden the client log directory resolution against the CodeQL rust/path-injection alerts: the base path is derived from $HOME / $ROCM_CLI_DATA_DIR / config, so route it through a single validated_log_dir() barrier that creates the dir, canonicalizes both it and the AppPaths root, rejects `..` traversal, and requires containment under the root — degrading to no logging on any mismatch. All fs ops (appender, pruner) now run on the validated, canonicalized PathBuf. - Canonicalize the test fixtures' temp base for the same reason. Relates to EAI-7357 Signed-off-by: Michael Roy <michael.roy@amd.com>
…ducible The CI "Third-party notices current" check regenerates the notices and compares them to the committed file. ureq is dual-licensed "MIT OR Apache-2.0", and cargo-about's choice between the two offered licenses is not stable across environments — it flips ureq between the Apache-2.0 and MIT sections run-to-run, so the committed file (generated in one environment) can fail the check when CI regenerates in another. Pin ureq to MIT (one of its own offered licenses) via a per-crate `accepted` entry in about.toml. With the pin, four consecutive full regenerations are byte-identical and match the committed THIRD_PARTY_NOTICES.txt, so the check is deterministic. The generated notices content is unchanged by this commit (ureq was already under MIT in the committed file); the pin only guarantees CI reproduces it. Relates to EAI-7357 Signed-off-by: Michael Roy <michael.roy@amd.com>
…otices The previous per-crate `accepted = ["MIT"]` entry did not fix the flaky "Third-party notices current" check: cargo-about treats per-crate `accepted` as ADDITIVE to the global list, not restrictive, so it never forced ureq's license. ureq ships both LICENSE-MIT and LICENSE-APACHE and cargo-about's pick between them varies with filesystem read order, so a notice generated locally (ureq under MIT) failed CI's regenerate-and- compare (CI, like main, resolves ureq under Apache-2.0). Replace it with a proper `[ureq.clarify]` entry that pins ureq to Apache-2.0 with LICENSE-APACHE as the source of truth (checksum-verified). Three consecutive regenerations are now byte-identical, ureq resolves to Apache-2.0 exactly as main does, and THIRD_PARTY_NOTICES.txt now differs from main only by the three crates the new tracing-appender dependency adds (tracing-appender, crossbeam-channel, symlink). Verified with `cargo xtask tpn --check`. Relates to EAI-7357 Signed-off-by: Michael Roy <michael.roy@amd.com>
The logging test helper canonicalized the OS temp dir but fell back to the raw `std::env::temp_dir()` on error (`.unwrap_or(base)`). That fallback re-introduces the env-derived (tainted) path into the test's filesystem operations, so CodeQL's rust/path-injection flow was not fully cut for the test-code sinks. Replace the fallback with `expect` (the OS temp dir always exists and canonicalizes), matching production's `canonicalize(..)?` barrier which has no tainted fallback. Relates to EAI-7357 Signed-off-by: Michael Roy <michael.roy@amd.com>
rominf
left a comment
There was a problem hiding this comment.
LGTM. The log wiring is solid on every risk axis:
- File-only,
with_ansi(false)+with_writer(non_blocking)— the TUI raw-mode terminal is never touched. - Non-blocking via
tracing_appender::non_blocking(background worker); no blocking I/O on the render/event path. - Idempotent install (
set_global_default(...).ok(), the only subscriber in the workspace; tested). - Guard lifetime:
_log_guardbound inmain()scope, lives for the whole process. - Path-validated (canonicalize + containment under
data_dir); missing dir degrades to no-logging, never a startup failure. - Growth-bounded (
MAX_RETAINED_LOGS = 7, prune counts today's active file, keyed on therocm-cli.logprefix so the sibling daemon log is untouched). - No secrets: chat instrumentation logs only
reply_len/elapsed_ms/skills_fired/ staticbackend— no keys, prompts, or reply bodies.
All three finish_agent_request call sites are updated for the new backend param; manifest/notices/lock updated consistently for the new deps.
Approving.
|
🔴 Automated review · pr-review-watcher · 3a78b42 SummaryWires a file-only, non-blocking 🚫 Blocking (must fix before merge)None. Non-blocking
|
Resolve merge conflicts in generated artifacts by regeneration rather than manual conflict resolution: - Cargo.lock: regenerated from the merged Cargo.toml via cargo metadata + cargo check - MANIFEST.md: regenerated via cargo xtask manifest - THIRD_PARTY_NOTICES.txt: regenerated via cargo xtask tpn Source files (about.toml, apps/rocm/src/main.rs, crates/rocm-core/src/lib.rs) auto-merged cleanly. Verified: logging tests, cargo test --workspace --all-targets, cargo clippy --workspace --all-targets -D warnings, scripts/smoke_local.py, and a secret leak scan. Signed-off-by: Michael Roy <michael.roy@amd.com>
Summary
tracing-subscriberwas a declared dependency ofrocm-dash-tuiandrocm-dash-daemon, buttracing_subscriber::was never called anywhere in the codebase — everyinfo!/warn!/debug!call was silently dropped with no subscriber installed.apps/rocm/src/logging.rsmodule that installs a process-wide, file-only, non-blockingtracingsubscriber writing daily-rotated files under~/.rocm/logs(via a newAppPaths::client_log_dir()), with pruning to the 7 most recent files.AgentClient::complete()backends (RigAgentClient,ChatGptAgentClient,AnthropicAgentClient) incrates/rocm-dash-tui/src/agent.rs, tagged with abackendfield so a hung or failing chat is traceable to a specific provider.Root cause
The TUI owns the terminal in raw mode, so no subscriber could safely write to stdout/stderr — but no subscriber was ever wired up at all, meaning EAI-7357's client-side logging requirement was entirely unimplemented despite the dependency being present in
Cargo.toml.Implementation notes
tracing_appender::non_blocking) so it never risks corrupting the TUI's raw-mode display.logging::init()returnsOption<WorkerGuard>; the guard is held for the process lifetime inmain()since dropping it flushes and stops the writer.AppPaths::discover()or log-dir creation degrades to no logging rather than a startup failure.warn,rocm=info,rocm_dash_tui=info,rocm_dash_daemon=info, overridable viaRUST_LOG.init()call, bounding disk usage for long-lived installs.Relates to EAI-7357
Test Plan
cargo buildcleancargo clippy --all-targets --all-features -- -D warningscleancargo test -p rocm-dash-tui agent:: -- --test-threads=1— all 28 existing agent tests pass unchangedcargo test -p rocm(full suite, 3x repeated to check for flakiness) — 340/340 passed each runapps/rocm/src/logging.rs: log-dir/file creation, idempotent re-init when a global subscriber already exists, and log pruning retains only the most recent filesFollow-up / out of scope (reviewer note)
This PR wires client-side logging for the dash/TUI client (the
rocmbinary), which is the scope of EAI-7357. Instrumenting the standalonerocmddaemon binary is intentionally not included here and is left as a follow-up.