Repository: pinojs/pino
Pull request #781 (2020-02-29 01:00:04)

Merge `master` into `next`


Patch:
diff --git a/docs/api.md b/docs/api.md
index 0751017..d4107e8 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -252,14 +252,18 @@ Enables printing of level labels instead of level values in the printed logs.
 Warning: this option may not be supported by downstream transports.
 
 <a id="changeLevelName"></a>
-#### `changeLevelName` (String)
+#### `changeLevelName` (String) - DEPRECATED
+Use `levelKey` instead. This will be removed in v7.
+
+<a id="levelKey"></a>
+#### `levelKey` (String)
 
 Default: `'level'`
 
 Changes the property `level` to any string value you pass in:
 ```js
 const logger = pino({
-  changeLevelName: 'priority'
+  levelKey: 'priority'
 })
 logger.info('hello world')
 // {"priority":30,"time":1531257112193,"msg":"hello world","pid":55956,"hostname":"x"}
diff --git a/docs/pretty.md b/docs/pretty.md
index c9eca19..bd90ebb 100644
--- a/docs/pretty.md
+++ b/docs/pretty.md
@@ -23,8 +23,12 @@ The API requires modules provide a factory function which returns a prettifier
 function. This prettifier function must accept either a string of NDJSON or
 a Pino log object. A psuedo-example of such a prettifier is:
 
+The uninitialized Pino instance is passed as `this` into prettifier factory function,
+so it can be accessed via closure by the returned prettifier function.
+
 ```js
 module.exports = function myPrettifier (options) {
+  // `this` is bound to the pino instance
   // Deal with whatever options are supplied.
   return function prettifier (inputData) {
     let logObject
diff --git a/lib/levels.js b/lib/levels.js
index c4ebbdc..0f60f7b 100644
--- a/lib/levels.js
+++ b/lib/levels.js
@@ -4,7 +4,7 @@ const {
   lsCacheSym,
   levelValSym,
   useLevelLabelsSym,
-  changeLevelNameSym,
+  levelKeySym,
   useOnlyCustomLevelsSym,
   streamSym
 } = require('./symbols')
@@ -49,7 +49,7 @@ const initialLsCache = Object.keys(nums).reduce((o, k) => {
 }, {})
 
 function genLsCache (instance) {
-  const levelName = instance[changeLevelNameSym]
+  const levelName = instance[levelKeySym]
   instance[lsCacheSym] = Object.keys(instance.levels.labels).reduce((o, k) => {
     o[k] = instance[useLevelLabelsSym]
       ? `{"${levelName}":"${instance.levels.labels[k]}"`
diff --git a/lib/symbols.js b/lib/symbols.js
index 9e27d7c..13d3662 100644
--- a/lib/symbols.js
+++ b/lib/symbols.js
@@ -4,7 +4,7 @@ const setLevelSym = Symbol('pino.setLevel')
 const getLevelSym = Symbol('pino.getLevel')
 const levelValSym = Symbol('pino.levelVal')
 const useLevelLabelsSym = Symbol('pino.useLevelLabels')
-const changeLevelNameSym = Symbol('pino.changeLevelName')
+const levelKeySym = Symbol('pino.levelKey')
 const useOnlyCustomLevelsSym = Symbol('pino.useOnlyCustomLevels')
 const mixinSym = Symbol('pino.mixin')
 
@@ -57,7 +57,7 @@ module.exports = {
   messageKeySym,
   nestedKeySym,
   wildcardFirstSym,
-  changeLevelNameSym,
+  levelKeySym,
   wildcardGsym,
   needsMetadataGsym,
   useOnlyCustomLevelsSym
diff --git a/lib/tools.js b/lib/tools.js
index 16a209f..b68559c 100644
--- a/lib/tools.js
+++ b/lib/tools.js
@@ -157,8 +157,9 @@ function asChindings (instance, bindings) {
   return data
 }
 
-function getPrettyStream (opts, prettifier, dest) {
+function getPrettyStream (opts, prettifier, dest, instance) {
   if (prettifier && typeof prettifier === 'function') {
+    prettifier = prettifier.bind(instance)
     return prettifierMetaWrapper(prettifier(opts), dest)
   }
   try {
@@ -183,6 +184,7 @@ function prettifierMetaWrapper (pretty, dest) {
         return
       }
       warned = true
+      setMetadataProps(dest, this)
       dest.write(pretty(Object.assign({
         level: 40, // warn
         msg: 'pino.final with prettyPrint does not support flushing',
@@ -242,6 +244,8 @@ function prettifierMetaWrapper (pretty, dest) {
 
       const formatted = pretty(typeof redact === 'function' ? redact(obj) : obj)
       if (formatted === undefined) return
+
+      setMetadataProps(dest, this)
       dest.write(formatted)
     }
   }
@@ -274,7 +278,7 @@ function buildSafeSonicBoom (dest, buffer = 0, sync = true) {
 }
 
 function createArgsNormalizer (defaultOptions) {
-  return function normalizeArgs (opts = {}, stream) {
+  return function normalizeArgs (instance, opts = {}, stream) {
     // support stream as a string
     if (typeof opts === 'string') {
       stream = buildSafeSonicBoom(opts)
@@ -292,6 +296,14 @@ function createArgsNormalizer (defaultOptions) {
     if ('onTerminated' in opts) {
       throw Error('The onTerminated option has been removed, use pino.final instead')
     }
+    if ('changeLevelName' in opts) {
+      process.emitWarning(
+        'The changeLevelName option is deprecated and will be removed in v7. Use levelKey instead.',
+        { code: 'changeLevelName_deprecation' }
+      )
+      opts.levelKey = opts.changeLevelName
+      delete opts.changeLevelName
+    }
     const { enabled, prettyPrint, prettifier, messageKey } = opts
     if (enabled === false) opts.level = 'silent'
     stream = stream || process.stdout
@@ -300,7 +312,7 @@ function createArgsNormalizer (defaultOptions) {
     }
     if (prettyPrint) {
       const prettyOpts = Object.assign({ messageKey }, prettyPrint)
-      stream = getPrettyStream(prettyOpts, prettifier, stream)
+      stream = getPrettyStream(prettyOpts, prettifier, stream, instance)
     }
     return { opts, stream }
   }
@@ -356,6 +368,16 @@ function stringify (obj) {
   }
 }
 
+function setMetadataProps (dest, that) {
+  if (dest[needsMetadataGsym] === true) {
+    dest.lastLevel = that.lastLevel
+    dest.lastMsg = that.lastMsg
+    dest.lastObj = that.lastObj
+    dest.lastTime = that.lastTime
+    dest.lastLogger = that.lastLogger
+  }
+}
+
 module.exports = {
   noop,
   buildSafeSonicBoom,
diff --git a/package.json b/package.json
index 00e19cf..9d52a96 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "pino",
-  "version": "5.16.0",
+  "version": "5.17.0",
   "description": "super fast, all natural json logger",
   "main": "pino.js",
   "browser": "./browser.js",
diff --git a/pino.js b/pino.js
index 1c88342..d177c8c 100644
--- a/pino.js
+++ b/pino.js
@@ -29,7 +29,7 @@ const {
   messageKeySym,
   nestedKeySym,
   useLevelLabelsSym,
-  changeLevelNameSym,
+  levelKeySym,
   mixinSym,
   useOnlyCustomLevelsSym
 } = symbols
@@ -52,7 +52,7 @@ const defaultOptions = {
   name: undefined,
   redact: null,
   customLevels: null,
-  changeLevelName: 'level',
+  levelKey: 'level',
   useOnlyCustomLevels: false
 }
 
@@ -61,7 +61,8 @@ const normalize = createArgsNormalizer(defaultOptions)
 const serializers = Object.assign(Object.create(null), stdSerializers)
 
 function pino (...args) {
-  const { opts, stream } = normalize(...args)
+  const instance = {}
+  const { opts, stream } = normalize(instance, ...args)
   const {
     redact,
     crlf,
@@ -74,7 +75,7 @@ function pino (...args) {
     level,
     customLevels,
     useLevelLabels,
-    changeLevelName,
+    levelKey,
     mixin,
     useOnlyCustomLevels
   } = opts
@@ -102,10 +103,10 @@ function pino (...args) {
   assertDefaultLevelFound(level, customLevels, useOnlyCustomLevels)
   const levels = mappings(customLevels, useOnlyCustomLevels)
 
-  const instance = {
+  Object.assign(instance, {
     levels,
     [useLevelLabelsSym]: useLevelLabels,
-    [changeLevelNameSym]: changeLevelName,
+    [levelKeySym]: levelKey,
     [useOnlyCustomLevelsSym]: useOnlyCustomLevels,
     [streamSym]: stream,
     [timeSym]: time,
@@ -119,10 +120,10 @@ function pino (...args) {
     [serializersSym]: serializers,
     [mixinSym]: mixin,
     [chindingsSym]: chindings
-  }
+  })
   Object.setPrototypeOf(instance, proto)
 
-  if (customLevels || useLevelLabels || changeLevelName !== defaultOptions.changeLevelName) genLsCache(instance)
+  if (customLevels || useLevelLabels || levelKey !== defaultOptions.levelKey) genLsCache(instance)
 
   instance[setLevelSym](level)
 


Test patch:
diff --git a/test/custom-levels.test.js b/test/custom-levels.test.js
index 283504d..b7d19c3 100644
--- a/test/custom-levels.test.js
+++ b/test/custom-levels.test.js
@@ -251,17 +251,58 @@ test('does not share custom level state across siblings', async ({ doesNotThrow
   })
 })
 
-test('custom level does not affect changeLevelName', async ({ is }) => {
+test('custom level does not affect levelKey', async ({ is }) => {
   const stream = sink()
   const logger = pino({
     customLevels: {
       foo: 35,
       bar: 45
     },
-    changeLevelName: 'priority'
+    levelKey: 'priority'
   }, stream)
 
   logger.foo('test')
   const { priority } = await once(stream, 'data')
   is(priority, 35)
 })
+
+test('custom levels accesible in prettifier function', async ({ plan, same }) => {
+  plan(1)
+  const logger = pino({
+    prettyPrint: true,
+    prettifier: function prettifierFactory () {
+      const instance = this
+      return function () {
+        same(instance.levels, {
+          labels: {
+            10: 'trace',
+            20: 'debug',
+            30: 'info',
+            35: 'foo',
+            40: 'warn',
+            45: 'bar',
+            50: 'error',
+            60: 'fatal'
+          },
+          values: {
+            trace: 10,
+            debug: 20,
+            info: 30,
+            warn: 40,
+            error: 50,
+            fatal: 60,
+            foo: 35,
+            bar: 45
+          }
+        })
+      }
+    },
+    customLevels: {
+      foo: 35,
+      bar: 45
+    },
+    changeLevelName: 'priority'
+  })
+
+  logger.foo('test')
+})
diff --git a/test/levels.test.js b/test/levels.test.js
index 06fea3e..76b7324 100644
--- a/test/levels.test.js
+++ b/test/levels.test.js
@@ -257,12 +257,21 @@ test('resets levels from labels to numbers', async ({ is }) => {
   instance.info('hello world')
 })
 
+test('aliases changeLevelName to levelKey', async ({ is }) => {
+  const instance = pino({ changeLevelName: 'priority' }, sink((result, enc, cb) => {
+    is(result.priority, 30)
+    cb()
+  }))
+
+  instance.info('hello world')
+})
+
 test('changes label naming when told to', async ({ is }) => {
   const expected = [{
     priority: 30,
     msg: 'hello world'
   }]
-  const instance = pino({ changeLevelName: 'priority' }, sink((result, enc, cb) => {
+  const instance = pino({ levelKey: 'priority' }, sink((result, enc, cb) => {
     const current = expected.shift()
     is(result.priority, current.priority)
     is(result.msg, current.msg)
@@ -323,11 +332,11 @@ test('produces labels for custom levels', async ({ is }) => {
   instance.foo('foobar')
 })
 
-test('setting changeLevelName does not affect labels when told to', async ({ is }) => {
+test('setting levelKey does not affect labels when told to', async ({ is }) => {
   const instance = pino(
     {
       useLevelLabels: true,
-      changeLevelName: 'priority'
+      levelKey: 'priority'
     },
     sink((result, enc, cb) => {
       is(result.priority, 'info')
diff --git a/test/pretty.test.js b/test/pretty.test.js
index caf9f62..4017d0c 100644
--- a/test/pretty.test.js
+++ b/test/pretty.test.js
@@ -272,3 +272,41 @@ test('works as expected with an object with the msg prop', async ({ isNot }) =>
   await once(child, 'close')
   isNot(actual.match(/\(123456 on abcdefghijklmnopqr\): hello/), null)
 })
+
+test('should not lose stream metadata for streams with `needsMetadataGsym` flag', async ({ isNot }) => {
+  const dest = new Writable({
+    objectMode: true,
+    write () {
+      isNot(typeof this.lastLevel === 'undefined', true)
+      isNot(typeof this.lastMsg === 'undefined', true)
+      isNot(typeof this.lastObj === 'undefined', true)
+      isNot(typeof this.lastTime === 'undefined', true)
+      isNot(typeof this.lastLogger === 'undefined', true)
+    }
+  })
+
+  dest[pino.symbols.needsMetadataGsym] = true
+
+  const log = pino({
+    prettyPrint: true
+  }, dest)
+  log.info('foo')
+})
+
+test('should not add stream metadata for streams without `needsMetadataGsym` flag', async ({ is }) => {
+  const dest = new Writable({
+    objectMode: true,
+    write () {
+      is(typeof this.lastLevel === 'undefined', true)
+      is(typeof this.lastMsg === 'undefined', true)
+      is(typeof this.lastObj === 'undefined', true)
+      is(typeof this.lastTime === 'undefined', true)
+      is(typeof this.lastLogger === 'undefined', true)
+    }
+  })
+
+  const log = pino({
+    prettyPrint: true
+  }, dest)
+  log.info('foo')
+})