A small tool for a small, loud problem

Your logs
are JSON.
You’re not.

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

Two minutes of checkout traffic.

Same three log lines. Same stream. Only the reader changed.

before — raw stdout
{"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"}
after — | 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

Every log tool wants to be a platform.
boobook just wants to be a pipe.

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

Whatever pipe
you already have.

boobook reads stdin. If you can pipe it, boobook can read it.

kubernetes
$ kubectl logs -f deploy/api | boobook --exclude hostname,pid --level info
docker
$ docker compose logs -f --no-log-prefix web | boobook
pino / winston
$ node server.js | boobook --include msg,level,userId,route
go (zap / logrus)
$ ./server 2>&1 | boobook --exclude caller,stacktrace
ci logs to a file
$ pnpm test | boobook --compact --no-color > run.log

the whole api

Five flags. That’s it.

--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.

Two seconds from now
your logs are readable.

MIT · Node 18+ · ~40KB gzipped

Copied — now pipe something at it.