heisentest → guides
How to read JSON logs without losing your mind
Structured logging won: pino and winston in Node, zap and logrus in Go, structlog in
Python, Docker's json-file driver. Machines love the result. Humans get a wall of
{"level":30,"time":1753495200123,"msg":...} that is unreadable at 2 a.m.
The terminal way: jq
cat app.log | jq -r '[.time, .level, .msg] | @tsv' jq 'select(.level >= 50)' app.log # errors only (pino numeric levels) jq 'select(.service == "checkout")' app.log
jq is superb once you know the field names — and that is the catch. Every logger spells
things differently: time vs timestamp vs @timestamp,
level as a word ("error") or a number (50), msg vs
message. You end up reverse-engineering the schema before you can read a line.
The browser way: drop the file, get a timeline
heisentest recognizes JSON lines automatically and already knows the common field spellings: pino's numeric levels, winston's string levels, the usual timestamp keys, service and logger names. Each line becomes a clean row — time, level badge, service, message — and the noise fields wait in a detail view until you ask.
- Filter by level with one click once levels are detected.
- The timeline shows when errors clustered, not just that they exist.
- Mixed content is fine — stray
console.logprose between JSON lines still shows up. - All of it local: the file never leaves your browser tab.
Honest limits
heisentest reads one JSON object per line (the JSON-lines convention). A single giant JSON array, or pretty-printed multi-line objects, are not log files in this sense — for those, jq remains the right tool.