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