-
-
Notifications
You must be signed in to change notification settings - Fork 34.3k
[WIP] lib: added logger api in node core #60468
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 13 commits
429359b
6d56e36
79faca4
1a76b7f
61461e4
c669174
3ec6c9f
58cca8b
c5cb707
5dc2b2d
a9e288f
8f88a71
83a12b7
22ed15d
c38b9b1
8a300fe
68c520b
1f5b449
e0ae4d6
8f4c641
09f0ef2
b23e669
76b0742
da165bf
6cf3d03
b856491
ad43d25
0cc656b
40095f6
3ee94ba
455f861
bda6d67
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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(); | ||
| } |
| 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('node: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(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| # 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. | ||
|
|
||
| ## Class: `LogConsumer` | ||
|
|
||
| ### `consumer.enabled(level)` | ||
|
|
||
| <!-- YAML | ||
| added: REPLACEME | ||
| --> | ||
|
|
||
| * `level` {string} The log level to check (e.g., `'debug'`, `'info'`, `'warn'`, | ||
| `'error'`, `'fatal'`). | ||
| * Returns: {boolean} `true` if the level is enabled, `false` otherwise. | ||
|
|
||
| Checks if a specific log level is enabled for this consumer. | ||
|
|
||
| This method returns `false` for unknown log levels without throwing an error. | ||
| Log levels are case-sensitive and must be one of the predefined levels: | ||
| `'trace'`, `'debug'`, `'info'`, `'warn'`, `'error'`, `'fatal'`. | ||
|
|
||
| ```js | ||
| const { LogConsumer } = require('node:logger'); | ||
|
|
||
| const consumer = new LogConsumer({ level: 'info' }); | ||
|
|
||
| console.log(consumer.enabled('debug')); // false (below threshold) | ||
| console.log(consumer.enabled('info')); // true | ||
| console.log(consumer.enabled('error')); // true | ||
| console.log(consumer.enabled('DEBUG')); // false (unknown level - case sensitive) | ||
| console.log(consumer.enabled('unknown')); // false (unknown level) | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| 'use strict'; | ||
| const { isNativeError } = require('internal/util/types'); | ||
mertcanaltin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| /** | ||
| * Serializes an Error object | ||
| * @param {Error} error | ||
| * @returns {object} | ||
| */ | ||
| function serializeErr(error) { | ||
| if (!isNativeError(error)) { | ||
| return error; | ||
| } | ||
|
|
||
| const obj = { | ||
| type: error.constructor.name, | ||
mertcanaltin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| message: error.message, | ||
| stack: error.stack, | ||
| }; | ||
|
|
||
| // Include additional error properties | ||
| for (const key in error) { | ||
| if (obj[key] === undefined) { | ||
| obj[key] = error[key]; | ||
| } | ||
| } | ||
mertcanaltin marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // Handle error code if present | ||
| if (error.code !== undefined) { | ||
| obj.code = error.code; | ||
| } | ||
mertcanaltin marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // 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 | ||
mertcanaltin marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| * @returns {object} | ||
| */ | ||
| function req(req) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might want to consider a similar pattern to https://nodejs.org/api/util.html#utilinspectcustom for these type-specific serializers.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thanks for suggestion, I will try this pattern |
||
| 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} | ||
mertcanaltin marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| */ | ||
| function res(res) { | ||
| return { | ||
| statusCode: res.statusCode, | ||
| headers: res.getHeaders ? res.getHeaders() : res.headers, | ||
mertcanaltin marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| }; | ||
| } | ||
|
|
||
| module.exports = { | ||
| err: serializeErr, | ||
| req, | ||
| res, | ||
| }; | ||
Uh oh!
There was an error while loading. Please reload this page.