2026-05-09
Thirty years of staring at log files has taught me that tail -f | grep is the duct tape of observability — it works, but you'll regret it the moment you need to correlate two events. lnav (the Log File Navigator) is what you reach for when grep stops scaling but Splunk would be embarrassing overkill.
It's a single static binary. Point it at a directory of logs and it auto-detects formats — syslog, Apache, nginx, journald, strace, glog, JSON-lines, and dozens more — then merges them into one timeline, color-coded by severity.
lnav /var/log/nginx/ /var/log/syslog /var/log/auth.log
That alone beats multitail. But the real magic is that lnav parses each format into a relational schema and exposes it as SQLite. Hit ; and you're at a SQL prompt against your logs:
;SELECT c_ip, COUNT(*) AS hits, AVG(sc_bytes) AS avg_bytes
FROM access_log
WHERE sc_status >= 500
AND log_time > datetime('now', '-1 hour')
GROUP BY c_ip
ORDER BY hits DESC
LIMIT 10;
No ingestion. No index. No daemon. It just walks the file. For ad-hoc forensics on a box at 3am, that's a superpower.
A few moves that earn it permanent residence in ~/bin:
P on a JSON line and it expands into a formatted tree. Press q and you're back in the stream.lnav ssh://prod-web-01//var/log/nginx/error.log — it sets up the pipe for you.:filter-out and :filter-in. Hide healthcheck noise without touching the file: :filter-out /healthz.i for an ASCII histogram of log volume by minute, broken down by level. Spot the spike instantly.g jumps to top, G to bottom, e/E jumps between errors, and :goto 2026-05-09T03:14:00 lands you at a wall-clock moment across all open files simultaneously.~/.lnav/formats/ describing your app's log regex, and your bespoke logs join the relational world. Now request_id is a queryable column.The killer combo is correlation. Suppose nginx logged a 502 at 03:14:07 and you want to know what your app did. In tail-and-grep land you're flipping between two terminals doing timestamp arithmetic. In lnav:
;SELECT log_time, log_level, log_body
FROM all_logs
WHERE log_time BETWEEN '2026-05-09 03:14:05' AND '2026-05-09 03:14:10'
ORDER BY log_time;
One merged, sorted view of everything that happened in that five-second window, regardless of which file or format it came from.
It also batch-runs non-interactively, which makes it scriptable:
lnav -n -c ';SELECT c_ip, COUNT(*) FROM access_log GROUP BY c_ip' \
-c ':write-csv-to /tmp/ips.csv' /var/log/nginx/access.log*
The mainstream alternative is "ship logs to ELK." lnav's pitch is that 80% of the time you don't need a stack — you need ten minutes with a sharp tool on the box where the problem actually is.
