NodeJSThe console Module

The console Module

console is the logging object you already know from the browser, but Node's version writes to the process's standard streams and ships several methods beyond log that are genuinely useful for debugging and diagnostics. Knowing how it really works — which stream each method targets, when it blocks, and why it is not a real logger — saves you from a class of production surprises.

stdout vs stderr

console.log / console.info write to stdout; console.error / console.warn write to stderr. The global console is really just a Console instance bound to process.stdout and process.stderr. Keeping errors on stderr lets you redirect the two streams separately — essential for logging pipelines:

Bash
node app.js > out.log 2> err.log   # stdout → out.log, stderr → err.log
node app.js 2>&1 | tee all.log     # merge both, also print to terminal
Why diagnostics belong on stderr
Putting logs on stderr keeps your program's *real* output (a JSON result, a generated file) clean on stdout, so it can be piped into another command. A CLI that prints progress chatter on stdout pollutes whatever consumes it — progress goes to stderr, results to stdout.
Format specifiers

The first argument can contain printf-style placeholders, filled by the remaining arguments. Anything left over is appended, space-separated:

JS
console.log('User: %s, Age: %d', 'Ada', 36)   // User: Ada, Age: 36
console.log('As JSON: %j', { a: 1 })           // As JSON: {"a":1}
console.log('%o', { nested: { deep: true } })  // %o = inspect with options
console.log('Object:', { a: 1, b: [2, 3] })    // no specifier → pretty-print
console.log('a', 'b', 'c')                      // a b c

Specifier

Formats as

%s

String

%d / %i

Integer

%f

Float

%j

JSON ([Circular] on cycles)

%o / %O

Object via util.inspect

%c

CSS — ignored in Node, consumed in browsers

Controlling object output with util.inspect

By default Node only expands objects two levels deep and truncates long output. For deep structures, format explicitly:

JS
import { inspect } from 'node:util'

console.log(deepObj)                                  // truncated at depth 2 → [Object]
console.log(inspect(deepObj, { depth: null, colors: true }))  // full tree, colorized
Beyond log: the useful methods

JS
console.table([                       // render arrays/objects as a grid
  { name: 'Ada', role: 'admin' },
  { name: 'Linus', role: 'user' },
])

console.dir(obj, { depth: 2 })        // inspect with options
console.assert(1 === 2, 'math broke') // logs to stderr ONLY if assertion is false
console.count('hits')                 // hits: 1, hits: 2, ... (keyed counter)
console.trace('got here')             // prints a stack trace at this point
console.group('Request'); console.log('...'); console.groupEnd()  // indent nesting
┌─────────┬─────────┬─────────┐
│ (index) │  name   │  role   │
├─────────┼─────────┼─────────┤
│    0    │  'Ada'  │ 'admin' │
│    1    │ 'Linus' │ 'user'  │
└─────────┴─────────┴─────────┘
console.assert does not throw in Node
Unlike a test-framework assertion, `console.assert(false, msg)` only **prints** `msg` to stderr and keeps running — it does not throw or exit. Do not use it for control flow or input validation; it is a diagnostic aid only.
Measuring time

JS
console.time('query')
await runQuery()
console.timeEnd('query')   // query: 42.318ms — must use the same label
query: 42.318ms
Custom Console instances

You can construct a Console bound to any writable streams — e.g. to log to files instead of the terminal:

JS
import { Console } from 'node:console'
import fs from 'node:fs'

const logger = new Console({
  stdout: fs.createWriteStream('./out.log'),
  stderr: fs.createWriteStream('./err.log'),
})
logger.log('written to out.log')
logger.error('written to err.log')
Is console output synchronous?
It depends on where the stream points
This bites people in production. Node writes synchronously (blocking) when stdout/stderr is a **file or a TTY**, but asynchronously when it is a **pipe** (e.g. piping to another process). So `console.log` *can* block your event loop under heavy logging to a terminal or file — and conversely, a program that exits immediately after a piped `console.log` may lose the last line. High-throughput services should not log on the hot path with `console`.
console is fine — until it is not
`console` has no log levels, no structured (JSON) output, no sampling, and no rotation. For real applications use a dedicated logger like **pino** or **winston**, which write structured logs asynchronously and off the hot path — see [Logging](/nodejs/logging).
  • Use console.error/warn for problems so they land on stderr.

  • console.table, console.time, console.trace, and console.dir are debugging gold.

  • Remember output can block (TTY/file) or be lost on instant exit (pipe).

  • Swap to a structured logger for production.

Next
Understand scheduling with [Timers (setTimeout, setInterval)](/nodejs/timers).