A small tool for a small, loud problem
boobook is a universal NDJSON log prettifier. Pipe any structured log stream at it — pino, winston, bunyan, logrus, kubectl, docker — and get back something a human can actually scan. Non-JSON lines pass through untouched, so it never eats your stack traces.
or npm i -g boobook · requires Node 18+
the proof
Same three log lines. Same stream. Only the reader changed.
{"level":30,"time":1712635200123,"pid":4821,"hostname":"api-7f8b9","service":"checkout","msg":"order placed","orderId":"ord_8af2","userId":"usr_91","amountCents":4299}
{"level":40,"time":1712635201455,"pid":4821,"hostname":"api-7f8b9","service":"checkout","msg":"rate limit warning","userId":"usr_91","remaining":2}
{"level":50,"time":1712635202781,"pid":4821,"hostname":"api-7f8b9","service":"checkout","msg":"payment declined","orderId":"ord_8af2","code":"card_declined"}
| boobook --exclude hostname,pid
14:00:00.123 INFO order placed service=checkout orderId=ord_8af2 userId=usr_91 amountCents=4299
14:00:01.455 WARN rate limit warning service=checkout userId=usr_91 remaining=2
14:00:02.781 ERROR payment declined service=checkout orderId=ord_8af2 code=card_declined
the thinking
There’s a whole industry of log aggregators that want your
credit card. pino-pretty only
works with pino. bunyan only
works with bunyan. jq asks you
to write a query just to read a line.
Meanwhile you’re SSH’d into a staging box at 2am with nothing installed and a stream of 180-column JSON flying past. You don’t need a platform. You need a pipe that takes structured noise on one end and spits out something you can read on the other.
“Pipe your logs through boobook and actually read them.”
That’s the whole thing. It reads NDJSON from stdin. It prints
back a timestamp, a colorized level, the message, and a flat trail
of key=value. Non-JSON lines
pass through untouched so stack traces, startup banners, and
Go’s helpful panics all survive intact. Four flags cover the
real cases. There is no config file. There will never be a config
file.
in the wild
boobook reads stdin. If you can pipe it, boobook can read it.
$ kubectl logs -f deploy/api | boobook --exclude hostname,pid --level info
$ docker compose logs -f --no-log-prefix web | boobook
$ node server.js | boobook --include msg,level,userId,route
$ ./server 2>&1 | boobook --exclude caller,stacktrace
$ pnpm test | boobook --compact --no-color > run.log
the whole api
--exclude
Comma-separated field names to drop. Kills
hostname, pid, and any other boilerplate your
logger insists on.
--include
The opposite. Only keep these fields (plus level, ts, msg — always kept). Use when you know exactly what you’re hunting for.
--level
Suppress anything below a level.
trace debug info warn error fatal.
Accepts pino numeric levels too.
--compact
Single-line dense mode. Good for tail-then-grep workflows.
--no-color
Disable ANSI. boobook already auto-detects non-TTY and does the right thing — this is for when you want to force it.
MIT · Node 18+ · ~40KB gzipped