Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions benchmark/logger/basic-json.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
'use strict';

const common = require('../common');
const { createLogger, JSONHandler } = require('node:logger');
const fs = require('node:fs');

const bench = common.createBenchmark(main, {
n: [1e5],
level: ['info', 'debug'],
fields: [0, 5],
type: ['simple', 'child', 'disabled'],
});

function main({ n, level, fields, type }) {
// Use /dev/null to avoid I/O overhead in benchmarks
const nullFd = fs.openSync('/dev/null', 'w');
const handler = new JSONHandler({ stream: nullFd, level: 'info' });
const logger = createLogger({ handler, level });

// Create test data based on fields count
const logData = { msg: 'benchmark test message' };
for (let i = 0; i < fields; i++) {
logData[`field${i}`] = `value${i}`;
}

let testLogger;
switch (type) {
case 'simple':
testLogger = logger;
break;
case 'child':
testLogger = logger.child({ requestId: 'bench-123', userId: 456 });
break;
case 'disabled': {
// When level is debug and handler is info, logs will be disabled
const nullFd2 = fs.openSync('/dev/null', 'w');

testLogger = createLogger({
handler: new JSONHandler({ stream: nullFd2, level: 'warn' }),
level: 'debug',
});
break;
}
}

bench.start();
for (let i = 0; i < n; i++) {
testLogger.info(logData);
}
bench.end(n);

handler.end();
}
146 changes: 146 additions & 0 deletions benchmark/logger/vs-pino.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
'use strict';

const common = require('../common');
const fs = require('node:fs');

const bench = common.createBenchmark(main, {
n: [1e5],
logger: ['node-logger', 'pino'],
scenario: ['simple', 'child', 'disabled', 'fields'],
});

function main({ n, logger, scenario }) {
const nullFd = fs.openSync('/dev/null', 'w');
let testLogger;
let consumer;

if (logger === 'node-logger') {
const { createLogger, JSONConsumer } = require('logger');

switch (scenario) {
case 'simple': {
consumer = new JSONConsumer({ stream: nullFd, level: 'info' });
consumer.attach();
testLogger = createLogger({ level: 'info' });

bench.start();
for (let i = 0; i < n; i++) {
testLogger.info('benchmark test message');
}
bench.end(n);
break;
}

case 'child': {
consumer = new JSONConsumer({ stream: nullFd, level: 'info' });
consumer.attach();
const baseLogger = createLogger({ level: 'info' });
testLogger = baseLogger.child({ requestId: 'req-123', userId: 456 });

bench.start();
for (let i = 0; i < n; i++) {
testLogger.info('benchmark test message');
}
bench.end(n);
break;
}

case 'disabled': {
consumer = new JSONConsumer({ stream: nullFd, level: 'warn' });
consumer.attach();
testLogger = createLogger({ level: 'warn' });

bench.start();
for (let i = 0; i < n; i++) {
testLogger.debug('benchmark test message');
}
bench.end(n);
break;
}

case 'fields': {
consumer = new JSONConsumer({ stream: nullFd, level: 'info' });
consumer.attach();
testLogger = createLogger({ level: 'info' });

bench.start();
for (let i = 0; i < n; i++) {
testLogger.info('benchmark test message', {
field1: 'value1',
field2: 'value2',
field3: 'value3',
field4: 'value4',
field5: 'value5',
});
}
bench.end(n);
break;
}
}

if (consumer) {
consumer.flushSync();
}
fs.closeSync(nullFd);

} else if (logger === 'pino') {
const pino = require('pino');
const destination = pino.destination({ dest: nullFd, sync: false });

switch (scenario) {
case 'simple': {
testLogger = pino({ level: 'info' }, destination);

bench.start();
for (let i = 0; i < n; i++) {
testLogger.info('benchmark test message');
}
bench.end(n);
break;
}

case 'child': {
const baseLogger = pino({ level: 'info' }, destination);
testLogger = baseLogger.child({ requestId: 'req-123', userId: 456 });

bench.start();
for (let i = 0; i < n; i++) {
testLogger.info('benchmark test message');
}
bench.end(n);
break;
}

case 'disabled': {
testLogger = pino({ level: 'warn' }, destination);

bench.start();
for (let i = 0; i < n; i++) {
testLogger.debug('benchmark test message');
}
bench.end(n);
break;
}

case 'fields': {
testLogger = pino({ level: 'info' }, destination);

bench.start();
for (let i = 0; i < n; i++) {
testLogger.info({
msg: 'benchmark test message',
field1: 'value1',
field2: 'value2',
field3: 'value3',
field4: 'value4',
field5: 'value5',
});
}
bench.end(n);
break;
}
}

destination.flushSync();
}
}
1 change: 1 addition & 0 deletions doc/api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
* [UDP/datagram](dgram.md)
* [URL](url.md)
* [Utilities](util.md)
* [Logger](logger.md)
* [V8](v8.md)
* [VM](vm.md)
* [WASI](wasi.md)
Expand Down
10 changes: 10 additions & 0 deletions doc/api/logger.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Logger

<!--introduced_in=v26.0.0-->

> Stability: 1 - Experimental
<!-- source_link=lib/logger.js -->

The `node:logger` module provides structured logging capabilities for Node.js
applications.
72 changes: 72 additions & 0 deletions lib/internal/logger/serializers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
'use strict';

/**
* Serializes an Error object
* @param {Error} error
* @returns {object}
*/
function serializeErr(error) {
if (!error || typeof error !== 'object') {
return error;
}

const obj = {
type: error.constructor.name,
message: error.message,
stack: error.stack,
};

// Include additional error properties
for (const key in error) {
if (obj[key] === undefined) {
obj[key] = error[key];
}
}

// Handle error code if present
if (error.code !== undefined) {
obj.code = error.code;
}

// Handle error cause recursively
if (error.cause !== undefined) {
obj.cause = typeof error.cause === 'object' && error.cause !== null ?
serializeErr(error.cause) :
error.cause;
}

return obj;
}

/**
* Serializes HTTP request object
* @param {object} req - HTTP request
* @returns {object}
*/
function req(req) {
return {
method: req.method,
url: req.url,
headers: req.headers,
remoteAddress: req.socket?.remoteAddress,
remotePort: req.socket?.remotePort,
};
}

/**
* Serializes HTTP response object
* @param {object} res - HTTP response
* @returns {object}
*/
function res(res) {
return {
statusCode: res.statusCode,
headers: res.getHeaders ? res.getHeaders() : res.headers,
};
}

module.exports = {
err: serializeErr,
req,
res,
};
Loading
Loading