Skip to content

Commit 16da91e

Browse files
committed
lib: add logger api in node core
1 parent 76215dc commit 16da91e

File tree

17 files changed

+2658
-1
lines changed

17 files changed

+2658
-1
lines changed

benchmark/logger/basic-json.js

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
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+
scenario: [
9+
'string-short',
10+
'string-long',
11+
'object-simple',
12+
'object-nested',
13+
'object-array',
14+
'object-mixed',
15+
'child-logger',
16+
'disabled-level',
17+
'error-object',
18+
],
19+
});
20+
21+
function main({ n, scenario }) {
22+
const { Logger, JSONConsumer } = require('node:logger');
23+
24+
const nullFd = fs.openSync('/dev/null', 'w');
25+
const consumer = new JSONConsumer({ stream: nullFd, level: 'info' });
26+
consumer.attach();
27+
28+
const logger = new Logger({ level: 'info' });
29+
30+
switch (scenario) {
31+
case 'string-short': {
32+
// Simple short string message
33+
bench.start();
34+
for (let i = 0; i < n; i++) {
35+
logger.info('hello');
36+
}
37+
bench.end(n);
38+
break;
39+
}
40+
41+
case 'string-long': {
42+
// Long string message (100 chars)
43+
const longMsg = 'This is a much longer log message that contains ' +
44+
'more text to serialize and process during logging operations';
45+
bench.start();
46+
for (let i = 0; i < n; i++) {
47+
logger.info(longMsg);
48+
}
49+
bench.end(n);
50+
break;
51+
}
52+
53+
case 'object-simple': {
54+
// Object with msg and a few string fields
55+
bench.start();
56+
for (let i = 0; i < n; i++) {
57+
logger.info({
58+
msg: 'user action',
59+
userId: 'user-123',
60+
action: 'login',
61+
});
62+
}
63+
bench.end(n);
64+
break;
65+
}
66+
67+
case 'object-nested': {
68+
// Object with nested structure
69+
bench.start();
70+
for (let i = 0; i < n; i++) {
71+
logger.info({
72+
msg: 'request completed',
73+
request: {
74+
method: 'POST',
75+
path: '/api/users',
76+
headers: {
77+
'content-type': 'application/json',
78+
'user-agent': 'Mozilla/5.0',
79+
},
80+
},
81+
response: {
82+
statusCode: 200,
83+
body: { success: true },
84+
},
85+
});
86+
}
87+
bench.end(n);
88+
break;
89+
}
90+
91+
case 'object-array': {
92+
// Object with array fields
93+
const tags = ['web', 'api', 'auth', 'production'];
94+
const ids = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
95+
bench.start();
96+
for (let i = 0; i < n; i++) {
97+
logger.info({
98+
msg: 'batch operation',
99+
tags,
100+
processedIds: ids,
101+
results: ['success', 'success', 'failed', 'success'],
102+
});
103+
}
104+
bench.end(n);
105+
break;
106+
}
107+
108+
case 'object-mixed': {
109+
// Mixed types: strings, numbers, booleans, null
110+
bench.start();
111+
for (let i = 0; i < n; i++) {
112+
logger.info({
113+
msg: 'mixed data',
114+
stringField: 'value',
115+
numberField: 42,
116+
floatField: 3.14159,
117+
booleanField: true,
118+
nullField: null,
119+
timestamp: 1704067200000,
120+
});
121+
}
122+
bench.end(n);
123+
break;
124+
}
125+
126+
case 'child-logger': {
127+
// Child logger with pre-bound context
128+
const childLogger = logger.child({
129+
service: 'api',
130+
version: '1.0.0',
131+
env: 'production',
132+
});
133+
bench.start();
134+
for (let i = 0; i < n; i++) {
135+
childLogger.info({
136+
msg: 'request',
137+
requestId: 'req-123',
138+
duration: 150,
139+
});
140+
}
141+
bench.end(n);
142+
break;
143+
}
144+
145+
case 'disabled-level': {
146+
// Logging at disabled level (debug when level is info)
147+
bench.start();
148+
for (let i = 0; i < n; i++) {
149+
logger.debug('this will be skipped');
150+
}
151+
bench.end(n);
152+
break;
153+
}
154+
155+
case 'error-object': {
156+
// Logging with Error object
157+
const error = new Error('Something went wrong');
158+
error.code = 'ERR_SOMETHING';
159+
bench.start();
160+
for (let i = 0; i < n; i++) {
161+
logger.error({
162+
msg: 'operation failed',
163+
err: error,
164+
operation: 'database-query',
165+
});
166+
}
167+
bench.end(n);
168+
break;
169+
}
170+
}
171+
172+
consumer.flushSync();
173+
fs.closeSync(nullFd);
174+
}

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/cli.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1207,6 +1207,17 @@ Specify the `module` containing exported [asynchronous module customization hook
12071207

12081208
This feature requires `--allow-worker` if used with the [Permission Model][].
12091209

1210+
### `--experimental-logger`
1211+
1212+
<!-- YAML
1213+
added: REPLACEME
1214+
-->
1215+
1216+
> Stability: 1.1 - Active Development
1217+
1218+
Enable the experimental `node:logger` module for structured logging.
1219+
See the [Logger][] documentation for more details.
1220+
12101221
### `--experimental-network-inspection`
12111222

12121223
<!-- YAML
@@ -3613,6 +3624,7 @@ one is included in the list below.
36133624
* `--experimental-import-meta-resolve`
36143625
* `--experimental-json-modules`
36153626
* `--experimental-loader`
3627+
* `--experimental-logger`
36163628
* `--experimental-modules`
36173629
* `--experimental-print-required-tla`
36183630
* `--experimental-quic`
@@ -4197,6 +4209,7 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12
41974209
[ExperimentalWarning: `vm.measureMemory` is an experimental feature]: vm.md#vmmeasurememoryoptions
41984210
[File System Permissions]: permissions.md#file-system-permissions
41994211
[Loading ECMAScript modules using `require()`]: modules.md#loading-ecmascript-modules-using-require
4212+
[Logger]: logger.md
42004213
[Module resolution and loading]: packages.md#module-resolution-and-loading
42014214
[Navigator API]: globals.md#navigator
42024215
[Node.js issue tracker]: https://github.com/nodejs/node/issues

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)

0 commit comments

Comments
 (0)