Plate 85
consola vs pino: 155k vs 371k Log Lines/s on Node 20
Aditya Challa6 min read
I logged 100,000 lines with the same message and object through each library on this box, stdout redirected to a file: pino 10.4.0 wrote a median of about 371,000 lines per second, consola 3.4.2 about 155,000, so pino was roughly 2.4× faster. consola installs as 1 package at 448 KB on disk; pino installs as 13 packages (itself plus 12 dependencies) at 2,656 KB.
Short answer: pick pino for servers whose logs go to a file or a log shipper, and consola for CLIs, build tools and dev scripts where a human reads the output. No affiliate links in this post.
What I tested
- Machine: 8 vCPU Intel Xeon / 15 GB RAM shared Linux cloud box, tested 6 Oct 2026 about 03:05 IST.
- Versions: consola 3.4.2, pino 10.4.0 (both npm latest that day), Node 20.19.2, esbuild 0.28.2 minify + gzip -9.
- Load: one fresh Node process per run with stdout redirected to a file on local disk. Each call logged a message with a counter plus a small object (counter, user, path). 2,000 warm-up lines, then 100,000 timed lines. Variants: pino default, pino with an async sonic-boom destination, consola with fancy: false (what you get when output is not a terminal), consola with fancy: true forced (20,000 lines, it is slow), and console.log of a JSON.stringify line as a baseline. Median of 7 rounds, order reversed on alternate rounds. Scripts and JSON in /workspace/bench-consola-pino/.
- Not tested: pino transports (pino-pretty, worker-thread targets), real terminals, log shippers, Bun or Deno, browsers at runtime, and Node 22 or newer.
How many lines per second does each one write?
| 100k lines to a file (median of 7) | Lines/s | Range | Import ms |
|---|---|---|---|
| pino 10.4.0 default | 371,087 | 349,445–396,237 | 36.5 |
| pino, async destination | 363,663 | 319,118–395,330 | 36.5 |
| console.log + JSON.stringify | 184,516 | 167,736–223,928 | 0 |
| consola 3.4.2, fancy: false | 155,130 | 125,799–166,573 | 30.9 |
| consola, fancy: true forced (20k lines) | 19,709 | 16,121–20,576 | 29.8 |
pino beat even a hand-rolled console.log JSON line by about 2×, and consola's plain reporter sat a little below that baseline. Forcing the fancy reporter cost almost 8× against consola's own plain output, which is fine for a terminal and wasteful in CI logs. With stdout going to a file, the async destination made no measurable difference here. Writes to a pipe or a terminal behave differently, and the same buffering effect is measured in Python in stdout line vs full buffering. The same lines-per-second question for Python's logging module is measured in logging vs print.
Filtered calls matter too: one million debug() calls below the active level took 8.3 ms in pino and 21 ms in consola, so leaving debug lines in hot code is cheap in both.
What does each one cost in size?
| Build (esbuild minify, gzip -9) | Minified | gzip | Input files |
|---|---|---|---|
| consola, full entry (node) | 76,420 B | 22,762 B | 6 |
| consola/basic entry (node) | 6,772 B | 2,600 B | 4 |
| pino (node) | 63,288 B | 21,140 B | 30 |
| consola (browser) | 6,248 B | 2,428 B | 3 |
| pino (browser build) | 14,941 B | 5,425 B | 4 |
The surprise: bundled for Node, consola's full entry is slightly bigger than pino, most likely the fancy reporter and prompt code; I did not break the bundle down further. The consola/basic entry drops that to 2.6 KB gzip. On disk the picture flips: consola is a single dependency-free package, pino brings 12 dependencies, including sonic-boom and thread-stream. Color output in consola-style CLIs is its own size choice, measured in picocolors vs chalk, and size-limit vs bundlesize shows how to keep either number from creeping.
Do they behave the same?
| Check | consola 3.4.2 | pino 10.4.0 |
|---|---|---|
| Output format | readable text, [info] request 0 plus the object | one JSON object per line |
| Bytes per line in this test | about 67 | about 148 (adds time, pid, hostname) |
| 10 identical messages within 1 s | 6 printed, then a "(repeated 4 times)" line | all 10 written |
| Default level | 3 (info) | info |
| Child loggers / tags | withTag() | child() |
| Redaction | no built-in option | redact paths, printed [Redacted] |
| Interactive prompts | prompt() built in | none |
The throttling row is the one that surprises people: consola collapses repeated identical messages by default (throttle 1,000 ms, throttleMin 5), which is friendly in a terminal and confusing when you count log lines in production. pino keeps every line and leaves pretty printing to a transport running in a worker thread, the same worker model compared in tinypool vs piscina. If your logs end up in a metrics and tracing stack, the collector side is walked through in OpenTelemetry Collector, Prometheus and Grafana.
Which one should you pick?
| Situation | My pick |
|---|---|
| HTTP API or worker writing to files or a log shipper | pino |
| Need redaction of secrets in logs | pino |
| CLI, build tool or dev script read by humans | consola |
| Need prompts plus pretty output in one package | consola |
| Tiny bundled CLI | consola/basic |
Bottom line: on this box pino 10.4.0 wrote about 371,000 JSON lines per second versus about 155,000 for consola 3.4.2, with 13 installed packages versus 1 and JSON lines versus readable text. Who should not switch to pino: CLIs and scripts where people read the output and JSON lines just get in the way. Who should not stay on consola: services that log at volume, need every line kept, or need machine-parsable logs.
How this was made: I installed both packages on the ShopperCove box, ran each logger in fresh Node processes with stdout redirected to a file, took the median of 7 order-alternated rounds, bundled each entry with esbuild, and checked throttling, levels and redaction with in-memory streams. The JSON results sit next to the scripts. The write-up was drafted with AI help and checked against that output.
Sources
- https://github.com/unjs/consola
- https://unjs.io/packages/consola
- https://github.com/pinojs/pino
- https://getpino.io/
- https://github.com/pinojs/pino/blob/main/docs/transports.md
- https://www.npmjs.com/package/consola
- https://www.npmjs.com/package/pino
Related
- https://www.shoppercove.com/blog/picocolors-vs-chalk
- https://www.shoppercove.com/blog/logging-vs-print-localhost-lab
- https://www.shoppercove.com/blog/stdout-buffering-line-vs-full-localhost-lab
- https://www.shoppercove.com/blog/tinypool-vs-piscina
- https://www.shoppercove.com/blog/size-limit-vs-bundlesize
- https://www.shoppercove.com/blog/opentelemetry-collector-prometheus-grafana
- https://www.shoppercove.com/blog/undici-vs-node-fetch
- https://www.shoppercove.com/blog/tinyexec-vs-execa
Lab evidence
What I found running this
Hands-on on ShopperCove box 6 Oct 2026 ~03:05 IST (8 vCPU Intel Xeon / 15 GB shared Linux, Node 20.19.2). consola 3.4.2 vs pino 10.4.0, esbuild 0.28.2 minify + gzip -9. Fresh process per run, stdout redirected to a local file, 2,000 warm-up + 100,000 timed lines (message + counter/user/path object), median of 7, order alternated (lines/s): pino 371,087 (349,445-396,237); pino async sonic-boom 363,663; console.log+JSON.stringify 184,516; consola fancy:false 155,130 (125,799-166,573); consola fancy:true forced 19,709 (20k lines). Import ~36.5 vs ~30.9 ms. 1e6 filtered debug(): pino 8.3 ms, consola 21 ms. Bytes/line ~148 vs ~67. Bundle node gzip: consola full 22,762 B, consola/basic 2,600 B, pino 21,140 B; browser 2,428 vs 5,425 B. Install 1 pkg 448 KB vs 13 pkgs 2,656 KB. consola throttles 10 identical messages to 6 + repeated line; pino writes all 10; pino redact built in. Not tested: transports/pino-pretty, real TTY, shippers, Bun/Deno, Node 22+. No affiliate.