util and diagnostics
node:util bridges callbacks to Promises, formats objects for logs, and provides debugging helpers - pair with node:diagnostics_channel and perf_hooks for deeper production insight.
Recipe
import { promisify, inspect, debuglog, styleText, parseEnv } from 'node:util';
import { gzip } from 'node:zlib';
const gzipAsync = promisify(gzip);
const log = debuglog('billing');
log('processing', { id: '1' });When to reach for this:
- Promisifying legacy callback APIs
- Logging nested objects without
JSON.stringifyfailures - Gated debug output with
NODE_DEBUG=billing - CLI tools coloring output with
styleText
Working Example
import { promisify, inspect, debuglog, styleText } from 'node:util';
import { gzip } from 'node:zlib';
import { diagnostics_channel } from 'node:diagnostics_channel';
const gzipAsync = promisify(gzip);
const debug = debuglog('api');
const orderChannel = diagnostics_channel.channel('app:order');
orderChannel.subscribe((message) => {
debug('order event', inspect(message, { depth: 2, maxArrayLength: 10 }));
});
export async function compressJson(obj: unknown): Promise<Buffer> {
const json = JSON.stringify(obj);
debug('compress input bytes', json.length);
return gzipAsync(Buffer.from(json, 'utf8'));
}
console.log(styleText('cyan', 'info'), 'server boot');import { parseEnv } from 'node:util';
const parsed = parseEnv('FOO=bar\nBAZ=qux');What this demonstrates:
inspecttruncates depth/array for readable logs vs circular JSON errorsdebuglogonly prints whenNODE_DEBUG=apiorNODE_DEBUG=*diagnostics_channelpublishes/subscribes cross-cutting events without tight couplingparseEnvparses dotenv-style strings in tests without files
Deep Dive
How It Works
- promisify assumes
(err, result)callback last - returns Promise-returning function. - callbackify inverse for legacy - rare in new code.
- inspect customizes
util.inspect.customon classes for redacted representations. - diagnostics_channel - lightweight pub/sub for APM hooks and internal tracing.
util vs dedicated modules
| Need | Tool |
|---|---|
| Callback → Promise | promisify |
| Safe object log | inspect |
| Feature flags debug | debuglog |
| Performance timing | perf_hooks |
| OpenTelemetry | OTel SDK |
TypeScript Notes
import { types } from 'node:util';
if (types.isPromise(maybe)) {
await maybe;
}Gotchas
- Logging huge objects in production - cost and PII risk. Fix:
inspectlimits + redaction. - promisify non-standard callbacks - hangs. Fix: manual Promise wrapper.
- Leaving NODE_DEBUG= on in prod* - verbose stderr I/O. Fix: dev/staging only.
- Relying on inspect for serialization - not JSON - use
JSON.stringifyfor wire format. - Subscribing diagnostics_channel without unsubscribe - test leaks. Fix: unsubscribe in teardown.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| fs/promises | File callbacks | Already promise-native |
| Pino structured logs | Production logging | Quick debug |
| OpenTelemetry | Distributed traces | Local printf debug |
node:assert | Invariants | Verbose debug |
FAQs
promisify vs fs/promises?
Built-in promise modules preferred - promisify for third-party callback libs only.
What is util.format?
printf-style formatting like console.log first arg - legacy pattern.
inspect depth default?
2 - increase locally, keep low in production logs.
debuglog enable?
NODE_DEBUG=namespace environment variable at process start.
diagnostics_channel vs EventEmitter?
Channels designed for diagnostic probes - lower coupling than domain EventEmitter buses.
parseEnv vs dotenv?
parseEnv for in-memory tests; dotenv file loading for app bootstrap with Zod validation.
styleText requirements?
TTY color support - check process.stdout.isTTY for CI plain output.
promisify.custom?
Libraries can export custom promisify symbol for correct behavior.
relation to perf_hooks?
Detecting Event-Loop Blockage for ELU metrics.
util.inherits?
Legacy class inheritance - use ES class extends in modern code.
deprecate util._extend?
Use Object.assign - avoid deprecated util helpers.
testing debuglog?
Set NODE_DEBUG=test in test env and capture stderr if needed.
Related
- Callbacks vs Promises Migration - promisify patterns
- Reading Official Docs - NODE_DEBUG
- pino - production logging
- OpenTelemetry Node SDK - tracing
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.