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) +})