Skip to content

Commit 429359b

Browse files
committed
lib: added logger api in node core
1 parent d0c1024 commit 429359b

File tree

8 files changed

+1560
-0
lines changed

8 files changed

+1560
-0
lines changed

benchmark/logger/basic-json.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const { createLogger, JSONHandler } = require('node:logger');
5+
const fs = require('node:fs');
6+
7+
const bench = common.createBenchmark(main, {
8+
n: [1e5],
9+
level: ['info', 'debug'],
10+
fields: [0, 5],
11+
type: ['simple', 'child', 'disabled'],
12+
});
13+
14+
function main({ n, level, fields, type }) {
15+
// Use /dev/null to avoid I/O overhead in benchmarks
16+
const nullFd = fs.openSync('/dev/null', 'w');
17+
const handler = new JSONHandler({ stream: nullFd, level: 'info' });
18+
const logger = createLogger({ handler, level });
19+
20+
// Create test data based on fields count
21+
const logData = { msg: 'benchmark test message' };
22+
for (let i = 0; i < fields; i++) {
23+
logData[`field${i}`] = `value${i}`;
24+
}
25+
26+
let testLogger;
27+
switch (type) {
28+
case 'simple':
29+
testLogger = logger;
30+
break;
31+
case 'child':
32+
testLogger = logger.child({ requestId: 'bench-123', userId: 456 });
33+
break;
34+
case 'disabled': {
35+
// When level is debug and handler is info, logs will be disabled
36+
const nullFd2 = fs.openSync('/dev/null', 'w');
37+
38+
testLogger = createLogger({
39+
handler: new JSONHandler({ stream: nullFd2, level: 'warn' }),
40+
level: 'debug',
41+
});
42+
break;
43+
}
44+
}
45+
46+
bench.start();
47+
for (let i = 0; i < n; i++) {
48+
testLogger.info(logData);
49+
}
50+
bench.end(n);
51+
52+
handler.end();
53+
}

benchmark/logger/vs-pino.js

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const fs = require('node:fs');
5+
6+
const bench = common.createBenchmark(main, {
7+
n: [1e5],
8+
logger: ['node-logger', 'pino'],
9+
scenario: ['simple', 'child', 'disabled', 'fields'],
10+
});
11+
12+
function main({ n, logger, scenario }) {
13+
const nullFd = fs.openSync('/dev/null', 'w');
14+
let testLogger;
15+
let consumer;
16+
17+
if (logger === 'node-logger') {
18+
const { createLogger, JSONConsumer } = require('logger');
19+
20+
switch (scenario) {
21+
case 'simple': {
22+
consumer = new JSONConsumer({ stream: nullFd, level: 'info' });
23+
consumer.attach();
24+
testLogger = createLogger({ level: 'info' });
25+
26+
bench.start();
27+
for (let i = 0; i < n; i++) {
28+
testLogger.info('benchmark test message');
29+
}
30+
bench.end(n);
31+
break;
32+
}
33+
34+
case 'child': {
35+
consumer = new JSONConsumer({ stream: nullFd, level: 'info' });
36+
consumer.attach();
37+
const baseLogger = createLogger({ level: 'info' });
38+
testLogger = baseLogger.child({ requestId: 'req-123', userId: 456 });
39+
40+
bench.start();
41+
for (let i = 0; i < n; i++) {
42+
testLogger.info('benchmark test message');
43+
}
44+
bench.end(n);
45+
break;
46+
}
47+
48+
case 'disabled': {
49+
consumer = new JSONConsumer({ stream: nullFd, level: 'warn' });
50+
consumer.attach();
51+
testLogger = createLogger({ level: 'warn' });
52+
53+
bench.start();
54+
for (let i = 0; i < n; i++) {
55+
testLogger.debug('benchmark test message');
56+
}
57+
bench.end(n);
58+
break;
59+
}
60+
61+
case 'fields': {
62+
consumer = new JSONConsumer({ stream: nullFd, level: 'info' });
63+
consumer.attach();
64+
testLogger = createLogger({ level: 'info' });
65+
66+
bench.start();
67+
for (let i = 0; i < n; i++) {
68+
testLogger.info('benchmark test message', {
69+
field1: 'value1',
70+
field2: 'value2',
71+
field3: 'value3',
72+
field4: 'value4',
73+
field5: 'value5',
74+
});
75+
}
76+
bench.end(n);
77+
break;
78+
}
79+
}
80+
81+
if (consumer) {
82+
consumer.flushSync();
83+
}
84+
fs.closeSync(nullFd);
85+
86+
} else if (logger === 'pino') {
87+
const pino = require('pino');
88+
const destination = pino.destination({ dest: nullFd, sync: false });
89+
90+
switch (scenario) {
91+
case 'simple': {
92+
testLogger = pino({ level: 'info' }, destination);
93+
94+
bench.start();
95+
for (let i = 0; i < n; i++) {
96+
testLogger.info('benchmark test message');
97+
}
98+
bench.end(n);
99+
break;
100+
}
101+
102+
case 'child': {
103+
const baseLogger = pino({ level: 'info' }, destination);
104+
testLogger = baseLogger.child({ requestId: 'req-123', userId: 456 });
105+
106+
bench.start();
107+
for (let i = 0; i < n; i++) {
108+
testLogger.info('benchmark test message');
109+
}
110+
bench.end(n);
111+
break;
112+
}
113+
114+
case 'disabled': {
115+
testLogger = pino({ level: 'warn' }, destination);
116+
117+
bench.start();
118+
for (let i = 0; i < n; i++) {
119+
testLogger.debug('benchmark test message');
120+
}
121+
bench.end(n);
122+
break;
123+
}
124+
125+
case 'fields': {
126+
testLogger = pino({ level: 'info' }, destination);
127+
128+
bench.start();
129+
for (let i = 0; i < n; i++) {
130+
testLogger.info({
131+
msg: 'benchmark test message',
132+
field1: 'value1',
133+
field2: 'value2',
134+
field3: 'value3',
135+
field4: 'value4',
136+
field5: 'value5',
137+
});
138+
}
139+
bench.end(n);
140+
break;
141+
}
142+
}
143+
144+
destination.flushSync();
145+
}
146+
}

