Stability: 1.1 - Active Development
This module is only available with the --experimental-logger CLI flag.
The node:logger module provides high-performance structured logging
capabilities for Node.js applications. It uses diagnostics_channel internally
to dispatch log events to consumers, allowing multiple consumers to receive
logs independently.
The built-in console.log() and related console APIs are designed for
simple text output and debugging during development. They write directly to
stdout/stderr as unstructured text, provide no log levels beyond
log/warn/error/debug, and offer no mechanism for routing logs to
different destinations or filtering by severity in production environments.
node:logger addresses these limitations with capabilities that production
applications typically require:
- Structured output: Log records are emitted as structured objects (e.g., JSON) rather than plain text, making them machine-parseable and compatible with log aggregation systems like Elasticsearch, Datadog, and Splunk.
- Log levels with filtering: Six severity levels (
tracethroughfatal) with numeric ordering allow fine-grained control over which logs are emitted. Both loggers and consumers can set independent minimum levels, so debug logs can be written to a file without appearing on the console. - Producer-consumer separation via
diagnostics_channel: Loggers (producers) and consumers are decoupled throughdiagnostics_channel. Application code logs without knowing where logs go; consumers decide the destination (stdout, files, network). Multiple consumers can process the same log records independently. - Child loggers with context propagation:
logger.child()creates loggers that automatically include inherited context fields (e.g.,requestId,service) in every log record, eliminating the need to manually pass context through call chains. - Serializers and the
serializesymbol: Custom serializers and the[serialize]()symbol ensure sensitive data (passwords, tokens) is excluded from logs and complex objects are reduced to loggable representations without manual transformation at each call site. - Zero-cost level checks:
logger.debug.enabledallows skipping expensive computation when a level is disabled, somethingconsoledoes not support. - No third-party dependency required:
node:loggerprovides structured logging out of the box, removing the need for userland loggers like pino or winston for common use cases.
import { Logger, JSONConsumer } from 'node:logger';
const logger = new Logger({ level: 'info' });
const consumer = new JSONConsumer({ level: 'info' });
consumer.attach();
logger.info('Hello world');
// Outputs: {"level":"info","time":1234567890,"msg":"Hello world"}const { Logger, JSONConsumer } = require('node:logger');
const logger = new Logger({ level: 'info' });
const consumer = new JSONConsumer({ level: 'info' });
consumer.attach();
logger.info('Hello world');
// Outputs: {"level":"info","time":1234567890,"msg":"Hello world"}The logger supports the following log levels, in order of severity:
| Level | Description |
|---|---|
trace |
Detailed debugging information |
debug |
Debug information |
info |
General information |
warn |
Warning messages |
error |
Error messages |
fatal |
Critical errors |
Log levels follow RFC 5424 severity ordering (lowest to highest).
The Logger class is used to create log records and publish them to
diagnostics_channel channels.
options{Object}level{string} Minimum log level. Default:'info'.bindings{Object} Context fields added to all log records.serializers{Object} Custom serializer functions for specific fields. Default:{}.
Creates a new Logger instance.
import { Logger } from 'node:logger';
const logger = new Logger({
level: 'debug',
bindings: { service: 'my-app', version: '1.0.0' },
});const { Logger } = require('node:logger');
const logger = new Logger({
level: 'debug',
bindings: { service: 'my-app', version: '1.0.0' },
});msg{string} Log message. When called with a string, logs that string as the message.fields{Object} Additional fields to include in the log record. Only used with the stringmsgsignature. Each key-value pair is added to the log record. Values must be JSON-serializable (strings, numbers, booleans,null, plain objects, and arrays). Values that are not JSON-serializable (such asBigInt, functions, orSymbol) will causeJSON.stringify()to throw. If a field key matches a registered serializer, the serializer is applied to the value before serialization.obj{Object} Object containing a requiredmsg{string} property and additional fields that will be included in the log record. All properties other thanmsgare treated as log fields and follow the same serialization rules asfields. This form is useful when the set of fields is determined dynamically or when using spread syntax.error{Error} Error object to log. The error'smessageproperty becomes the log message and the error is serialized into theerrfield.
Logs a message at the trace level.
// String message
logger.trace('Detailed trace message');
// String message with additional fields
logger.trace('User action', { userId: 123, action: 'click' });
// Object form: msg is required, all other properties become log fields
logger.trace({ msg: 'Object format', requestId: 'abc123', duration: 42 });msg{string} Log message. When called with a string, logs that string as the message.fields{Object} Additional fields to include in the log record. Only used with the stringmsgsignature.obj{Object} Object containing a requiredmsg{string} property and additional fields that will be included in the log record.error{Error} Error object to log. The error'smessageproperty becomes the log message and the error is serialized into theerrfield.
Logs a message at the debug level.
logger.debug('Debug information');
logger.debug('Processing request', { requestId: 'abc123' });msg{string} Log message. When called with a string, logs that string as the message.fields{Object} Additional fields to include in the log record. Only used with the stringmsgsignature.obj{Object} Object containing a requiredmsg{string} property and additional fields that will be included in the log record.error{Error} Error object to log. The error'smessageproperty becomes the log message and the error is serialized into theerrfield.
Logs a message at the info level.
logger.info('Server started');
logger.info('Request received', { method: 'GET', path: '/api/users' });
logger.info({ msg: 'User logged in', userId: 123 });msg{string} Log message. When called with a string, logs that string as the message.fields{Object} Additional fields to include in the log record. Only used with the stringmsgsignature.obj{Object} Object containing a requiredmsg{string} property and additional fields that will be included in the log record.error{Error} Error object to log. The error'smessageproperty becomes the log message and the error is serialized into theerrfield.
Logs a message at the warn level.
logger.warn('Deprecated API used');
logger.warn('High memory usage', { memoryUsage: process.memoryUsage() });msg{string} Log message. When called with a string, logs that string as the message.fields{Object} Additional fields to include in the log record. Only used with the stringmsgsignature.obj{Object} Object containing a requiredmsg{string} property and additional fields that will be included in the log record.error{Error} Error object to log. The error'smessageproperty becomes the log message and the error is serialized into theerrfield.
Logs a message at the error level.
logger.error('Database connection failed');
logger.error(new Error('Something went wrong'));
logger.error(new Error('Request failed'), { requestId: 'abc123' });msg{string} Log message. When called with a string, logs that string as the message.fields{Object} Additional fields to include in the log record. Only used with the stringmsgsignature.obj{Object} Object containing a requiredmsg{string} property and additional fields that will be included in the log record.error{Error} Error object to log. The error'smessageproperty becomes the log message and the error is serialized into theerrfield.
Logs a message at the fatal level.
logger.fatal('Application crash');
logger.fatal(new Error('Unrecoverable error'));Stability: 1.1 - Active Development
bindings{Object} Additional context fields for the child logger.options{Object}level{string} Log level for the child logger.serializers{Object} Additional serializers for the child logger.
- Returns: {Logger} A new child logger instance.
Creates a child logger with additional context bindings. Child loggers inherit the parent's configuration and add their own bindings to all log records.
Note for library authors: The
leveloption inchild()is intended for application code only. Library and module authors should NOT override the log level in child loggers. Instead, libraries should inherit the parent logger's level to respect the application developer's log level configuration. Application developers can use this feature to isolate specific components or adjust verbosity for particular subsystems they directly control.
import { Logger } from 'node:logger';
const logger = new Logger({ bindings: { service: 'my-app' } });
const requestLogger = logger.child({ requestId: 'abc123' });
requestLogger.info('Processing request');
// Log includes: service: 'my-app', requestId: 'abc123'const { Logger } = require('node:logger');
const logger = new Logger({ bindings: { service: 'my-app' } });
const requestLogger = logger.child({ requestId: 'abc123' });
requestLogger.info('Processing request');
// Log includes: service: 'my-app', requestId: 'abc123'- {boolean}
trueif the level is enabled,falseotherwise.
Each log method (trace, debug, info, warn, error, fatal) has an
enabled property that indicates whether that level is enabled for this logger.
Use this to check if a level is enabled before performing expensive computations:
if (logger.debug.enabled) {
// Perform expensive debug computation only if debug is enabled
logger.debug('Debug info', { expensiveData: computeDebugData() });
}
// Typos will throw a TypeError (safer than silent failure)
// logger.debg.enabled β TypeError: Cannot read properties of undefinedFor dynamic level checks, use property access:
const level = config.logLevel; // 'info', 'debug', etc.
if (logger[level]?.enabled) {
logger[level]('Dynamic log message');
}The LogConsumer class is the base class for log consumers. Consumers
subscribe to diagnostics_channel events and process log records.
One channel is published per log level. The channel names are:
log:tracelog:debuglog:infolog:warnlog:errorlog:fatal
Advanced users may subscribe to these channels directly via
diagnostics_channel.channel(name) instead of using a LogConsumer.
options{Object}level{string} Minimum log level to consume. Default:'info'.
Creates a new LogConsumer instance.
Attaches the consumer to log channels. After calling this method, the consumer will receive log records from all loggers.
const consumer = new JSONConsumer({ level: 'info' });
consumer.attach();
// Consumer now receives all log records at 'info' level and aboveDetaches the consumer from log channels. After calling this method, the consumer will no longer receive log records.
const consumer = new JSONConsumer({ level: 'info' });
consumer.attach();
// ... later
consumer.detach();
// Consumer no longer receives log records- {boolean}
trueif the level is enabled,falseotherwise.
Each log level (trace, debug, info, warn, error, fatal) has an
enabled property that indicates whether that level is enabled for this
consumer.
const { LogConsumer } = require('node:logger');
const consumer = new LogConsumer({ level: 'info' });
console.log(consumer.debug.enabled); // false (below threshold)
console.log(consumer.info.enabled); // true
console.log(consumer.error.enabled); // true
// Typos will throw a TypeError (safer than silent failure)
// consumer.debg.enabled β TypeError: Cannot read properties of undefinedrecord{Object} The log record to handle.level{string} Log level.msg{string} Log message.time{number} Timestamp in milliseconds.bindingsStr{string} Pre-serialized bindings JSON string.fields{Object} Additional log fields.
Handles a log record. Subclasses must implement this method.
import { LogConsumer } from 'node:logger';
class CustomConsumer extends LogConsumer {
handle(record) {
console.log(`[${record.level}] ${record.msg}`);
}
}const { LogConsumer } = require('node:logger');
class CustomConsumer extends LogConsumer {
handle(record) {
console.log(`[${record.level}] ${record.msg}`);
}
}- Extends: {LogConsumer}
The JSONConsumer class outputs log records as JSON to a stream.
options{Object}-
level{string} Minimum log level to consume. Default:'info'. -
stream{number|string|Object} Output destination. One of:- A file descriptor (number).
- A file path (string).
- A stream-like object implementing all of the following methods:
write(chunk)flush(callback)flushSync()end()
A plain
stream.Writable(e.g. fromfs.createWriteStream()) does not satisfy this contract because it lacksflush()/flushSync(); wrap it or use a stream that implements the required methods. Default:stdout(fd 1). -
fields{Object} Additional fields to include in every log record. Default:{}.
-
Creates a new JSONConsumer instance.
import { JSONConsumer } from 'node:logger';
// Output to stdout (default)
const consumer1 = new JSONConsumer({ level: 'info' });
// Output to a file
const consumer2 = new JSONConsumer({
level: 'debug',
stream: '/var/log/app.log',
});
// Output to stderr
const consumer3 = new JSONConsumer({
level: 'error',
stream: 2,
});
// Add fields to every log
const consumer4 = new JSONConsumer({
level: 'info',
fields: { hostname: 'server-1', env: 'production' },
});const { JSONConsumer } = require('node:logger');
// Output to stdout (default)
const consumer1 = new JSONConsumer({ level: 'info' });
// Output to a file
const consumer2 = new JSONConsumer({
level: 'debug',
stream: '/var/log/app.log',
});The JSONConsumer uses JSON.stringify() internally. Values that are not
JSON-serializable will cause an error at log time:
BigIntvalues throw aTypeError(BigInt value can't be serialized in JSON).Symbolvalues are silently omitted byJSON.stringify().- Functions are silently omitted by
JSON.stringify(). - Circular references throw a
TypeError.
Error objects are an exception: the built-in err serializer runs by
default and converts them to plain objects (with type, message, stack,
and a recursively serialized cause) before they reach JSON.stringify(),
so logging errors directly does not trigger the issues above.
To log BigInt values, convert them to strings or numbers first:
logger.info('Large number', { id: bigIntValue.toString() });To handle non-serializable types automatically, use a custom serializer:
const logger = new Logger({
serializers: {
count: (value) => (typeof value === 'bigint' ? value.toString() : value),
},
});callback{Function} Called when flush completes.
Flushes pending writes to the underlying stream.
consumer.flush(() => {
console.log('All logs flushed');
});Flushes pending writes synchronously.
consumer.flushSync();Closes the consumer and its underlying stream.
consumer.end();- {Object}
An object containing standard serializer functions for common objects.
error{Error} Error object to serialize.- Returns: {Object} Serialized error object.
Serializes an Error object for logging. Includes type, message, stack,
and any additional properties. Traverses the cause chain if present.
import { Logger, stdSerializers } from 'node:logger';
const logger = new Logger({
serializers: {
err: stdSerializers.err,
},
});const { Logger, stdSerializers } = require('node:logger');
const logger = new Logger({
serializers: {
err: stdSerializers.err,
},
});request{http.IncomingMessage} HTTP request object.- Returns: {Object} Serialized request object with
method,url,headers,remoteAddress, andremotePort.
Serializes an HTTP request object for logging.
const http = require('node:http');
const { Logger, JSONConsumer, stdSerializers } = require('node:logger');
const logger = new Logger({
serializers: {
req: stdSerializers.req,
},
});
const consumer = new JSONConsumer();
consumer.attach();
http.createServer((req, res) => {
logger.info('Request received', { req });
res.end('OK');
}).listen(3000);response{http.ServerResponse} HTTP response object.- Returns: {Object} Serialized response object with
statusCodeandheaders.
Serializes an HTTP response object for logging.
logger.info('Response sent', { res });- {symbol}
A symbol that objects can implement to define custom serialization behavior
for logging. Similar to util.inspect.custom.
When an object with a [serialize]() method is logged, the logger will call
that method instead of serializing the object directly. This allows objects
to control which properties are included in logs, filtering out sensitive
data like passwords or tokens.
import { Logger, JSONConsumer, serialize } from 'node:logger';
class User {
constructor(id, name, password) {
this.id = id;
this.name = name;
this.password = password; // Sensitive!
}
// Define custom serialization
[serialize]() {
return {
id: this.id,
name: this.name,
// password is excluded
};
}
}
const consumer = new JSONConsumer();
consumer.attach();
const logger = new Logger();
const user = new User(1, 'Alice', 'secret123');
logger.info({ msg: 'User logged in', user });
// Output: {"level":"info","time":...,"msg":"User logged in","user":{"id":1,"name":"Alice"}}
// Note: password is not included in the outputconst { Logger, JSONConsumer, serialize } = require('node:logger');
class DatabaseConnection {
constructor(host, user, password) {
this.host = host;
this.user = user;
this.password = password;
}
[serialize]() {
return {
host: this.host,
user: this.user,
connected: this.isConnected,
// password is excluded
};
}
}The serialize symbol takes precedence over field-specific serializers.
If an object has both a [serialize]() method and a matching serializer
in the logger's serializers option, the [serialize]() method will be used.
import { Logger, JSONConsumer } from 'node:logger';
// Create a logger
const logger = new Logger({ level: 'info' });
// Create and attach a consumer
const consumer = new JSONConsumer({ level: 'info' });
consumer.attach();
// Log messages
logger.info('Application started');
logger.info('User logged in', { userId: 123 });
logger.error(new Error('Something went wrong'));import { Logger, JSONConsumer } from 'node:logger';
import { randomUUID } from 'node:crypto';
const logger = new Logger({
bindings: { service: 'api-server' },
});
const consumer = new JSONConsumer();
consumer.attach();
function handleRequest(req, res) {
const requestLogger = logger.child({
requestId: randomUUID(),
method: req.method,
path: req.url,
});
requestLogger.info('Request started');
// ... handle request ...
requestLogger.info('Request completed', { statusCode: res.statusCode });
}import { Logger, JSONConsumer } from 'node:logger';
const logger = new Logger({ level: 'trace' });
// Console output for development (info and above)
const consoleConsumer = new JSONConsumer({ level: 'info' });
consoleConsumer.attach();
// File output for debugging (all levels)
const fileConsumer = new JSONConsumer({
level: 'trace',
stream: '/var/log/app-debug.log',
});
fileConsumer.attach();
// Error file (errors only)
const errorConsumer = new JSONConsumer({
level: 'error',
stream: '/var/log/app-error.log',
});
errorConsumer.attach();import { Logger, JSONConsumer, stdSerializers } from 'node:logger';
const logger = new Logger({
serializers: {
err: stdSerializers.err,
req: stdSerializers.req,
res: stdSerializers.res,
user: (user) => ({ id: user.id, email: user.email }), // Custom serializer
},
});
const consumer = new JSONConsumer();
consumer.attach();
logger.info('User action', {
user: { id: 1, email: 'user@example.com', password: 'secret' },
});
// Output will not include password due to custom serializerimport { LogConsumer } from 'node:logger';
import { styleText } from 'node:util';
const levelStyles = {
trace: 'gray',
debug: 'cyan',
info: 'green',
warn: 'yellow',
error: 'red',
fatal: 'magenta',
};
class ConsoleColorConsumer extends LogConsumer {
handle(record) {
const style = levelStyles[record.level] ?? 'white';
const label = styleText(style, `[${record.level.toUpperCase()}]`);
console.log(`${label} ${record.msg}`);
}
}
const consumer = new ConsoleColorConsumer({ level: 'debug' });
consumer.attach();