fix: cut log noise and add severity-based searching - #23
Merged
Conversation
Live testing on three boxes showed the durable streams were >94% noise: one reconcile-loop heartbeat line accounted for 17,360 of 17,419 daily agent events, executor logs were a single repeated MCP line, and multi-line Hermes records exploded into contextless per-line fragments. - hb: report Executor reachability state-change-only from the reconcile loop via a /tmp state file (resets with the container); interactive paths such as `hb up` always print and never consume the loop's state. - tx9-logs capture: collapse consecutive identical lines into one structured event plus an output_repeated summary carrying the count; raw logs and mirrored stdout/stderr stay byte-faithful. Blank lines no longer become structured events. - tx9-logs query: group timestamp-less continuation lines (tracebacks, embedded files, ps output) into the Hermes record that introduced them; skip the possibly mid-write final line of the active raw file (fixes truncated-duplicate fragments); render multi-line messages with indented continuations. - Add best-effort level (debug/info/warn/error) to tx9.event.v1, parsed from python-logging prefixes, level= tokens, and native record fields; new --level filter keeps that level and above, counting unleveled events as info. Omitted when unknown, so the schema stays compatible. - Reject sub-2001 numeric timestamps so loadavg-like fragments fall back to file mtime instead of 1970 (they were invisible to every --since). - Drop native-history records with no extractable text (~80% of codex events were empty). Timestamp-less warning lines grouped under one record still share the mtime-based fallback timestamp; that is inherent to the source format.
📝 WalkthroughWalkthroughChangesLogging now suppresses repeated reconcile and capture output, groups multiline records, parses severity levels, supports Logging behavior
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Live testing of the new first-class logs feature on three running boxes (columbia, liles-box, media-bot) found the durable streams flooded with noise:
executor daemon: reachable (remote)was logged by every 20s reconcile cycle — 17,360 of 17,419 agent events in 24h (99.7%) on media-bot, >94% of all events on every box. A plaintx9 logs <box>showed 200 identical lines.[executor] MCP session mode {...}line repeated 2–4× every ~11 minutes.psoutput) split into 50–104 per-line events sharing one timestamp; grep returned contextless fragments, plus truncated duplicates ([Tool lo,[Tool loop warning: same_too) from re-reading the mid-write tail of the active log file.WARNING.1970-01-01timestamps (invisible to every--since); ~80% of codex events had empty messages.Fixes
guest/hb: reconcile-loop Executor reachability reporting is now state-change-only, tracked in a/tmpstate file that resets with the container. Interactive paths (hb up, etc.) always print and never consume the loop's state, so a manual invocation can't swallow the next flip notification.guest/tx9-logscapture: consecutive identical lines collapse into one structured event plus anoutput_repeatedsummary with the count (60s window, 1000-repeat cap). Raw text logs and the mirrored stdout/stderr stay byte-faithful. Blank lines no longer become structured events.guest/tx9-logsquery: Hermes raw-log continuation lines (no leading timestamp or level token) group into the record that introduced them, capped at 1000 lines/1MiB; the possibly mid-write final line of the active file is skipped (kills truncated duplicates); text output renders multi-line messages with indented continuations.tx9.event.v1gains an optionallevelfield (debug|info|warn|error), best-effort parsed from python-logging prefixes,level=tokens, and native record fields (level/levelname/severity), omitted when unknown — backward compatible. New--levelflag ontx9 logsandtx9 logs exportkeeps that level and above; unleveled events count asinfo, so--level warnsilences unleveled chatter while--level infoshows everything.Impact on media-bot: ~17k noise events/day → ~60 real ones.
Tests
tests/regressions-hb-workload.sh: state-change-only reporting across reachable→unreachable→reachable flips; interactiveupalways reports; reconcile still fails loudly when unreachable.tests/regressions-logs.sh: capture dedup +output_repeatedcounts + raw-log fidelity; continuation grouping; active-file partial-tail skip; epoch-timestamp rejection; level parsing/filtering incl. bogus-value drop on schema-v1 records; multi-line text rendering; empty-message drop (flips one existing assertion that pinned the old behavior).internal/cli/cmd_logs_test.go:--levelin helper args, omitted when unset, value validation.make checkpasses (syntax, shellcheck, static contracts, shell regressions, go vet/build/test).Notes
--levelto a pre-upgrade in-box helper fails with the existing "upgrade the box" guidance; the flag is never passed when unset.🤖 Generated with Claude Code
Note
Add severity-based log filtering and suppress steady-state reconcile log noise
--levelflag (debug/info/warn/error) totx9 logsandtx9 logs export; unlabeled events are treated asinfo. Level is validated in cmd_logs.go and forwarded to the Python helper.levelfield used for threshold filtering.output_repeatedsummary; raw bytes and mirrored outputs remain unmodified.hbreconcile loop via_steady_report, which persists last-seen state and only logs on transitions whenHB_STEADY_QUIET=1.Macroscope summarized 23d6b8e.
Greptile Summary
This PR reduces log noise and adds severity-based log filtering. The main changes are:
--levelfiltering for log queries and exports.Confidence Score: 5/5
This looks safe to merge.
No blocking issues found in the changed code.
No files need attention.
What T-Rex did
Important Files Changed
--levelplumbing for logs query and export helper calls.--levelhelper arguments and value validation.Comments Outside Diff (4)
guest/tx9-logs, line 1392-1394 (link)When the active structured log ends with a valid JSON object that was flushed without a trailing newline, this branch drops the whole record. That can hide the last structured event from queries and exports even though the record is already present in the file.
Artifacts
Repro: focused harness that creates a final JSONL record without newline and invokes query/export
Repro: command transcript showing valid bytes are omitted by query and export
Prompt To Fix With AI
General comment
executor.jsonlcontains matching stdout output records, but the assertion usesjq -e 'select(.source == "executor" and .stream == "stdout" and .type == "output")' file >/dev/null. Since later records in the same JSONL stream are stderr/event records that do not match and produce no output, jq exits 4 even though earlier matching records were emitted.jq -e select(...)over a multi-record stream. jq's-estatus is based on the last output status, and a later input that produces no output makes the command fail despite earlier matches.jq -e 'any(inputs?; .source == "executor" and .stream == "stdout" and .type == "output")'with appropriate input handling, or usejq -e '[. | select(...)] | length > 0'under slurp mode (-s).General comment
tests/regressions-logs.shfails at the first executor stdout JSONL assertion even though matching stdout output records exist. The command usesjq -e 'select(...)' file >/dev/nullover the full JSONL stream. Withjq -e, the process exit status is based on the last output value; because the final input record isprocess_exitand does not match theselect, jq exits 4.jq -e select(...)directly on a multi-record JSONL file where later non-matching records can make the assertion fail despite earlier matching records.jq -e 'any(inputs?; .source == "executor" and .stream == "stdout" and .type == "output")'with suitable JSONL handling, orjq -e 'select(...)' file | grep -q .without relying on jq's final-record exit semantics.General comment
executor.jsonlcontains multiple records matching.source == "executor" and .stream == "stdout" and .type == "output", and the focused repro showsjqprints those matching objects. However, the command still exits 4, causingset -eto aborttests/regressions-logs.shbefore the remaining deduplication, multiline, timestamp, and level-filtering coverage can run.jq -e 'select(...)' file >/dev/null. With-e, jq's exit status is based on the last result processed, not merely whether any earlier input matched. Because later records such as stderr/process_exit do not match the select predicate, jq exits 4 despite earlier matches.-e, for example:jq -e 'any(.source == "executor" and .stream == "stdout" and .type == "output")' "$executor_root/logs/executor.jsonl" >/dev/null, or collect/select then testlength > 0with slurp mode.Reviews (3): Last reviewed commit: "fix: preserve log record boundaries" | Re-trigger Greptile