A multi-source JSONL log pager that behaves like less but can open a directory of local and remote (SSH) log files, parse JSON lines, extract a datetime field, and present all records in a single merged, chronologically sorted stream.
-
Multiple Source Types:
- Local files
- Local directories (recursive or non-recursive)
- Remote files over SSH
- Standard input
- Glob pattern support
-
Chronological Merging: All log entries from all sources are merged into a single, chronologically ordered stream using a k-way merge algorithm
-
Interactive Pager UI: Terminal-based interface similar to
lesswith:- Navigation (j/k, arrow keys, page up/down, g/G)
- Search with regex support (/pattern, n/N for next/previous)
- Follow mode (F key) for tailing logs
- Detailed view (Enter key) for individual entries
-
Filtering:
- Time range filtering (
--since,--until) - Field-based filtering (
--filter field=value)
- Time range filtering (
-
Flexible Timestamp Parsing:
- Configurable timestamp field name (default:
timestamp) - Support for RFC3339, ISO-8601, and custom formats
- Fallback to file modification time if timestamp is missing
- Configurable timestamp field name (default:
go build -o logless ./cmd/logless# View a single log file
logless app.log
# View multiple local files
logless app.log api.log
# View all files in a directory
logless ./logs/
# Recursively view all files in a directory
logless -r /var/log/myapp/
# View remote files via SSH
logless ssh://user@host1:/var/log/app/*.log ssh://user@host2:/srv/logs/app.jsonl
# View from stdin
cat app.log | logless -
# Combine local and remote sources
logless ./local.log ssh://user@host:/var/log/remote.log--time-field, -t string JSON field name containing the timestamp (default: "timestamp")
--time-format string Timestamp format (Go layout string, e.g. "2006-01-02 15:04:05")
--since string Show logs since this time (RFC3339 or relative like "-1h")
--until string Show logs until this time (RFC3339 or relative like "-1h")
--filter value Filter logs by field=value (e.g. "level=error") (can be specified multiple times)
--show-invalid Show lines that fail to parse as JSON
--fallback-time Use file modification time or line index if timestamp is missing
--follow, -f Follow log files (like tail -f)
--color string Color output: auto, always, never (default: "auto")
--max-buffer-lines int Maximum number of lines to buffer in memory (default: 10000)
--ssh-identity string SSH private key file path
--ssh-config string SSH config file path
--recursive, -r Recursively read directories
Once in the pager:
j/↓: Scroll down one linek/↑: Scroll up one lineSpace: Next pageb: Previous pageg: Go to first entryG: Go to last entry/pattern: Search (regex supported)n: Next search matchN: Previous search matchF: Toggle follow modeEnter: Show detailed view of current entryq: QuitEsc: Cancel search or exit detail view
logless --filter level=error app.loglogless --since "-1h" app.loglogless --since "2025-12-10T00:00:00Z" --until "2025-12-10T02:00:00Z" app.loglogless --time-field ts app.loglogless --follow app.logThe application is structured as follows:
cmd/logless/: Main entry pointinternal/cli/: CLI argument parsing and configurationinternal/source/: Source abstraction (local files, directories, SSH, stdin)internal/parser/: JSONL parsing and timestamp extractioninternal/merger/: K-way merge using min-heap for chronological orderinginternal/pager/: Interactive TUI using bubbletea
- Uses streaming I/O to avoid loading entire files into memory
- K-way merge algorithm ensures efficient chronological ordering
- Configurable memory limits via
--max-buffer-lines - Concurrent reading from multiple sources
- SSH glob pattern expansion is simplified (uses shell expansion on remote side)
- SSH authentication currently supports key-based auth and basic password auth
- Follow mode for SSH sources uses periodic re-reading (not true tail -F)
MIT