From 1460394d768d4c8a67127c9c6bb2d14584ffcc94 Mon Sep 17 00:00:00 2001 From: Gerardo Lima Date: Sat, 9 Nov 2024 18:55:35 +0000 Subject: [PATCH 1/8] feat(serializers): avoid implicit sanitization When serializers are defined for HTTP Request or HTTP Response, do not run the correspondent `stdSerializers` before calling the provided serializer functions. ISSUE https://github.com/pinojs/pino/issues/1991 --- docs/api.md | 16 +++++++++++++++- lib/proto.js | 9 +++++++++ lib/symbols.js | 6 +++++- lib/tools.js | 15 ++++++++------- pino.d.ts | 10 ++++++++++ pino.js | 14 +++++++++++++- test/http.test.js | 46 ++++++++++++++++++---------------------------- 7 files changed, 78 insertions(+), 38 deletions(-) diff --git a/docs/api.md b/docs/api.md index b3d5a0b6a..e941ad3d5 100644 --- a/docs/api.md +++ b/docs/api.md @@ -515,13 +515,27 @@ Default: `'msg'` The string key for the 'message' in the JSON object. - + #### `errorKey` (String) Default: `'err'` The string key for the 'error' in the JSON object. + +#### `requestKey` (String) + +Default: `'req'` + +The string key for the 'Request' in the JSON object. + + +#### `responseKey` (String) + +Default: `'res'` + +The string key for the 'Response' in the JSON object. + #### `nestedKey` (String) diff --git a/lib/proto.js b/lib/proto.js index ef49a7122..8519a6d49 100644 --- a/lib/proto.js +++ b/lib/proto.js @@ -3,6 +3,7 @@ /* eslint no-prototype-builtins: 0 */ const { EventEmitter } = require('node:events') +const { IncomingMessage, ServerResponse} = require('node:http') const { lsCacheSym, levelValSym, @@ -20,6 +21,8 @@ const { serializersSym, formattersSym, errorKeySym, + requestKeySym, + responseKeySym, messageKeySym, useOnlyCustomLevelsSym, needsMetadataGsym, @@ -182,6 +185,8 @@ function write (_obj, msg, num) { const t = this[timeSym]() const mixin = this[mixinSym] const errorKey = this[errorKeySym] + const requestKey = this[requestKeySym] + const responseKey = this[responseKeySym] const messageKey = this[messageKeySym] const mixinMergeStrategy = this[mixinMergeStrategySym] || defaultMixinMergeStrategy let obj @@ -193,6 +198,10 @@ function write (_obj, msg, num) { if (msg === undefined) { msg = _obj.message } + } else if (_obj instanceof IncomingMessage) { + obj = { [requestKey]: _obj } + } else if (_obj instanceof ServerResponse) { + obj = { [responseKey]: _obj } } else { obj = _obj if (msg === undefined && _obj[messageKey] === undefined && _obj[errorKey]) { diff --git a/lib/symbols.js b/lib/symbols.js index 69f1a9d25..050fe912a 100644 --- a/lib/symbols.js +++ b/lib/symbols.js @@ -29,6 +29,8 @@ const nestedKeySym = Symbol('pino.nestedKey') const nestedKeyStrSym = Symbol('pino.nestedKeyStr') const mixinMergeStrategySym = Symbol('pino.mixinMergeStrategy') const msgPrefixSym = Symbol('pino.msgPrefix') +const responseKeySym = Symbol('pino.responseKey') +const requestKeySym = Symbol('pino.requestKey') const wildcardFirstSym = Symbol('pino.wildcardFirst') @@ -70,5 +72,7 @@ module.exports = { hooksSym, nestedKeyStrSym, mixinMergeStrategySym, - msgPrefixSym + msgPrefixSym, + responseKeySym, + requestKeySym, } diff --git a/lib/tools.js b/lib/tools.js index 5f68f588e..f3c746d8c 100644 --- a/lib/tools.js +++ b/lib/tools.js @@ -21,6 +21,8 @@ const { formattersSym, messageKeySym, errorKeySym, + requestKeySym, + responseKeySym, nestedKeyStrSym, msgPrefixSym } = require('./symbols') @@ -40,13 +42,6 @@ function genLog (level, hook) { function LOG (o, ...n) { if (typeof o === 'object') { let msg = o - if (o !== null) { - if (o.method && o.headers && o.socket) { - o = mapHttpRequest(o) - } else if (typeof o.setHeader === 'function') { - o = mapHttpResponse(o) - } - } let formatParams if (msg === null && n.length === 0) { formatParams = [null] @@ -113,6 +108,8 @@ function asJson (obj, msg, num, time) { const formatters = this[formattersSym] const messageKey = this[messageKeySym] const errorKey = this[errorKeySym] + const responseKey = this[responseKeySym] + const requestKey = this[requestKeySym] let data = this[lsCacheSym][num] + time // we need the child bindings added to the output first so instance logged @@ -132,6 +129,10 @@ function asJson (obj, msg, num, time) { value = serializers[key](value) } else if (key === errorKey && serializers.err) { value = serializers.err(value) + } else if (key === requestKey && serializers.req) { + value = serializers.req(value) + } else if (key === responseKey && serializers.res) { + value = serializers.res(value) } const stringifier = stringifiers[key] || wildcardStringifier diff --git a/pino.d.ts b/pino.d.ts index cdc5958d3..b8378bcf0 100644 --- a/pino.d.ts +++ b/pino.d.ts @@ -416,6 +416,14 @@ declare namespace pino { * The string key for the 'error' in the JSON object. Default: "err". */ errorKey?: string; + /** + * The string key for the 'Request' in the JSON object. Default: "req". + */ + requestKey?: string; + /** + * The string key for the 'Response' in the JSON object. Default: "res". + */ + responseKey?: string; /** * The string key to place any logged object under. */ @@ -749,6 +757,8 @@ declare namespace pino { readonly formatOptsSym: unique symbol; readonly messageKeySym: unique symbol; readonly errorKeySym: unique symbol; + readonly responseKeySym: unique symbol; + readonly requestKeySym: unique symbol; readonly nestedKeySym: unique symbol; readonly wildcardFirstSym: unique symbol; readonly needsMetadataGsym: unique symbol; diff --git a/pino.js b/pino.js index 2398e5acb..962424474 100644 --- a/pino.js +++ b/pino.js @@ -35,6 +35,8 @@ const { formatOptsSym, messageKeySym, errorKeySym, + requestKeySym, + responseKeySym, nestedKeySym, mixinSym, levelCompSym, @@ -49,17 +51,23 @@ const { epochTime, nullTime } = time const { pid } = process const hostname = os.hostname() const defaultErrorSerializer = stdSerializers.err +const defaultRequestSerializer = stdSerializers.req +const defaultResponseSerializer = stdSerializers.res const defaultOptions = { level: 'info', levelComparison: SORTING_ORDER.ASC, levels: DEFAULT_LEVELS, messageKey: 'msg', errorKey: 'err', + requestKey: 'req', + responseKey: 'res', nestedKey: null, enabled: true, base: { pid, hostname }, serializers: Object.assign(Object.create(null), { - err: defaultErrorSerializer + err: defaultErrorSerializer, + req: defaultRequestSerializer, + res: defaultResponseSerializer, }), formatters: Object.assign(Object.create(null), { bindings (bindings) { @@ -98,6 +106,8 @@ function pino (...args) { timestamp, messageKey, errorKey, + requestKey, + responseKey, nestedKey, base, name, @@ -185,6 +195,8 @@ function pino (...args) { [formatOptsSym]: formatOpts, [messageKeySym]: messageKey, [errorKeySym]: errorKey, + [requestKeySym]: requestKey, + [responseKeySym]: responseKey, [nestedKeySym]: nestedKey, // protect against injection [nestedKeyStrSym]: nestedKey ? `,${JSON.stringify(nestedKey)}:{` : '', diff --git a/test/http.test.js b/test/http.test.js index 650868ffe..c6eace883 100644 --- a/test/http.test.js +++ b/test/http.test.js @@ -44,33 +44,27 @@ test('http request support', async ({ ok, same, error, teardown }) => { server.close() }) -test('http request support via serializer', async ({ ok, same, error, teardown }) => { +test('http request support via serializer', async ({ error, match }) => { let originalReq const instance = pino({ serializers: { - req: pino.stdSerializers.req + req: (req) => req.arbitraryProperty, } - }, sink((chunk, enc) => { - ok(new Date(chunk.time) <= new Date(), 'time is greater than Date.now()') - delete chunk.time - same(chunk, { + }, sink((chunk, _enc) => { + match(chunk, { pid, hostname, level: 30, msg: 'my request', - req: { - method: originalReq.method, - url: originalReq.url, - headers: originalReq.headers, - remoteAddress: originalReq.socket.remoteAddress, - remotePort: originalReq.socket.remotePort - } + req: originalReq.arbitraryProperty, }) })) const server = http.createServer(function (req, res) { + req.arbitraryProperty = Math.random() + originalReq = req - instance.info({ req }, 'my request') + instance.info(req, 'my request') res.end('hello') }) server.unref() @@ -160,34 +154,30 @@ test('http response support', async ({ ok, same, error, teardown }) => { server.close() }) -test('http response support via a serializer', async ({ ok, same, error, teardown }) => { +test('http response support via a serializer', async ({ match, error }) => { + let originalRes const instance = pino({ serializers: { - res: pino.stdSerializers.res + res: (res) => res.arbitraryProperty, } }, sink((chunk, enc) => { - ok(new Date(chunk.time) <= new Date(), 'time is greater than Date.now()') - delete chunk.time - same(chunk, { + match(chunk, { pid, hostname, level: 30, msg: 'my response', - res: { - statusCode: 200, - headers: { - 'x-single': 'y', - 'x-multi': [1, 2] - } - } + res: originalRes.arbitraryProperty, }) })) - const server = http.createServer(function (req, res) { + const server = http.createServer(function (_req, res) { + res.arbitraryProperty = Math.random() + + originalRes = res res.setHeader('x-single', 'y') res.setHeader('x-multi', [1, 2]) res.end('hello') - instance.info({ res }, 'my response') + instance.info(res, 'my response') }) server.unref() From d84609d92d37a9eb8a5052b1ff52fee864bb7d10 Mon Sep 17 00:00:00 2001 From: Gerardo Lima Date: Sat, 9 Nov 2024 19:28:44 +0000 Subject: [PATCH 2/8] test(http): use alternative names to `req` and `res` --- lib/symbols.js | 6 +++--- test/http.test.js | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/symbols.js b/lib/symbols.js index 050fe912a..9077e5899 100644 --- a/lib/symbols.js +++ b/lib/symbols.js @@ -64,6 +64,8 @@ module.exports = { formatOptsSym, messageKeySym, errorKeySym, + responseKeySym, + requestKeySym, nestedKeySym, wildcardFirstSym, needsMetadataGsym, @@ -72,7 +74,5 @@ module.exports = { hooksSym, nestedKeyStrSym, mixinMergeStrategySym, - msgPrefixSym, - responseKeySym, - requestKeySym, + msgPrefixSym } diff --git a/test/http.test.js b/test/http.test.js index c6eace883..905daeb86 100644 --- a/test/http.test.js +++ b/test/http.test.js @@ -47,6 +47,7 @@ test('http request support', async ({ ok, same, error, teardown }) => { test('http request support via serializer', async ({ error, match }) => { let originalReq const instance = pino({ + requestKey: 'myRequest', serializers: { req: (req) => req.arbitraryProperty, } @@ -56,7 +57,7 @@ test('http request support via serializer', async ({ error, match }) => { hostname, level: 30, msg: 'my request', - req: originalReq.arbitraryProperty, + myRequest: originalReq.arbitraryProperty, }) })) @@ -70,7 +71,6 @@ test('http request support via serializer', async ({ error, match }) => { server.unref() server.listen() const err = await once(server, 'listening') - error(err) const res = await once(http.get('http://localhost:' + server.address().port), 'response') res.resume() @@ -157,16 +157,17 @@ test('http response support', async ({ ok, same, error, teardown }) => { test('http response support via a serializer', async ({ match, error }) => { let originalRes const instance = pino({ + responseKey: 'myResponse', serializers: { res: (res) => res.arbitraryProperty, } - }, sink((chunk, enc) => { + }, sink((chunk, _enc) => { match(chunk, { pid, hostname, level: 30, msg: 'my response', - res: originalRes.arbitraryProperty, + myResponse: originalRes.arbitraryProperty, }) })) @@ -196,7 +197,7 @@ test('http request support via serializer in a child', async ({ ok, same, error, serializers: { req: pino.stdSerializers.req } - }, sink((chunk, enc) => { + }, sink((chunk, _enc) => { ok(new Date(chunk.time) <= new Date(), 'time is greater than Date.now()') delete chunk.time same(chunk, { @@ -224,7 +225,6 @@ test('http request support via serializer in a child', async ({ ok, same, error, server.unref() server.listen() const err = await once(server, 'listening') - error(err) const res = await once(http.get('http://localhost:' + server.address().port), 'response') res.resume() From b8b2b74d8df394b89014e09e7f9ce964c469abfe Mon Sep 17 00:00:00 2001 From: Gerardo Lima Date: Sun, 10 Nov 2024 17:07:35 +0000 Subject: [PATCH 3/8] test(http): restore original tests --- test/http.test.js | 109 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 97 insertions(+), 12 deletions(-) diff --git a/test/http.test.js b/test/http.test.js index 905daeb86..d01cf86d3 100644 --- a/test/http.test.js +++ b/test/http.test.js @@ -44,12 +44,54 @@ test('http request support', async ({ ok, same, error, teardown }) => { server.close() }) -test('http request support via serializer', async ({ error, match }) => { +test('http request support via serializer', async ({ ok, same, error, teardown }) => { + let originalReq + const instance = pino({ + serializers: { + req: pino.stdSerializers.req + } + }, sink((chunk, enc) => { + ok(new Date(chunk.time) <= new Date(), 'time is greater than Date.now()') + delete chunk.time + same(chunk, { + pid, + hostname, + level: 30, + msg: 'my request', + req: { + method: originalReq.method, + url: originalReq.url, + headers: originalReq.headers, + remoteAddress: originalReq.socket.remoteAddress, + remotePort: originalReq.socket.remotePort + } + }) + })) + + const server = http.createServer(function (req, res) { + originalReq = req + instance.info({ req }, 'my request') + res.end('hello') + }) + server.unref() + server.listen() + const err = await once(server, 'listening') + error(err) + + const res = await once(http.get('http://localhost:' + server.address().port), 'response') + res.resume() + server.close() +}) + +test('http request support via serializer (avoids stdSerializers)', async ({ error, equal, match }) => { let originalReq const instance = pino({ requestKey: 'myRequest', serializers: { - req: (req) => req.arbitraryProperty, + req: (req) => { + equal(req, originalReq) + return req.arbitraryProperty + } } }, sink((chunk, _enc) => { match(chunk, { @@ -57,20 +99,21 @@ test('http request support via serializer', async ({ error, match }) => { hostname, level: 30, msg: 'my request', - myRequest: originalReq.arbitraryProperty, + myRequest: originalReq.arbitraryProperty }) })) const server = http.createServer(function (req, res) { + originalReq = req req.arbitraryProperty = Math.random() - originalReq = req instance.info(req, 'my request') res.end('hello') }) server.unref() server.listen() const err = await once(server, 'listening') + error(err) const res = await once(http.get('http://localhost:' + server.address().port), 'response') res.resume() @@ -154,12 +197,55 @@ test('http response support', async ({ ok, same, error, teardown }) => { server.close() }) -test('http response support via a serializer', async ({ match, error }) => { +test('http response support via a serializer', async ({ ok, same, error, teardown }) => { + const instance = pino({ + serializers: { + res: pino.stdSerializers.res + } + }, sink((chunk, enc) => { + ok(new Date(chunk.time) <= new Date(), 'time is greater than Date.now()') + delete chunk.time + same(chunk, { + pid, + hostname, + level: 30, + msg: 'my response', + res: { + statusCode: 200, + headers: { + 'x-single': 'y', + 'x-multi': [1, 2] + } + } + }) + })) + + const server = http.createServer(function (req, res) { + res.setHeader('x-single', 'y') + res.setHeader('x-multi', [1, 2]) + res.end('hello') + instance.info({ res }, 'my response') + }) + + server.unref() + server.listen() + const err = await once(server, 'listening') + error(err) + + const res = await once(http.get('http://localhost:' + server.address().port), 'response') + res.resume() + server.close() +}) + +test('http response support via serializer (avoids stdSerializers)', async ({ match, equal, error }) => { let originalRes const instance = pino({ responseKey: 'myResponse', serializers: { - res: (res) => res.arbitraryProperty, + res: (res) => { + equal(res, originalRes) + return res.arbitraryProperty + } } }, sink((chunk, _enc) => { match(chunk, { @@ -167,18 +253,16 @@ test('http response support via a serializer', async ({ match, error }) => { hostname, level: 30, msg: 'my response', - myResponse: originalRes.arbitraryProperty, + myResponse: originalRes.arbitraryProperty }) })) const server = http.createServer(function (_req, res) { + originalRes = res res.arbitraryProperty = Math.random() - originalRes = res - res.setHeader('x-single', 'y') - res.setHeader('x-multi', [1, 2]) - res.end('hello') instance.info(res, 'my response') + res.end('hello') }) server.unref() @@ -197,7 +281,7 @@ test('http request support via serializer in a child', async ({ ok, same, error, serializers: { req: pino.stdSerializers.req } - }, sink((chunk, _enc) => { + }, sink((chunk, enc) => { ok(new Date(chunk.time) <= new Date(), 'time is greater than Date.now()') delete chunk.time same(chunk, { @@ -225,6 +309,7 @@ test('http request support via serializer in a child', async ({ ok, same, error, server.unref() server.listen() const err = await once(server, 'listening') + error(err) const res = await once(http.get('http://localhost:' + server.address().port), 'response') res.resume() From 39b069799a9aedbf8f856006e7c91376fbedc630 Mon Sep 17 00:00:00 2001 From: Gerardo Lima Date: Sun, 10 Nov 2024 17:08:13 +0000 Subject: [PATCH 4/8] fix: linter issues --- lib/proto.js | 2 +- lib/tools.js | 1 - pino.js | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/proto.js b/lib/proto.js index 8519a6d49..1454c84c3 100644 --- a/lib/proto.js +++ b/lib/proto.js @@ -3,7 +3,7 @@ /* eslint no-prototype-builtins: 0 */ const { EventEmitter } = require('node:events') -const { IncomingMessage, ServerResponse} = require('node:http') +const { IncomingMessage, ServerResponse } = require('node:http') const { lsCacheSym, levelValSym, diff --git a/lib/tools.js b/lib/tools.js index f3c746d8c..74746677a 100644 --- a/lib/tools.js +++ b/lib/tools.js @@ -3,7 +3,6 @@ /* eslint no-prototype-builtins: 0 */ const format = require('quick-format-unescaped') -const { mapHttpRequest, mapHttpResponse } = require('pino-std-serializers') const SonicBoom = require('sonic-boom') const onExit = require('on-exit-leak-free') const { diff --git a/pino.js b/pino.js index 962424474..a4391c937 100644 --- a/pino.js +++ b/pino.js @@ -67,7 +67,7 @@ const defaultOptions = { serializers: Object.assign(Object.create(null), { err: defaultErrorSerializer, req: defaultRequestSerializer, - res: defaultResponseSerializer, + res: defaultResponseSerializer }), formatters: Object.assign(Object.create(null), { bindings (bindings) { From 7d4796420dde064a0384f8038c0d76256d6854f8 Mon Sep 17 00:00:00 2001 From: Gerardo Lima Date: Tue, 12 Nov 2024 17:17:48 +0000 Subject: [PATCH 5/8] tests: fix assertions on default serializers --- test/serializers.test.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/serializers.test.js b/test/serializers.test.js index 35edee8b7..986fa4a08 100644 --- a/test/serializers.test.js +++ b/test/serializers.test.js @@ -118,7 +118,9 @@ test('child does not overwrite parent serializers', async ({ equal }) => { test('Symbol.for(\'pino.serializers\')', async ({ equal, same, not }) => { const stream = sink() const expected = Object.assign({ - err: stdSerializers.err + err: stdSerializers.err, + req: stdSerializers.req, + res: stdSerializers.res }, parentSerializers) const parent = pino({ serializers: parentSerializers }, stream) const child = parent.child({ a: 'property' }) @@ -158,7 +160,9 @@ test('children inherit parent Symbol serializers', async ({ equal, same, not }) [Symbol.for('b')]: b } const expected = Object.assign({ - err: stdSerializers.err + err: stdSerializers.err, + req: stdSerializers.req, + res: stdSerializers.res }, symbolSerializers) const parent = pino({ serializers: symbolSerializers }, stream) From b3937327c145e00e8ad4d004859d96b95a0757a5 Mon Sep 17 00:00:00 2001 From: Gerardo Lima Date: Wed, 27 Nov 2024 20:23:12 +0000 Subject: [PATCH 6/8] feat(serializers): use properties to infer types Request and Response --- lib/proto.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/proto.js b/lib/proto.js index 1454c84c3..3f1fa6541 100644 --- a/lib/proto.js +++ b/lib/proto.js @@ -3,7 +3,6 @@ /* eslint no-prototype-builtins: 0 */ const { EventEmitter } = require('node:events') -const { IncomingMessage, ServerResponse } = require('node:http') const { lsCacheSym, levelValSym, @@ -198,9 +197,9 @@ function write (_obj, msg, num) { if (msg === undefined) { msg = _obj.message } - } else if (_obj instanceof IncomingMessage) { + } else if (_obj.method && _obj.headers && _obj.socket) { obj = { [requestKey]: _obj } - } else if (_obj instanceof ServerResponse) { + } else if (typeof _obj.setHeader === 'function') { obj = { [responseKey]: _obj } } else { obj = _obj From ebd84c37c14e9ad9ba790ef51f56c079041206f8 Mon Sep 17 00:00:00 2001 From: Gerardo Lima Date: Mon, 9 Dec 2024 15:34:31 +0000 Subject: [PATCH 7/8] feat(future): add flags to anticipate breaking changes --- docs/api.md | 14 ++++++++++++ lib/deprecations.js | 21 ++++++++++++++---- lib/proto.js | 9 +++++++- lib/symbols.js | 4 +++- pino.d.ts | 18 ++++++++++++++- pino.js | 16 ++++++++++---- test/deprecations.test.js | 46 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 117 insertions(+), 11 deletions(-) create mode 100644 test/deprecations.test.js diff --git a/docs/api.md b/docs/api.md index e941ad3d5..00625ab6c 100644 --- a/docs/api.md +++ b/docs/api.md @@ -30,6 +30,7 @@ * [pino.stdTimeFunctions](#pino-stdtimefunctions) * [pino.symbols](#pino-symbols) * [pino.version](#pino-version) + * [pino.futures](#pino-version) * [Interfaces](#interfaces) * [MultiStreamRes](#multistreamres) * [StreamEntry](#streamentry) @@ -614,6 +615,19 @@ const parent = require('pino')({ onChild: (instance) => { parent.child(bindings) ``` + +#### `future` (Object) + +The `future` object contains _opt-in_ flags specific to a Pino major version. These flags are used to change behavior, +anticipating breaking-changes that will be introduced in the next major version. +```js +const parent = require('pino')({ + future: { + skipUnconditionalStdSerializers: true + } +}) +``` + ### `destination` (Number | String | Object | DestinationStream | SonicBoomOpts | WritableStream) diff --git a/lib/deprecations.js b/lib/deprecations.js index 806c5362e..72f2523a2 100644 --- a/lib/deprecations.js +++ b/lib/deprecations.js @@ -1,8 +1,21 @@ 'use strict' -const warning = require('process-warning')() -module.exports = warning +const warning = require('process-warning') -// const warnName = 'PinoWarning' +/** + * Future flags, specific to the current major-version of Pino. These flags allow for breaking-changes to be introduced + * on a opt-in basis, anticipating behavior of the next major-version. All future flag must be frozen as false. These + * future flags are specific to Pino major-version 9. + */ +const future = Object.freeze({ + skipUnconditionalStdSerializers: false // see PINODEP010 +}) -// warning.create(warnName, 'PINODEP010', 'A new deprecation') +const PINODEP010 = warning.createDeprecation({ code: 'PINODEP010', message: 'Unconditional execution of standard serializers for HTTP Request and Response will be discontinued in the next major version.' }) + +module.exports = { + warning: { + PINODEP010 + }, + future +} diff --git a/lib/proto.js b/lib/proto.js index 3f1fa6541..5f9237992 100644 --- a/lib/proto.js +++ b/lib/proto.js @@ -29,7 +29,8 @@ const { stringifySym, formatOptsSym, stringifiersSym, - msgPrefixSym + msgPrefixSym, + futureSym } = require('./symbols') const { getLevel, @@ -90,6 +91,12 @@ function child (bindings, options) { const formatters = this[formattersSym] const instance = Object.create(this) + if (options.hasOwnProperty('future') === true) { + throw RangeError('future can only be set in top-level Pino') + } + + instance[futureSym] = this[futureSym] // assigning future from parent is safe, as future it is immutable + if (options.hasOwnProperty('serializers') === true) { instance[serializersSym] = Object.create(null) diff --git a/lib/symbols.js b/lib/symbols.js index 9077e5899..0c8dbf665 100644 --- a/lib/symbols.js +++ b/lib/symbols.js @@ -31,6 +31,7 @@ const mixinMergeStrategySym = Symbol('pino.mixinMergeStrategy') const msgPrefixSym = Symbol('pino.msgPrefix') const responseKeySym = Symbol('pino.responseKey') const requestKeySym = Symbol('pino.requestKey') +const futureSym = Symbol('pino.future') const wildcardFirstSym = Symbol('pino.wildcardFirst') @@ -74,5 +75,6 @@ module.exports = { hooksSym, nestedKeyStrSym, mixinMergeStrategySym, - msgPrefixSym + msgPrefixSym, + futureSym } diff --git a/pino.d.ts b/pino.d.ts index b8378bcf0..55740e9b6 100644 --- a/pino.d.ts +++ b/pino.d.ts @@ -31,7 +31,7 @@ type TimeFn = () => string; type MixinFn = (mergeObject: object, level: number, logger:pino.Logger) => object; type MixinMergeStrategyFn = (mergeObject: object, mixinObject: object) => object; -type CustomLevelLogger = { +type CustomLevelLogger = { /** * Define additional logging levels. */ @@ -327,6 +327,9 @@ declare namespace pino { (msg: string, ...args: any[]): void; } + /** Future flags for Pino major-version 9. */ + type FutureFlags = 'skipUnconditionalStdSerializers' + interface LoggerOptions { transport?: TransportSingleOptions | TransportMultiOptions | TransportPipelineOptions /** @@ -670,6 +673,18 @@ declare namespace pino { * logs newline delimited JSON with `\r\n` instead of `\n`. Default: `false`. */ crlf?: boolean; + + /** + * The `future` object contains _opt-in_ flags specific to a Pino major version. These flags are used to change behavior, + * anticipating breaking-changes that will be introduced in the next major version. + * @example + * const parent = require('pino')({ + * future: { + * skipUnconditionalStdSerializers: true + * } + * }) + */ + future?: Partial> } interface ChildLoggerOptions { @@ -765,6 +780,7 @@ declare namespace pino { readonly useOnlyCustomLevelsSym: unique symbol; readonly formattersSym: unique symbol; readonly hooksSym: unique symbol; + readonly futureSym: unique symbol; }; /** diff --git a/pino.js b/pino.js index a4391c937..c3ded444b 100644 --- a/pino.js +++ b/pino.js @@ -20,6 +20,8 @@ const { noop } = require('./lib/tools') const { version } = require('./lib/meta') +const { future: defaultFuture } = require('./lib/deprecations') + const { chindingsSym, redactFmtSym, @@ -45,7 +47,8 @@ const { hooksSym, nestedKeyStrSym, mixinMergeStrategySym, - msgPrefixSym + msgPrefixSym, + futureSym } = symbols const { epochTime, nullTime } = time const { pid } = process @@ -86,7 +89,8 @@ const defaultOptions = { customLevels: null, useOnlyCustomLevels: false, depthLimit: 5, - edgeLimit: 100 + edgeLimit: 100, + future: Object.assign(Object.create(null), defaultFuture) } const normalize = createArgsNormalizer(defaultOptions) @@ -122,9 +126,12 @@ function pino (...args) { depthLimit, edgeLimit, onChild, - msgPrefix + msgPrefix, + future } = opts + const futureSafe = Object.assign(Object.create(null), defaultFuture, future) + const stringifySafe = configure({ maximumDepth: depthLimit, maximumBreadth: edgeLimit @@ -208,7 +215,8 @@ function pino (...args) { [hooksSym]: hooks, silent: noop, onChild, - [msgPrefixSym]: msgPrefix + [msgPrefixSym]: msgPrefix, + [futureSym]: Object.freeze(futureSafe) // future is set immutable to each Pino top-instance, as it affects behavior of other settings }) Object.setPrototypeOf(instance, proto()) diff --git a/test/deprecations.test.js b/test/deprecations.test.js new file mode 100644 index 000000000..c7299b6f3 --- /dev/null +++ b/test/deprecations.test.js @@ -0,0 +1,46 @@ +'use strict' +const { test } = require('tap') +const { future } = require('../lib/deprecations') +const { futureSym } = require('../lib/symbols') +const pino = require('../') + +test('instance future is copied from default future', async ({ same, not }) => { + const instance = pino() + + not(instance[futureSym], future) + same(instance[futureSym], future) +}) + +test('instance future entries may be individually overridden by opts', async ({ match }) => { + const opts = { future: { skipUnconditionalStdSerializers: true } } + const instance = pino(opts) + + match(instance[futureSym], { skipUnconditionalStdSerializers: true }) +}) + +test('instance future entries are kept, when not individually overridden in opts', async ({ match }) => { + const instance = pino({ future: { foo: '-foo-' } }) + + match(instance[futureSym], future) // this is true because opts.future does not override any default property + match(instance[futureSym], { foo: '-foo-' }) +}) + +test('instance future entries are immutable', async ({ throws }) => { + const instance = pino({ future: { foo: '-foo-' } }) + + throws(() => { instance[futureSym].foo = '-FOO-' }, TypeError) +}) + +test('child instance does not accept opts future', async ({ throws }) => { + const parent = pino({ future: { foo: '-foo-' } }) + + throws(() => parent.child({}, { future: { foo: '-FOO-' } }), RangeError) +}) + +test('child inherits future from parent and it is immutable', async ({ equal, throws }) => { + const parent = pino({ future: { foo: '-foo-' } }) + const child = parent.child({}) + + equal(child[futureSym], parent[futureSym]) + throws(() => { child[futureSym].foo = '-FOO-' }, TypeError) +}) From da9a16f7a02e5c1ef30efe3c89cb476008b6383b Mon Sep 17 00:00:00 2001 From: Gerardo Lima Date: Mon, 9 Dec 2024 16:51:41 +0000 Subject: [PATCH 8/8] feat(serializers): wrap breaking changes in `future` --- lib/proto.js | 9 +- lib/symbols.js | 6 +- lib/tools.js | 21 ++++- pino.js | 31 +++---- test/http.test.js | 191 +++++++++++++++++++++++++++------------ test/serializers.test.js | 156 ++++++++++++++++++++++---------- 6 files changed, 283 insertions(+), 131 deletions(-) diff --git a/lib/proto.js b/lib/proto.js index 5f9237992..239aef52d 100644 --- a/lib/proto.js +++ b/lib/proto.js @@ -195,6 +195,7 @@ function write (_obj, msg, num) { const responseKey = this[responseKeySym] const messageKey = this[messageKeySym] const mixinMergeStrategy = this[mixinMergeStrategySym] || defaultMixinMergeStrategy + const future = this[futureSym] let obj if (_obj === undefined || _obj === null) { @@ -205,9 +206,13 @@ function write (_obj, msg, num) { msg = _obj.message } } else if (_obj.method && _obj.headers && _obj.socket) { - obj = { [requestKey]: _obj } + if (future.skipUnconditionalStdSerializers) { + obj = { [requestKey]: _obj } + } } else if (typeof _obj.setHeader === 'function') { - obj = { [responseKey]: _obj } + if (future.skipUnconditionalStdSerializers) { + obj = { [responseKey]: _obj } + } } else { obj = _obj if (msg === undefined && _obj[messageKey] === undefined && _obj[errorKey]) { diff --git a/lib/symbols.js b/lib/symbols.js index 0c8dbf665..d41b926ff 100644 --- a/lib/symbols.js +++ b/lib/symbols.js @@ -25,12 +25,12 @@ const endSym = Symbol('pino.end') const formatOptsSym = Symbol('pino.formatOpts') const messageKeySym = Symbol('pino.messageKey') const errorKeySym = Symbol('pino.errorKey') +const requestKeySym = Symbol('pino.requestKey') +const responseKeySym = Symbol('pino.responseKey') const nestedKeySym = Symbol('pino.nestedKey') const nestedKeyStrSym = Symbol('pino.nestedKeyStr') const mixinMergeStrategySym = Symbol('pino.mixinMergeStrategy') const msgPrefixSym = Symbol('pino.msgPrefix') -const responseKeySym = Symbol('pino.responseKey') -const requestKeySym = Symbol('pino.requestKey') const futureSym = Symbol('pino.future') const wildcardFirstSym = Symbol('pino.wildcardFirst') @@ -65,8 +65,8 @@ module.exports = { formatOptsSym, messageKeySym, errorKeySym, - responseKeySym, requestKeySym, + responseKeySym, nestedKeySym, wildcardFirstSym, needsMetadataGsym, diff --git a/lib/tools.js b/lib/tools.js index 74746677a..ce84e2415 100644 --- a/lib/tools.js +++ b/lib/tools.js @@ -3,6 +3,7 @@ /* eslint no-prototype-builtins: 0 */ const format = require('quick-format-unescaped') +const { mapHttpRequest, mapHttpResponse } = require('pino-std-serializers') const SonicBoom = require('sonic-boom') const onExit = require('on-exit-leak-free') const { @@ -23,10 +24,12 @@ const { requestKeySym, responseKeySym, nestedKeyStrSym, - msgPrefixSym + msgPrefixSym, + futureSym } = require('./symbols') const { isMainThread } = require('worker_threads') const transport = require('./transport') +const { warning } = require('./deprecations') function noop () { } @@ -41,6 +44,16 @@ function genLog (level, hook) { function LOG (o, ...n) { if (typeof o === 'object') { let msg = o + if (!this[futureSym].skipUnconditionalStdSerializers) { + warning.PINODEP010() + if (o !== null) { + if (o.method && o.headers && o.socket) { + o = mapHttpRequest(o) + } else if (typeof o.setHeader === 'function') { + o = mapHttpResponse(o) + } + } + } let formatParams if (msg === null && n.length === 0) { formatParams = [null] @@ -128,9 +141,9 @@ function asJson (obj, msg, num, time) { value = serializers[key](value) } else if (key === errorKey && serializers.err) { value = serializers.err(value) - } else if (key === requestKey && serializers.req) { + } else if (key === requestKey && serializers.req && this[futureSym].skipUnconditionalStdSerializers) { value = serializers.req(value) - } else if (key === responseKey && serializers.res) { + } else if (key === responseKey && serializers.res && this[futureSym].skipUnconditionalStdSerializers) { value = serializers.res(value) } @@ -316,7 +329,7 @@ function createArgsNormalizer (defaultOptions) { stream = transport({ caller, ...opts.transport, levels: customLevels }) } opts = Object.assign({}, defaultOptions, opts) - opts.serializers = Object.assign({}, defaultOptions.serializers, opts.serializers) + opts.formatters = Object.assign({}, defaultOptions.formatters, opts.formatters) if (opts.prettyPrint) { diff --git a/pino.js b/pino.js index c3ded444b..3f6c49604 100644 --- a/pino.js +++ b/pino.js @@ -53,9 +53,7 @@ const { const { epochTime, nullTime } = time const { pid } = process const hostname = os.hostname() -const defaultErrorSerializer = stdSerializers.err -const defaultRequestSerializer = stdSerializers.req -const defaultResponseSerializer = stdSerializers.res + const defaultOptions = { level: 'info', levelComparison: SORTING_ORDER.ASC, @@ -67,11 +65,6 @@ const defaultOptions = { nestedKey: null, enabled: true, base: { pid, hostname }, - serializers: Object.assign(Object.create(null), { - err: defaultErrorSerializer, - req: defaultRequestSerializer, - res: defaultResponseSerializer - }), formatters: Object.assign(Object.create(null), { bindings (bindings) { return bindings @@ -95,8 +88,6 @@ const defaultOptions = { const normalize = createArgsNormalizer(defaultOptions) -const serializers = Object.assign(Object.create(null), stdSerializers) - function pino (...args) { const instance = {} const { opts, stream } = normalize(instance, caller(), ...args) @@ -110,8 +101,8 @@ function pino (...args) { timestamp, messageKey, errorKey, - requestKey, responseKey, + requestKey, nestedKey, base, name, @@ -130,7 +121,13 @@ function pino (...args) { future } = opts - const futureSafe = Object.assign(Object.create(null), defaultFuture, future) + // future is made immutable, as it impacts other settings + const futureSafe = Object.freeze(Object.assign(Object.create(null), defaultFuture, future)) + + const defaultSerializers = futureSafe.skipUnconditionalStdSerializers + ? { err: stdSerializers.err, req: stdSerializers.req, res: stdSerializers.res } + : { err: stdSerializers.err } + const serializersSafe = Object.assign(Object.create(null), defaultSerializers, serializers) const stringifySafe = configure({ maximumDepth: depthLimit, @@ -153,7 +150,7 @@ function pino (...args) { const end = '}' + (crlf ? '\r\n' : '\n') const coreChindings = asChindings.bind(null, { [chindingsSym]: '', - [serializersSym]: serializers, + [serializersSym]: serializersSafe, [stringifiersSym]: stringifiers, [stringifySym]: stringify, [stringifySafeSym]: stringifySafe, @@ -202,12 +199,12 @@ function pino (...args) { [formatOptsSym]: formatOpts, [messageKeySym]: messageKey, [errorKeySym]: errorKey, - [requestKeySym]: requestKey, [responseKeySym]: responseKey, + [requestKeySym]: requestKey, [nestedKeySym]: nestedKey, // protect against injection [nestedKeyStrSym]: nestedKey ? `,${JSON.stringify(nestedKey)}:{` : '', - [serializersSym]: serializers, + [serializersSym]: serializersSafe, [mixinSym]: mixin, [mixinMergeStrategySym]: mixinMergeStrategy, [chindingsSym]: chindings, @@ -216,7 +213,7 @@ function pino (...args) { silent: noop, onChild, [msgPrefixSym]: msgPrefix, - [futureSym]: Object.freeze(futureSafe) // future is set immutable to each Pino top-instance, as it affects behavior of other settings + [futureSym]: futureSafe }) Object.setPrototypeOf(instance, proto()) @@ -243,7 +240,7 @@ module.exports.transport = require('./lib/transport') module.exports.multistream = require('./lib/multistream') module.exports.levels = mappings() -module.exports.stdSerializers = serializers +module.exports.stdSerializers = Object.assign(Object.create(null), stdSerializers) module.exports.stdTimeFunctions = Object.assign({}, time) module.exports.symbols = symbols module.exports.version = version diff --git a/test/http.test.js b/test/http.test.js index d01cf86d3..2b342a1db 100644 --- a/test/http.test.js +++ b/test/http.test.js @@ -83,41 +83,79 @@ test('http request support via serializer', async ({ ok, same, error, teardown } server.close() }) -test('http request support via serializer (avoids stdSerializers)', async ({ error, equal, match }) => { - let originalReq - const instance = pino({ - requestKey: 'myRequest', - serializers: { - req: (req) => { - equal(req, originalReq) - return req.arbitraryProperty +test('http request support via serializer (avoids stdSerializers)', async ({ test }) => { + test('current behavior in major-version 9', async ({ equal, not, error }) => { + let originalReq + const instance = pino({ + serializers: { + req: (req) => { + // original request object is already replaced by pino.stdSerializers.req + not(req, originalReq) + equal(req.arbitraryProperty, undefined) + return req + } } - } - }, sink((chunk, _enc) => { - match(chunk, { - pid, - hostname, - level: 30, - msg: 'my request', - myRequest: originalReq.arbitraryProperty + }, sink()) + + const server = http.createServer(function (req, res) { + originalReq = req + req.arbitraryProperty = Math.random() + + instance.info(req, 'my response') + res.end('hello') }) - })) - const server = http.createServer(function (req, res) { - originalReq = req - req.arbitraryProperty = Math.random() + server.unref() + server.listen() + const err = await once(server, 'listening') + error(err) - instance.info(req, 'my request') - res.end('hello') + const res = await once(http.get('http://localhost:' + server.address().port), 'response') + res.resume() + server.close() }) - server.unref() - server.listen() - const err = await once(server, 'listening') - error(err) - const res = await once(http.get('http://localhost:' + server.address().port), 'response') - res.resume() - server.close() + test('future behavior', async ({ error, equal, match }) => { + const resultOfSerialization = Math.random() + let originalReq + const instance = pino({ + requestKey: 'myRequest', + serializers: { + req: (req) => { + equal(req, originalReq) + equal(req.arbitraryProperty, originalReq.arbitraryProperty) + return resultOfSerialization + } + }, + future: { + skipUnconditionalStdSerializers: true + } + }, sink((chunk, _enc) => { + match(chunk, { + pid, + hostname, + level: 30, + msg: 'my request', + myRequest: resultOfSerialization + }) + })) + + const server = http.createServer(function (req, res) { + originalReq = req + req.arbitraryProperty = Math.random() + + instance.info(req, 'my request') + res.end('hello') + }) + server.unref() + server.listen() + const err = await once(server, 'listening') + error(err) + + const res = await once(http.get('http://localhost:' + server.address().port), 'response') + res.resume() + server.close() + }) }) // skipped because request connection is deprecated since v13, and request socket is always available @@ -237,42 +275,79 @@ test('http response support via a serializer', async ({ ok, same, error, teardow server.close() }) -test('http response support via serializer (avoids stdSerializers)', async ({ match, equal, error }) => { - let originalRes - const instance = pino({ - responseKey: 'myResponse', - serializers: { - res: (res) => { - equal(res, originalRes) - return res.arbitraryProperty +test('http response support via serializer (avoids stdSerializers)', async ({ test }) => { + test('current behavior in major-version 9', async ({ equal, not, error }) => { + let originalRes + const instance = pino({ + serializers: { + res: (res) => { + // original response object is already replaced by pino.stdSerializers.res + not(res, originalRes) + equal(res.arbitraryProperty, undefined) + return res + } } - } - }, sink((chunk, _enc) => { - match(chunk, { - pid, - hostname, - level: 30, - msg: 'my response', - myResponse: originalRes.arbitraryProperty + }, sink()) + + const server = http.createServer(function (_req, res) { + originalRes = res + res.arbitraryProperty = Math.random() + + instance.info(res, 'my response') + res.end('hello') }) - })) - const server = http.createServer(function (_req, res) { - originalRes = res - res.arbitraryProperty = Math.random() + server.unref() + server.listen() + const err = await once(server, 'listening') + error(err) - instance.info(res, 'my response') - res.end('hello') + const res = await once(http.get('http://localhost:' + server.address().port), 'response') + res.resume() + server.close() }) + test('future behavior', async ({ match, equal, error }) => { + const resultOfSerialization = Math.random() + let originalRes + const instance = pino({ + responseKey: 'myResponseKey', + serializers: { + res: (res) => { + equal(res, originalRes) + equal(res.arbitraryProperty, originalRes.arbitraryProperty) + return resultOfSerialization + } + }, + future: { + skipUnconditionalStdSerializers: true + } + }, sink((chunk, _enc) => { + match(chunk, { + pid, + hostname, + level: 30, + msg: 'my response', + myResponseKey: resultOfSerialization + }) + })) - server.unref() - server.listen() - const err = await once(server, 'listening') - error(err) + const server = http.createServer(function (_req, res) { + originalRes = res + res.arbitraryProperty = Math.random() - const res = await once(http.get('http://localhost:' + server.address().port), 'response') - res.resume() - server.close() + instance.info(res, 'my response') + res.end('hello') + }) + + server.unref() + server.listen() + const err = await once(server, 'listening') + error(err) + + const res = await once(http.get('http://localhost:' + server.address().port), 'response') + res.resume() + server.close() + }) }) test('http request support via serializer in a child', async ({ ok, same, error, teardown }) => { diff --git a/test/serializers.test.js b/test/serializers.test.js index 986fa4a08..a26aca07a 100644 --- a/test/serializers.test.js +++ b/test/serializers.test.js @@ -115,35 +115,63 @@ test('child does not overwrite parent serializers', async ({ equal }) => { equal((await o2).test, 'child') }) -test('Symbol.for(\'pino.serializers\')', async ({ equal, same, not }) => { - const stream = sink() - const expected = Object.assign({ - err: stdSerializers.err, - req: stdSerializers.req, - res: stdSerializers.res - }, parentSerializers) - const parent = pino({ serializers: parentSerializers }, stream) - const child = parent.child({ a: 'property' }) - - same(parent[Symbol.for('pino.serializers')], expected) - same(child[Symbol.for('pino.serializers')], expected) - equal(parent[Symbol.for('pino.serializers')], child[Symbol.for('pino.serializers')]) +test('Symbol.for(\'pino.serializers\')', async ({ test }) => { + test('current behavior in major-version 9', async ({ equal, same, not }) => { + const stream = sink() + const expected = Object.assign({ + err: stdSerializers.err + }, parentSerializers) + const parent = pino({ serializers: parentSerializers }, stream) + const child = parent.child({ a: 'property' }) + + same(parent[Symbol.for('pino.serializers')], expected) + same(child[Symbol.for('pino.serializers')], expected) + equal(parent[Symbol.for('pino.serializers')], child[Symbol.for('pino.serializers')]) + + const child2 = parent.child({}, { + serializers: { + a + } + }) - const child2 = parent.child({}, { - serializers: { - a + function a () { + return 'hello' } + + not(child2[Symbol.for('pino.serializers')], parentSerializers) + equal(child2[Symbol.for('pino.serializers')].a, a) + equal(child2[Symbol.for('pino.serializers')].test, parentSerializers.test) }) + test('future behavior', async ({ equal, same, not }) => { + const stream = sink() + const expected = Object.assign({ + err: stdSerializers.err, + req: stdSerializers.req, + res: stdSerializers.res + }, parentSerializers) + const future = { skipUnconditionalStdSerializers: true } + const parent = pino({ serializers: parentSerializers, future }, stream) + const child = parent.child({ a: 'property' }) + + same(parent[Symbol.for('pino.serializers')], expected) + same(child[Symbol.for('pino.serializers')], expected) + equal(parent[Symbol.for('pino.serializers')], child[Symbol.for('pino.serializers')]) + + const child2 = parent.child({}, { + serializers: { + a + } + }) - function a () { - return 'hello' - } + function a () { + return 'hello' + } - not(child2[Symbol.for('pino.serializers')], parentSerializers) - equal(child2[Symbol.for('pino.serializers')].a, a) - equal(child2[Symbol.for('pino.serializers')].test, parentSerializers.test) + not(child2[Symbol.for('pino.serializers')], parentSerializers) + equal(child2[Symbol.for('pino.serializers')].a, a) + equal(child2[Symbol.for('pino.serializers')].test, parentSerializers.test) + }) }) - test('children inherit parent serializers', async ({ equal }) => { const stream = sink() const parent = pino({ serializers: parentSerializers }, stream) @@ -155,37 +183,71 @@ test('children inherit parent serializers', async ({ equal }) => { }) test('children inherit parent Symbol serializers', async ({ equal, same, not }) => { - const stream = sink() - const symbolSerializers = { - [Symbol.for('b')]: b - } - const expected = Object.assign({ - err: stdSerializers.err, - req: stdSerializers.req, - res: stdSerializers.res - }, symbolSerializers) - const parent = pino({ serializers: symbolSerializers }, stream) + test('current behavior in major-version 9', async ({ equal, same, not }) => { + const stream = sink() + const symbolSerializers = { + [Symbol.for('b')]: b + } + const expected = Object.assign({ + err: stdSerializers.err + }, symbolSerializers) + const parent = pino({ serializers: symbolSerializers }, stream) - same(parent[Symbol.for('pino.serializers')], expected) + same(parent[Symbol.for('pino.serializers')], expected) - const child = parent.child({}, { - serializers: { - [Symbol.for('a')]: a, - a + const child = parent.child({}, { + serializers: { + [Symbol.for('a')]: a, + a + } + }) + + function a () { + return 'hello' } + + function b () { + return 'world' + } + + same(child[Symbol.for('pino.serializers')].a, a) + same(child[Symbol.for('pino.serializers')][Symbol.for('b')], b) + same(child[Symbol.for('pino.serializers')][Symbol.for('a')], a) }) + test('future behavior', async ({ equal, same, not }) => { + const stream = sink() + const symbolSerializers = { + [Symbol.for('b')]: b + } + const expected = Object.assign({ + err: stdSerializers.err, + req: stdSerializers.req, + res: stdSerializers.res + }, symbolSerializers) + const future = { skipUnconditionalStdSerializers: true } + const parent = pino({ serializers: symbolSerializers, future }, stream) + + same(parent[Symbol.for('pino.serializers')], expected) + + const child = parent.child({}, { + serializers: { + [Symbol.for('a')]: a, + a + } + }) - function a () { - return 'hello' - } + function a () { + return 'hello' + } - function b () { - return 'world' - } + function b () { + return 'world' + } - same(child[Symbol.for('pino.serializers')].a, a) - same(child[Symbol.for('pino.serializers')][Symbol.for('b')], b) - same(child[Symbol.for('pino.serializers')][Symbol.for('a')], a) + same(child[Symbol.for('pino.serializers')].a, a) + same(child[Symbol.for('pino.serializers')][Symbol.for('b')], b) + same(child[Symbol.for('pino.serializers')][Symbol.for('a')], a) + }) }) test('children serializers get called', async ({ equal }) => {