Repository: pinojs/pino (js)

Issue:
Call pino.destination as pino.sync and pino.extreme as pino.async
Operationally they provide two very different approaches and I think we would be best to not call `extreme()` as a drastic measure but rather `async()` because that reflect what it is without making judgements.

Pull request:
Avoid the term "extreme", there are just sync and async destinations.
Fixes #785 

Patch:
diff --git a/docs/api.md b/docs/api.md
index d4107e8..a5fe927 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -22,7 +22,6 @@
   * [logger.version](#version)
 * [Statics](#statics)
   * [pino.destination()](#pino-destination)
-  * [pino.extreme()](#pino-extreme)
   * [pino.final()](#pino-final)
   * [pino.stdSerializers](#pino-stdserializers)
   * [pino.stdTimeFunctions](#pino-stdtimefunctions)
@@ -284,7 +283,7 @@ Default: `pino.destination(1)` (STDOUT)
 The `destination` parameter, at a minimum must be an object with a `write` method.
 An ordinary Node.js `stream` can be passed as the destination (such as the result
 of `fs.createWriteStream`) but for peak log writing performance it is strongly
-recommended to use `pino.destination` or `pino.extreme` to create the destination stream.
+recommended to use `pino.destination` to create the destination stream.
 
 ```js
 // pino.destination(1) by default
@@ -308,7 +307,6 @@ However, there are some special instances where `pino.destination` is not used a
 In these cases `process.stdout` is used instead.
 
 * See [`pino.destination`](#pino-destination)
-* See [`pino.extreme`](#pino-extreme)
 
 <a id="metadata"></a>
 #### `destination[Symbol.for('pino.metadata')]`
@@ -572,19 +570,20 @@ console.log(anotherChild.bindings())
 <a id="flush"></a>
 ### `logger.flush()`
 
-Flushes the content of the buffer when using a `pino.extreme` destination.
+Flushes the content of the buffer when using `pino.destination({
+sync: false })`.
 
 This is an asynchronous, fire and forget, operation.
 
-The use case is primarily for Extreme mode logging, which may hold up to
-4KiB of logs. The `logger.flush` method can be used to flush the logs
+The use case is primarily for asynchronous logging, which may buffer
+log lines while others are being written. The `logger.flush` method can be
+used to flush the logs
 on an long interval, say ten seconds. Such a strategy can provide an
 optimium balance between extremely efficient logging at high demand periods
 and safer logging at low demand periods.
 
-* See [`pino.extreme`](#pino-extreme)
 * See [`destination` parameter](#destination)
-* See [Extreme mode ⇗](/docs/extreme.md)
+* See [Asynchronous Logging ⇗](/docs/asynchronous.md)
 
 <a id="level"></a>
 ### `logger.level` (String) [Getter/Setter]
@@ -707,7 +706,7 @@ Exposes the Pino package version. Also available on the exported `pino` function
 ## Statics
 
 <a id="pino-destination"></a>
-### `pino.destination([target]) => SonicBoom`
+### `pino.destination([opts]) => SonicBoom`
 
 Create a Pino Destination instance: a stream-like object with
 significantly more throughput (over 30%) than a standard Node.js stream.
@@ -716,6 +715,11 @@ significantly more throughput (over 30%) than a standard Node.js stream.
 const pino = require('pino')
 const logger = pino(pino.destination('./my-file'))
 const logger2 = pino(pino.destination())
+const logger3 = pino(pino.destination({
+  dest: './my-file',
+  minLength: 4096, // Buffer before writing
+  sync: false // Asynchronous logging
+}))
 ```
 
 The `pino.destination` method may be passed a file path or a numerical file descriptor.
@@ -729,35 +733,7 @@ A `pino.destination` instance can also be used to reopen closed files
 * See [`destination` parameter](#destination)
 * See [`sonic-boom` ⇗](https://github.com/mcollina/sonic-boom)
 * See [Reopening log files](/docs/help.md#reopening)
-
-<a id="pino-extreme"></a>
-### `pino.extreme([target]) => SonicBoom`
-
-Create an extreme mode destination. This yields an additional 60% performance boost.
-There are trade-offs that should be understood before usage.
-
-```js
-const pino = require('pino')
-const logger = pino(pino.extreme('./my-file'))
-const logger2 = pino(pino.extreme())
-```
-
-The `pino.extreme` method may be passed a file path or a numerical file descriptor.
-By default, `pino.extreme` will use `process.stdout.fd` (1) as the file descriptor.
-
-`pino.extreme` is implemented with the [`sonic-boom` ⇗](https://github.com/mcollina/sonic-boom)
-module.
-
-A `pino.extreme` instance can also be used to reopen closed files
-(for example, for some log rotation scenarios), see [Reopening log files](/docs/help.md#reopening).
-
-On AWS Lambda we recommend to call `extreme.flushSync()` at the end
-of each function execution to avoid losing data.
-
-* See [`destination` parameter](#destination)
-* See [`sonic-boom` ⇗](https://github.com/mcollina/sonic-boom)
-* See [Extreme mode ⇗](/docs/extreme.md)
-* See [Reopening log files](/docs/help.md#reopening)
+* See [Asynchronous Logging ⇗](/docs/asynchronous.md)
 
 <a id="pino-final"></a>
 
@@ -848,4 +824,4 @@ for general use.
 
 Exposes the Pino package version. Also available on the logger instance.
 
-* See [`logger.version`](#version)
\ No newline at end of file
+* See [`logger.version`](#version)
diff --git a/docs/extreme.md b/docs/asynchronous.md
similarity index 81%
rename from docs/extreme.md
rename to docs/asynchronous.md
index 791be39..a2adc4e 100644
--- a/docs/extreme.md
+++ b/docs/asynchronous.md
@@ -1,9 +1,10 @@
-# Extreme Mode
+# Asynchronous Logging
 
-In essence, extreme mode enables even faster performance by Pino.
+In essence, asynchronous logging enables even faster performance by Pino.
 
 In Pino's standard mode of operation log messages are directly written to the
-output stream as the messages are generated. Extreme mode works by buffering
+output stream as the messages are generated with a _blocking_ operation.
+Asynchronous logging works by buffering
 log messages and writing them in larger chunks.
 
 ## Caveats
@@ -41,15 +42,20 @@ logging and it is acceptable to potentially lose the most recent logs.
   In the case of `SIGHUP`, we will look to see if any other handlers are
   registered for the event. If not, we will proceed as we do with all other
   signals. If there are more handlers registered than just our own, we will
-  simply flush the extreme mode buffer.
+  simply flush the asynchronous logging buffer.
+
+### AWS Lambda
+
+On AWS Lambda we recommend to call `dest.flushSync()` at the end
+of each function execution to avoid losing data.
 
 ## Usage
 
-The `pino.extreme()` method will provide an Extreme Mode destination.
+The `pino.destination({ sync: false })` method will provide an asynchronous destination.
 
 ```js
 const pino = require('pino')
-const dest = pino.extreme() // logs to stdout with no args
+const dest = pino.destination({ sync: false }) // logs to stdout with no args
 const logger = pino(dest)
 ```
 
@@ -60,7 +66,7 @@ The following strategy can be used to minimize log loss:
 
 ```js
 const pino = require('pino')
-const dest = pino.extreme() // no arguments
+const dest = pino.destination({ sync: false })
 const logger = pino(dest)
 
 // asynchronously flush every 10 seconds to keep the buffer empty
@@ -90,6 +96,6 @@ An extreme destination is an instance of
 buffering.
 
 
-* See [`pino.extreme` api](/docs/api.md#pino-extreme)
+* See [`pino.destination` api](/docs/api.md#pino-destination)
 * See [`pino.final` api](/docs/api.md#pino-final)
 * See [`destination` parameter](/docs/api.md#destination)
diff --git a/lib/tools.js b/lib/tools.js
index b68559c..719b7a9 100644
--- a/lib/tools.js
+++ b/lib/tools.js
@@ -255,8 +255,8 @@ function hasBeenTampered (stream) {
   return stream.write !== stream.constructor.prototype.write
 }
 
-function buildSafeSonicBoom (dest, buffer = 0, sync = true) {
-  const stream = new SonicBoom(dest, buffer, sync)
+function buildSafeSonicBoom (opts) {
+  const stream = new SonicBoom(opts)
   stream.on('error', filterBrokenPipe)
   return stream
 
@@ -281,10 +281,10 @@ function createArgsNormalizer (defaultOptions) {
   return function normalizeArgs (instance, opts = {}, stream) {
     // support stream as a string
     if (typeof opts === 'string') {
-      stream = buildSafeSonicBoom(opts)
+      stream = buildSafeSonicBoom({ dest: opts })
       opts = {}
     } else if (typeof stream === 'string') {
-      stream = buildSafeSonicBoom(stream)
+      stream = buildSafeSonicBoom({ dest: stream })
     } else if (opts instanceof SonicBoom || opts.writable || opts._writableState) {
       stream = opts
       opts = null
@@ -308,7 +308,7 @@ function createArgsNormalizer (defaultOptions) {
     if (enabled === false) opts.level = 'silent'
     stream = stream || process.stdout
     if (stream === process.stdout && stream.fd >= 0 && !hasBeenTampered(stream)) {
-      stream = buildSafeSonicBoom(stream.fd)
+      stream = buildSafeSonicBoom({ fd: stream.fd })
     }
     if (prettyPrint) {
       const prettyOpts = Object.assign({ messageKey }, prettyPrint)
diff --git a/package.json b/package.json
index 0374a07..4886ed7 100644
--- a/package.json
+++ b/package.json
@@ -89,6 +89,6 @@
     "flatstr": "^1.0.12",
     "pino-std-serializers": "^2.4.2",
     "quick-format-unescaped": "^3.0.3",
-    "sonic-boom": "^0.7.7"
+    "sonic-boom": "^1.0.0"
   }
 }
diff --git a/pino.js b/pino.js
index d177c8c..4c9dff8 100644
--- a/pino.js
+++ b/pino.js
@@ -130,8 +130,21 @@ function pino (...args) {
   return instance
 }
 
-pino.extreme = (dest = process.stdout.fd) => buildSafeSonicBoom(dest, 4096, false)
-pino.destination = (dest = process.stdout.fd) => buildSafeSonicBoom(dest, 0, true)
+pino.extreme = (dest = process.stdout.fd) => {
+  process.emitWarning(
+    'The pino.extreme() option is deprecated and will be removed in v7. Use pino.destination({ sync: false }) instead.',
+    { code: 'extreme_deprecation' }
+  )
+  return buildSafeSonicBoom({ dest, minLength: 4096, sync: false })
+}
+pino.destination = (dest = process.stdout.fd) => {
+  if (typeof dest === 'object') {
+    dest.dest = dest.dest || process.stdout.fd
+    return buildSafeSonicBoom(dest)
+  } else {
+    return buildSafeSonicBoom({ dest, minLength: 0, sync: true })
+  }
+}
 
 pino.final = final
 pino.levels = mappings()


Test patch:
diff --git a/test/broken-pipe.test.js b/test/broken-pipe.test.js
index 0454cee..5bc56c2 100644
--- a/test/broken-pipe.test.js
+++ b/test/broken-pipe.test.js
@@ -23,7 +23,7 @@ t.jobs = 42
 
 test('basic.js')
 test('destination.js')
-test('extreme.js')
+test('syncfalse.js')
 
 t.test('let error pass through', ({ is, plan }) => {
   plan(3)
diff --git a/test/exit.test.js b/test/exit.test.js
index fe7542a..b4279f2 100644
--- a/test/exit.test.js
+++ b/test/exit.test.js
@@ -22,9 +22,9 @@ test('pino.destination log everything when calling process.exit(0)', async ({ is
   isNot(actual.match(/world/), null)
 })
 
-test('pino.extreme does not log everything when calling process.exit(0)', async ({ is }) => {
+test('sync false does not log everything when calling process.exit(0)', async ({ is }) => {
   var actual = ''
-  const child = execa(process.argv[0], [join(__dirname, 'fixtures', 'extreme-exit.js')])
+  const child = execa(process.argv[0], [join(__dirname, 'fixtures', 'syncfalse-exit.js')])
 
   child.stdout.pipe(writer((s, enc, cb) => {
     actual += s
@@ -37,9 +37,9 @@ test('pino.extreme does not log everything when calling process.exit(0)', async
   is(actual.match(/world/), null)
 })
 
-test('pino.extreme logs everything when calling flushSync', async ({ isNot }) => {
+test('sync false logs everything when calling flushSync', async ({ isNot }) => {
   var actual = ''
-  const child = execa(process.argv[0], [join(__dirname, 'fixtures', 'extreme-flush-exit.js')])
+  const child = execa(process.argv[0], [join(__dirname, 'fixtures', 'syncfalse-flush-exit.js')])
 
   child.stdout.pipe(writer((s, enc, cb) => {
     actual += s
diff --git a/test/final.test.js b/test/final.test.js
index b5d6d0d..8915dbd 100644
--- a/test/final.test.js
+++ b/test/final.test.js
@@ -24,26 +24,26 @@ test('throws if the supplied handler is not a function', async ({ throws }) => {
   }, Error('if supplied, the handler parameter should be a function'))
 })
 
-test('throws if not supplied logger with pino.extreme instance', async ({ throws, doesNotThrow }) => {
+test('throws if not supplied logger with pino.destination instance with sync false', async ({ throws, doesNotThrow }) => {
   throws(() => {
     pino.final(pino(fs.createWriteStream(getPathToNull())), () => {})
-  }, Error('final requires a stream that has a flushSync method, such as pino.destination and pino.extreme'))
+  }, Error('final requires a stream that has a flushSync method, such as pino.destination'))
 
   doesNotThrow(() => {
-    pino.final(pino(pino.extreme()), () => {})
+    pino.final(pino(pino.destination({ sync: false })), () => {})
   })
 
   doesNotThrow(() => {
-    pino.final(pino(pino.extreme()), () => {})
+    pino.final(pino(pino.destination({ sync: false })), () => {})
   })
 })
 
 test('returns an exit listener function', async ({ is }) => {
-  is(typeof pino.final(pino(pino.extreme()), () => {}), 'function')
+  is(typeof pino.final(pino(pino.destination({ sync: false })), () => {}), 'function')
 })
 
-test('listener function immediately sync flushes when fired', async ({ pass, fail }) => {
-  const dest = pino.extreme(getPathToNull())
+test('listener function immediately sync flushes when fired (sync false)', async ({ pass, fail }) => {
+  const dest = pino.destination({ dest: getPathToNull(), sync: false })
   var passed = false
   dest.flushSync = () => {
     passed = true
@@ -54,8 +54,8 @@ test('listener function immediately sync flushes when fired', async ({ pass, fai
   if (passed === false) fail('flushSync not called')
 })
 
-test('listener function immediately sync flushes when fired (pino.destination)', async ({ pass, fail }) => {
-  const dest = pino.destination(getPathToNull())
+test('listener function immediately sync flushes when fired (sync true)', async ({ pass, fail }) => {
+  const dest = pino.destination({ dest: getPathToNull(), sync: true })
   var passed = false
   dest.flushSync = () => {
     passed = true
@@ -67,14 +67,14 @@ test('listener function immediately sync flushes when fired (pino.destination)',
 })
 
 test('swallows the non-ready error', async ({ doesNotThrow }) => {
-  const dest = pino.extreme(getPathToNull())
+  const dest = pino.destination({ dest: getPathToNull(), sync: false })
   doesNotThrow(() => {
     pino.final(pino(dest), () => {})()
   })
 })
 
 test('listener function triggers handler function parameter', async ({ pass, fail }) => {
-  const dest = pino.extreme(getPathToNull())
+  const dest = pino.destination({ dest: getPathToNull(), sync: false })
   var passed = false
   pino.final(pino(dest), () => {
     passed = true
@@ -85,14 +85,14 @@ test('listener function triggers handler function parameter', async ({ pass, fai
 })
 
 test('passes any error to the handler', async ({ is }) => {
-  const dest = pino.extreme(getPathToNull())
+  const dest = pino.destination({ dest: getPathToNull(), sync: false })
   pino.final(pino(dest), (err) => {
     is(err.message, 'test')
   })(Error('test'))
 })
 
 test('passes a specialized final logger instance', async ({ is, isNot, error }) => {
-  const dest = pino.extreme(getPathToNull())
+  const dest = pino.destination({ dest: getPathToNull(), sync: false })
   const logger = pino(dest)
   pino.final(logger, (err, finalLogger) => {
     error(err)
@@ -116,7 +116,7 @@ test('passes a specialized final logger instance', async ({ is, isNot, error })
 })
 
 test('returns a specialized final logger instance if no handler is passed', async ({ is, isNot }) => {
-  const dest = pino.extreme(getPathToNull())
+  const dest = pino.destination({ dest: getPathToNull(), sync: false })
   const logger = pino(dest)
   const finalLogger = pino.final(logger)
   is(typeof finalLogger.trace, 'function')
@@ -138,7 +138,7 @@ test('returns a specialized final logger instance if no handler is passed', asyn
 })
 
 test('final logger instances synchronously flush after a log method call', async ({ pass, fail, error }) => {
-  const dest = pino.extreme(getPathToNull())
+  const dest = pino.destination({ dest: getPathToNull(), sync: false })
   const logger = pino(dest)
   var passed = false
   var count = 0
@@ -158,7 +158,7 @@ test('final logger instances synchronously flush after a log method call', async
 })
 
 test('also instruments custom log methods', async ({ pass, fail, error }) => {
-  const dest = pino.extreme(getPathToNull())
+  const dest = pino.destination({ dest: getPathToNull(), sync: false })
   const logger = pino({
     customLevels: {
       foo: 35
diff --git a/test/fixtures/broken-pipe/extreme.js b/test/fixtures/broken-pipe/syncfalse.js
similarity index 83%
rename from test/fixtures/broken-pipe/extreme.js
rename to test/fixtures/broken-pipe/syncfalse.js
index 27a624b..75cbff4 100644
--- a/test/fixtures/broken-pipe/extreme.js
+++ b/test/fixtures/broken-pipe/syncfalse.js
@@ -5,7 +5,7 @@ Date.now = function () { return 1459875739796 }
 require('os').hostname = function () { return 'abcdefghijklmnopqr' }
 
 const pino = require('../../..')
-const logger = pino(pino.extreme())
+const logger = pino(pino.destination({ sync: false }))
 
 for (let i = 0; i < 1000; i++) {
   logger.info('hello world')
diff --git a/test/fixtures/extreme-child.js b/test/fixtures/syncfalse-child.js
similarity index 77%
rename from test/fixtures/extreme-child.js
rename to test/fixtures/syncfalse-child.js
index 81528ab..8d9b8ce 100644
--- a/test/fixtures/extreme-child.js
+++ b/test/fixtures/syncfalse-child.js
@@ -2,5 +2,5 @@ global.process = { __proto__: process, pid: 123456 }
 Date.now = function () { return 1459875739796 }
 require('os').hostname = function () { return 'abcdefghijklmnopqr' }
 var pino = require(require.resolve('./../../'))
-var extreme = pino(pino.extreme()).child({ hello: 'world' })
+var extreme = pino(pino.destination({ sync: false })).child({ hello: 'world' })
 pino.final(extreme, (_, logger) => logger.info('h'))()
diff --git a/test/fixtures/extreme-exit.js b/test/fixtures/syncfalse-exit.js
similarity index 81%
rename from test/fixtures/extreme-exit.js
rename to test/fixtures/syncfalse-exit.js
index a90cc67..7b7867b 100644
--- a/test/fixtures/extreme-exit.js
+++ b/test/fixtures/syncfalse-exit.js
@@ -2,7 +2,7 @@ global.process = { __proto__: process, pid: 123456 }
 Date.now = function () { return 1459875739796 }
 require('os').hostname = function () { return 'abcdefghijklmnopqr' }
 var pino = require(require.resolve('./../../'))
-var dest = pino.extreme(1)
+var dest = pino.destination({ dest: 1, minLength: 4096, sync: false })
 var logger = pino({}, dest)
 logger.info('hello')
 logger.info('world')
diff --git a/test/fixtures/extreme-flush-exit.js b/test/fixtures/syncfalse-flush-exit.js
similarity index 81%
rename from test/fixtures/extreme-flush-exit.js
rename to test/fixtures/syncfalse-flush-exit.js
index 3abf7e0..e0d931d 100644
--- a/test/fixtures/extreme-flush-exit.js
+++ b/test/fixtures/syncfalse-flush-exit.js
@@ -2,7 +2,7 @@ global.process = { __proto__: process, pid: 123456 }
 Date.now = function () { return 1459875739796 }
 require('os').hostname = function () { return 'abcdefghijklmnopqr' }
 var pino = require(require.resolve('./../../'))
-var dest = pino.extreme(1)
+var dest = pino.destination({ dest: 1, minLength: 4096, sync: false })
 var logger = pino({}, dest)
 logger.info('hello')
 logger.info('world')
diff --git a/test/fixtures/extreme.js b/test/fixtures/syncfalse.js
similarity index 79%
rename from test/fixtures/extreme.js
rename to test/fixtures/syncfalse.js
index 20b2a6c..119ca6f 100644
--- a/test/fixtures/extreme.js
+++ b/test/fixtures/syncfalse.js
@@ -2,5 +2,5 @@ global.process = { __proto__: process, pid: 123456 }
 Date.now = function () { return 1459875739796 }
 require('os').hostname = function () { return 'abcdefghijklmnopqr' }
 var pino = require(require.resolve('./../../'))
-var extreme = pino(pino.extreme())
+var extreme = pino(pino.destination({ minLength: 4096, sync: false }))
 pino.final(extreme, (_, logger) => logger.info('h'))()
diff --git a/test/extreme.test.js b/test/syncfalse.test.js
similarity index 73%
rename from test/extreme.test.js
rename to test/syncfalse.test.js
index 7145d45..f56560b 100644
--- a/test/extreme.test.js
+++ b/test/syncfalse.test.js
@@ -42,7 +42,7 @@ test('extreme mode', async ({ is, teardown }) => {
   var expected2 = expected.split('\n')[0]
   var actual2 = ''
 
-  const child = fork(join(__dirname, '/fixtures/extreme.js'), { silent: true })
+  const child = fork(join(__dirname, '/fixtures/syncfalse.js'), { silent: true })
   child.stdout.pipe(writer((s, enc, cb) => {
     actual2 += s
     cb()
@@ -58,7 +58,7 @@ test('extreme mode', async ({ is, teardown }) => {
   })
 })
 
-test('extreme mode with child', async ({ is, teardown }) => {
+test('sync false with child', async ({ is, teardown }) => {
   const now = Date.now
   const hostname = os.hostname
   const proc = process
@@ -96,7 +96,7 @@ test('extreme mode with child', async ({ is, teardown }) => {
   var expected2 = expected.split('\n')[0]
   var actual2 = ''
 
-  const child = fork(join(__dirname, '/fixtures/extreme-child.js'), { silent: true })
+  const child = fork(join(__dirname, '/fixtures/syncfalse-child.js'), { silent: true })
   child.stdout.pipe(writer((s, enc, cb) => {
     actual2 += s
     cb()
@@ -119,7 +119,24 @@ test('throw an error if extreme is passed', async ({ throws }) => {
   })
 })
 
-test('flush does nothing without extreme mode', async () => {
+test('flush does nothing with sync true (default)', async () => {
   var instance = require('..')()
   instance.flush()
 })
+
+test('pino.extreme() emits a warning', async ({ is }) => {
+  const pino = require('..')
+  process.removeAllListeners('warning')
+  process.nextTick(() => pino.extreme(0))
+  const warning = await once(process, 'warning')
+  const expected = 'The pino.extreme() option is deprecated and will be removed in v7. Use pino.destination({ sync: false }) instead.'
+  is(expected, warning.message)
+  is('extreme_deprecation', warning.code)
+})
+
+test('pino.extreme() defaults to stdout', async ({ is }) => {
+  const pino = require('..')
+  process.removeAllListeners('warning')
+  const dest = pino.extreme()
+  is(dest.fd, process.stdout.fd)
+})