doc/api/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
* [UDP/datagram](dgram.md)
6666
* [URL](url.md)
6767
* [Utilities](util.md)
68+
* [Logger](logger.md)
6869
* [V8](v8.md)
6970
* [VM](vm.md)
7071
* [WASI](wasi.md)

doc/api/logger.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Logger
2+
3+
<!--introduced_in=v26.0.0-->
4+
5+
> Stability: 1 - Experimental
6+
7+
<!-- source_link=lib/logger.js -->
8+
9+
The `node:logger` module provides structured logging capabilities for Node.js
10+
applications.

lib/internal/logger/serializers.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
'use strict';
2+
3+
/**
4+
* Serializes an Error object
5+
* @param {Error} error
6+
* @returns {object}
7+
*/
8+
function serializeErr(error) {
9+
if (!error || typeof error !== 'object') {
10+
return error;
11+
}
12+
13+
const obj = {
14+
type: error.constructor.name,
15+
message: error.message,
16+
stack: error.stack,
17+
};
18+
19+
// Include additional error properties
20+
for (const key in error) {
21+
if (obj[key] === undefined) {
22+
obj[key] = error[key];
23+
}
24+
}
25+
26+
// Handle error code if present
27+
if (error.code !== undefined) {
28+
obj.code = error.code;
29+
}
30+
31+
// Handle error cause recursively
32+
if (error.cause !== undefined) {
33+
obj.cause = typeof error.cause === 'object' && error.cause !== null ?
34+
serializeErr(error.cause) :
35+
error.cause;
36+
}
37+
38+
return obj;
39+
}
40+
41+
/**
42+
* Serializes HTTP request object
43+
* @param {object} req - HTTP request
44+
* @returns {object}
45+
*/
46+
function req(req) {
47+
return {
48+
method: req.method,
49+
url: req.url,
50+
headers: req.headers,
51+
remoteAddress: req.socket?.remoteAddress,
52+
remotePort: req.socket?.remotePort,
53+
};
54+
}
55+
56+
/**
57+
* Serializes HTTP response object
58+
* @param {object} res - HTTP response
59+
* @returns {object}
60+
*/
61+
function res(res) {
62+
return {
63+
statusCode: res.statusCode,
64+
headers: res.getHeaders ? res.getHeaders() : res.headers,
65+
};
66+
}
67+
68+
module.exports = {
69+
err: serializeErr,
70+
req,
71+
res,
72+
};

0 commit comments

Comments
 (0)