Repository: pinojs/pino
Pull request #496 (2018-08-21 10:56:42)

Modify pino.final to handle calls without a handler
Modifies pino.final to be able to return the finalLogger directly. See #490


Patch:
diff --git a/docs/api.md b/docs/api.md
index e14e07c..7a379db 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -661,15 +661,12 @@ of each function execution to avoid losing data.
 * See [Reopening log files](/docs/help.md#reopening)
 
 <a id="pino-final"></a>
-### `pino.final(logger, handler) => Function`
 
-The `pino.final` method supplies an exit listener function that can be
-supplied to process exit events such as `exit`, `uncaughtException`,
-`SIGHUP` and so on.
+### `pino.final(logger, [handler]) => Function | FinalLogger`
+
+The `pino.final` method can be used to acquire a final logger instance 
+or create an exit listener function.
 
-The exit listener function will call the supplied `handler` function
-with an error object (or else `null`), a `finalLogger` instance followed
-by any additional arguments the `handler` may be called with.
 The `finalLogger` is a specialist logger that synchronously flushes
 on every write. This is important to gaurantee final log writes,
 both when using `pino.destination` or `pino.extreme` targets.
@@ -680,6 +677,16 @@ is a Node.js stream `pino.final` will throw. It's highly recommended
 for both performance and safety to use `pino.destination`/`pino.extreme`
 destinations.
 
+#### `pino.final(logger, handler) => Function`
+
+In this case the `pino.final` method supplies an exit listener function that can be
+supplied to process exit events such as `exit`, `uncaughtException`,
+`SIGHUP` and so on.
+
+The exit listener function will call the supplied `handler` function
+with an error object (or else `null`), a `finalLogger` instance followed
+by any additional arguments the `handler` may be called with.
+
 ```js
 process.on('uncaughtException', pino.final(logger, (err, finalLogger) => {
   finalLogger.error(err, 'uncaughtException')
@@ -687,6 +694,15 @@ process.on('uncaughtException', pino.final(logger, (err, finalLogger) => {
 }))
 ```
 
+#### `pino.final(logger) => FinalLogger`
+
+In this case the `pino.final` method returns a finalLogger instance.
+
+```js
+var finalLogger = pino.final(logger)
+finalLogger.info('exiting...')
+```
+
 * See [`destination` parameter](#destination)
 * See [Exit logging help](/docs/help.md#exit-logging)
 * See [Extreme mode ⇗](/docs/extreme.md)
diff --git a/lib/tools.js b/lib/tools.js
index 4f51e0e..e0776bd 100644
--- a/lib/tools.js
+++ b/lib/tools.js
@@ -275,14 +275,31 @@ function final (logger, handler) {
   if (typeof logger === 'undefined' || typeof logger.child !== 'function') {
     throw Error('expected a pino logger instance')
   }
-  if (typeof handler !== 'function') {
-    throw Error('expected a handler function')
+  const hasHandler = (typeof handler !== 'undefined')
+  if (hasHandler && typeof handler !== 'function') {
+    throw Error('if supplied, the handler parameter should be a function')
   }
   const stream = logger[streamSym]
   if (!(stream instanceof SonicBoom)) {
     throw Error('only compatible with loggers with pino.destination and pino.extreme targets')
   }
 
+  const finalLogger = new Proxy(logger, {
+    get: (logger, key) => {
+      if (key in logger.levels.values) {
+        return (...args) => {
+          logger[key](...args)
+          stream.flushSync()
+        }
+      }
+      return logger[key]
+    }
+  })
+
+  if (!hasHandler) {
+    return finalLogger
+  }
+
   return (err = null, ...args) => {
     try {
       stream.flushSync()
@@ -292,17 +309,6 @@ function final (logger, handler) {
       // in practice there shouldn't be a situation where it isn't
       // however, swallow the error just in case (and for easier testing)
     }
-    const finalLogger = new Proxy(logger, {
-      get: (logger, key) => {
-        if (key in logger.levels.values) {
-          return (...args) => {
-            logger[key](...args)
-            stream.flushSync()
-          }
-        }
-        return logger[key]
-      }
-    })
     return handler(err, finalLogger, ...args)
   }
 }


Test patch:
diff --git a/test/final.test.js b/test/final.test.js
index c1860aa..679fad7 100644
--- a/test/final.test.js
+++ b/test/final.test.js
@@ -18,10 +18,10 @@ test('throws if not supplied a logger instance', async ({throws}) => {
   }, Error('expected a pino logger instance'))
 })
 
-test('throws if not supplied a handler', async ({throws}) => {
+test('throws if the supplied handler is not a function', async ({throws}) => {
   throws(() => {
-    pino.final(pino())
-  }, Error('expected a handler function'))
+    pino.final(pino(), 'dummy')
+  }, Error('if supplied, the handler parameter should be a function'))
 })
 
 test('throws if not supplied logger with pino.destination or pino.extreme instance', async ({throws, doesNotThrow}) => {
@@ -84,7 +84,6 @@ test('passes a specialized final logger instance', async ({is, isNot, error}) =>
   const logger = pino(dest)
   pino.final(logger, (err, finalLogger) => {
     error(err)
-
     is(typeof finalLogger.trace, 'function')
     is(typeof finalLogger.debug, 'function')
     is(typeof finalLogger.info, 'function')
@@ -104,6 +103,28 @@ 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.destination('/dev/null')
+  const logger = pino(dest)
+  const finalLogger = pino.final(logger)
+  is(typeof finalLogger.trace, 'function')
+  is(typeof finalLogger.debug, 'function')
+  is(typeof finalLogger.info, 'function')
+  is(typeof finalLogger.warn, 'function')
+  is(typeof finalLogger.error, 'function')
+  is(typeof finalLogger.fatal, 'function')
+
+  isNot(finalLogger.trace, logger.trace)
+  isNot(finalLogger.debug, logger.debug)
+  isNot(finalLogger.info, logger.info)
+  isNot(finalLogger.warn, logger.warn)
+  isNot(finalLogger.error, logger.error)
+  isNot(finalLogger.fatal, logger.fatal)
+
+  is(finalLogger.child, logger.child)
+  is(finalLogger.levels, logger.levels)
+})
+
 test('final logger instances synchronously flush after a log method call', async ({pass, fail, error}) => {
   const dest = pino.destination('/dev/null')
   const logger = pino(dest)