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.
Search across all documentation pages
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.
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:
JSON.stringify failuresNODE_DEBUG=billingstyleTextimport { 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:
inspect truncates depth/array for readable logs vs circular JSON errorsdebuglog only prints when NODE_DEBUG=api or NODE_DEBUG=*diagnostics_channel publishes/subscribes cross-cutting events without tight couplingparseEnv parses dotenv-style strings in tests without files(err, result) callback last - returns Promise-returning function.util.inspect.custom on classes for redacted representations.| Need | Tool |
|---|---|
| Callback → Promise | promisify |
| Safe object log | inspect |
| Feature flags debug | debuglog |
| Performance timing | perf_hooks |
| OpenTelemetry | OTel SDK |
import { types } from 'node:util';
if (types.isPromise(maybe)) {
await maybe;
}inspect limits + redaction.JSON.stringify for wire format.| 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 |
Built-in promise modules preferred - promisify for third-party callback libs only.
printf-style formatting like console.log first arg - legacy pattern.
2 - increase locally, keep low in production logs.
NODE_DEBUG=namespace environment variable at process start.
Channels designed for diagnostic probes - lower coupling than domain EventEmitter buses.
parseEnv for in-memory tests; dotenv file loading for app bootstrap with Zod validation.
TTY color support - check process.stdout.isTTY for CI plain output.
Libraries can export custom promisify symbol for correct behavior.
Detecting Event-Loop Blockage for ELU metrics.
Legacy class inheritance - use ES class extends in modern code.
Use Object.assign - avoid deprecated util helpers.
Set NODE_DEBUG=test in test env and capture stderr if needed.
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.
Reviewed by Chris St. John·Last updated Jul 16, 2026