Skip to content

feat: v0.7 log search and filtering#3

Open
jkhoffman wants to merge 15 commits into
mainfrom
feature/v0.7-log-search
Open

feat: v0.7 log search and filtering#3
jkhoffman wants to merge 15 commits into
mainfrom
feature/v0.7-log-search

Conversation

@jkhoffman
Copy link
Copy Markdown
Owner

Summary

  • --grep: Substring or regex filtering (--regex) on log output. Server-side for --follow, client-side for non-follow.
  • --since: Time-based log slicing — restart (since last restart marker), start (all retained history), or duration (30s/5m/2h).
  • --context N: Show N lines before/after each match, with -- group separators.
  • --json: JSONL output with process, line_number, stream, text, context fields.
  • TUI regex filter: Ctrl-R toggles regex mode during filter input, with /r! error indicator for invalid patterns.
  • Restart markers: All restart paths (manual, auto, watch) now write durable [agent-procs] Restarted annotations at sequence 0, before any child output. Enables reliable --since restart across size-based log rotations.

Infrastructure changes

  • DiskLogReader moved from src/tui/ to crate-level src/disk_log_reader.rs (shared by CLI and TUI)
  • MatchMode enum (Substring/Regex) for unified pattern matching
  • capture_output() refactored to accept pre-opened file handles
  • spawn_process() accepts initial_annotation parameter
  • New src/cli/log_search.rs module with the 4-stage filter pipeline

Test plan

  • 191 tests pass (up from 159): 86 unit, 5 proptest, 11 log search integration, 6 restart integration, 83 other integration/doc
  • --grep "pattern" returns only matching lines
  • --grep "pattern" --regex works with regex alternation
  • --grep "pattern" --tail 5 returns last 5 matches, not last 5 lines filtered
  • --grep with no matches returns empty (exit 0)
  • --grep "invalid[regex" --regex returns error (exit 1)
  • --context without --grep returns error (exit 1)
  • --grep "pattern" --json returns valid JSONL
  • --since start returns all output
  • --since restart after manual restart excludes pre-restart output
  • --since restart with no prior restart returns all output
  • --grep "ERROR" --context 1 shows context with -- separators
  • Manual restart writes [agent-procs] Restarted (manual) annotation

🤖 Generated with Claude Code

jkhoffman and others added 15 commits March 19, 2026 01:30
Move src/tui/disk_log_reader.rs to src/disk_log_reader.rs and expose it
as a top-level crate module. Also move StreamMode and LineSource enums
into disk_log_reader to break the circular dependency with tui::app;
re-export them from tui::app so all existing call sites remain unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tation to spawn_process

Move log file and LIDX index creation out of capture_output() into
spawn_process() so that restart annotations can be written at sequence 0
before either capture task starts. The initial_annotation parameter
writes the marker line synchronously to the stdout file and index,
then advances the shared sequence counter to 1.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pass initial_annotation through restart_process() and respawn_in_place()
to spawn_process(), so annotations are written as the first log line
before any child output. Removes post-spawn supervisor_tx sends that
previously wrote annotations after a race window.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add `grep: Option<String>` and `regex: bool` fields to `Request::Logs`
with `#[serde(default)]` for backward compatibility. Update all
construction sites and the proptest strategy to include the new fields.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add five new flags to the Logs subcommand with parse-time validation
(--context and --regex require --grep), follow-mode warnings for
inapplicable flags, and stub pass-through to execute() ready for
Tasks 8-9 to wire up the filter pipeline.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…skLogReader

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add src/cli/log_search.rs with the core search pipeline for non-follow
log queries. The module provides:

- SearchParams/SearchResult/SearchLine data types
- search_process() pipeline: since resolution -> grep scan -> tail
  windowing -> context expansion -> line reading
- parse_duration() for "30s"/"5m"/"2h" specs
- expand_context() with overlapping range merging
- resolve_since() supporting "start", "restart", and duration specs
- print_results() with text (--separators) and JSONL output modes

10 unit tests covering parse_duration and expand_context edge cases.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rewrite src/cli/logs.rs to use the log_search module for non-follow log
reading (--grep, --regex, --since, --context, --json) and pass grep/regex
to the daemon for follow mode. Add JSONL output for follow-mode --json.

Also fix DiskLogReader to fall back to sequential line scan when index
records are empty (unflushed), preventing blank output on newly-created
log files.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The test_proxy_drops_route_after_process_exit test was flaky in CI
because the exit-refresh tick (200ms) + proxy state update could
take longer than the fixed 2s sleep. Replace with a retry loop
(up to 5s) that polls for the expected "no running process" response.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…arators

H1: find_stderr_position_by_stdout_seq() now uses binary search
(O(log n)) instead of linear scan (O(n) index file opens per call).
Sequence numbers are monotonically increasing so binary search is
correct.

H2: -- separators in text output now only appear when context
expansion was used (any line has is_context=true). Previously,
--grep without --context printed -- between every non-adjacent
match. Removed the workaround filters in integration tests.

Also removed unused _context parameter from print_results().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant