Slit: The Chain-of-Filters Log Pager That Fixes Interactive Debugging
Hook
Most developers still pipe grep chains to less when debugging production issues, losing context with every filter. Slit proves there's a better way—one that lets you add and remove filters without starting over.
Context
Anyone who's debugged a production issue knows the dance: tail the log, grep for ERROR, pipe to less, realize you need more context, Control-C, expand the grep, repeat. Traditional Unix pagers like less and more were built for reading documentation, not analyzing multi-threaded application logs where relevant lines are buried in thousands of debug statements. Modern logs from microservices, async systems, and concurrent applications are fundamentally different from the text files these tools were designed for in the 1970s.
The standard workaround—chaining grep commands—forces you into a destructive workflow. Each filter permanently removes lines from the stream, so adding context back means restarting your entire pipeline. You lose your place, your scroll position, and your mental model of where you were in the log timeline. Tools like lnav emerged to solve this with SQL queries and automatic format detection, but they're heavyweight solutions that require learning query syntax when you just want to quickly exclude noisy loggers or drill down to a specific request ID. Slit occupies the middle ground: interactive filtering with the simplicity of search-as-you-type, built specifically for the iterative process of log investigation.
Technical Insight
Slit's core innovation is treating filters as a reversible chain rather than a destructive pipeline. When you search for a pattern, you're not creating a new filtered stream—you're adding a link to a chain that you can inspect, modify, or remove at any time. Press & to add an AND filter (show only lines matching this pattern), - for a NOT filter (exclude matching lines), or + for an OR filter (union with current results). Each operation preserves the full log in memory while updating the viewport to show only lines that satisfy the entire filter chain.
The implementation relies on Go's efficient string handling and terminal rendering libraries. Under the hood, Slit maintains two critical data structures: the complete log buffer and a filtered index of visible line numbers. When you add a filter, it walks through the currently visible lines (not the entire log), applies the new pattern, and rebuilds the index. This means your tenth filter only evaluates against lines that passed the first nine, making complex drill-downs surprisingly fast even on gigabyte-sized files.
Here's what a typical debugging session looks like:
# Start viewing a log file
slit application.log
# Press '&' and type 'request-id: abc123'
# Now you see only lines with that request ID
# Press '&' again and type 'ERROR|WARN' (regex mode)
# Now you see only errors/warnings for that request
# Press '-' and type 'connection pool'
# Exclude noisy connection pool messages
# Press 'C' to toggle context mode
# Temporarily see all surrounding lines, filters still visible
# Press 'C' again to return to filtered view
# Press 'X' to remove the last filter from the chain
The filter chain is always visible at the bottom of the screen, showing you exactly what's being applied: &request-id: abc123 &ERROR|WARN -connection pool. This transparency is crucial when you're deep in an investigation and need to remember what you've filtered out.
Slit's timestamp-locking feature (K-mode) solves another common pain point. Most log lines start with timestamps, then contain data that extends far beyond terminal width. When you scroll horizontally in a standard pager, the timestamp disappears, and you lose temporal context. Enable K-mode by pressing K followed by a number (typically 20-30 characters), and Slit keeps that many characters frozen on the left while scrolling the rest of the line. You maintain chronological awareness even when examining deeply indented stack traces or JSON payloads that extend hundreds of characters.
# Without K-mode, horizontal scroll loses the timestamp:
2024-01-15 10:23:45.123 INFO [thread-pool-4] com.example.Service - Processing request...
# Scroll right:
ple.Service - Processing request for user=john.doe, session=xyz, action=UPDATE, entity=...
# With K-mode set to 24 characters:
2024-01-15 10:23:45.123 nfo.Service - Processing request for user=john.doe, session=xyz...
# The timestamp stays visible no matter how far right you scroll
The follow mode (F key) implements tail-like functionality while respecting your filter chain. New lines appearing in the file are evaluated against all active filters before being displayed. When you scroll up to examine something, follow mode automatically pauses—no more fighting with tail -f while trying to read a specific section. Scroll back to the bottom, and automatic following resumes.
What makes this architecture elegant is its simplicity. Slit doesn't try to parse log formats, detect timestamps automatically, or provide SQL interfaces. It's a focused tool that does one thing well: let you interactively apply and modify filters while maintaining full context. The entire binary is a few megabytes with no runtime dependencies, making it trivial to scp to production servers when you need to debug in place.
Gotcha
The biggest limitation is the single-file constraint. Modern distributed systems often require correlating logs across multiple services or examining rotated log files (app.log, app.log.1, app.log.2). Slit can't open multiple files simultaneously, forcing you to either concatenate them first or switch between multiple Slit instances. For scenarios like "show me what service A was doing when service B logged this error," you're back to traditional tools or need to pre-process logs into a unified stream.
Filter history is another rough edge. Slit remembers your previous search patterns (accessible via up/down arrows), but it doesn't remember whether you were in regex mode or case-sensitive mode when you entered them. If you built a complex investigation using regex filters, then recall an earlier search, you need to manually toggle regex mode back on. This breaks flow when you're rapidly iterating through different filter combinations. Additionally, the boolean expression syntax mentioned in the TODO (supporting queries like (DEBUG OR INFO) AND NOT (send OR receive) as a single filter) would reduce the mental overhead of managing long filter chains for common patterns, but it's not yet implemented. You can achieve the same results by chaining simple filters, but it's more verbose and harder to modify atomically.
Verdict
Use slit if you regularly debug multi-threaded applications, microservices, or any system that generates high-volume logs where relevant information is scattered among noise. It excels when you don't know exactly what you're looking for upfront and need to iteratively drill down—hunting for race conditions, tracking request flows, or identifying patterns in failures. The reversible filter chain means you can explore aggressively without fear of losing context, making it ideal for exploratory debugging sessions. Also valuable if you frequently work on remote servers where you need a lightweight, no-dependency tool that you can drop in place. Skip slit if your debugging workflow requires correlating multiple log files simultaneously (use lnav instead), you primarily work with structured JSON logs (jq or specialized viewers are better), or you prefer scripting solutions over interactive tools (stick with grep/awk pipelines). Also skip if you need complex boolean expressions in filters—the current chain-based approach works but gets unwieldy for very complex queries that would be clearer as single expressions.