Repository: pinojs/pino (js)

Issue:
TypeError: Cannot read property 'labels' of undefined
```js
//test.js
const pino = require('pino')();

process.on('warning', (warning) => {
  pino.info(warning);
});
process.on('foo', () => {});
process.on('foo', () => {})
process.on('foo', () => {});
process.on('foo', () => {})
process.on('foo', () => {});
process.on('foo', () => {})
process.on('foo', () => {});
process.on('foo', () => {})
process.on('foo', () => {});
process.on('foo', () => {})
process.on('foo', () => {});
process.on('foo', () => {})
process.on('foo', () => {});
process.on('foo', () => {})
process.on('foo', () => {});
process.on('foo', () => {})
process.on('foo', () => {});
process.on('foo', () => {})
process.on('foo', () => {});
process.on('foo', () => {})

```


  return levels.labels[levelVal]
               ^

TypeError: Cannot read property 'labels' of undefined

Pull request description:
Wrap prototype in factory and guard against loss of scope. 
The scope of the Pino class instance is lost when circular refs are routed through `decirc` - and `levels` is undefined in the getter. 

This PR wraps the Pino prototype in a factory - which helps with scope, and adds a protection in the `getLevels` function, with a  simple test to keep coverage up. 

Fixes #833 

Patch:
diff --git a/lib/levels.js b/lib/levels.js
index 494368e..e553bff 100644
--- a/lib/levels.js
+++ b/lib/levels.js
@@ -112,7 +112,8 @@ function setLevel (level) {
 
 function getLevel (level) {
   const { levels, levelVal } = this
-  return levels.labels[levelVal]
+  // protection against potential loss of Pino scope from serializers (edge case with circular refs - https://github.com/pinojs/pino/issues/833)
+  return (levels && levels.labels) ? levels.labels[levelVal] : ''
 }
 
 function isLevelEnabled (logLevel) {
diff --git a/lib/proto.js b/lib/proto.js
index 23348e1..e08ea4a 100644
--- a/lib/proto.js
+++ b/lib/proto.js
@@ -64,7 +64,10 @@ const prototype = {
 
 Object.setPrototypeOf(prototype, EventEmitter.prototype)
 
-module.exports = prototype
+// exporting and consuming the prototype object using factory pattern fixes scoping issues with getters when serializing
+module.exports = function () {
+  return Object.create(prototype)
+}
 
 const resetChildingsFormatter = bindings => bindings
 function child (bindings) {
diff --git a/pino.js b/pino.js
index 2c5a5f1..5052109 100644
--- a/pino.js
+++ b/pino.js
@@ -169,7 +169,8 @@ function pino (...args) {
     [hooksSym]: hooks,
     silent: noop
   })
-  Object.setPrototypeOf(instance, proto)
+
+  Object.setPrototypeOf(instance, proto())
 
   genLsCache(instance)
 


Test patch:
diff --git a/test/levels.test.js b/test/levels.test.js
index a71cfd3..ed86c83 100644
--- a/test/levels.test.js
+++ b/test/levels.test.js
@@ -4,6 +4,8 @@ const { test } = require('tap')
 const { sink, once, check } = require('./helper')
 const pino = require('../')
 
+const levelsLib = require('../lib/levels')
+
 // Silence all warnings for this test
 process.removeAllListeners('warning')
 process.on('warning', () => {})
@@ -550,3 +552,10 @@ test('changing level from info to silent and back to info in child logger', asyn
   result = await once(stream, 'data')
   check(is, result, expected.level, expected.msg)
 })
+
+// testing for potential loss of Pino constructor scope from serializers - an edge case with circular refs see:  https://github.com/pinojs/pino/issues/833
+test('trying to get levels when `this` is no longer a Pino instance returns an empty string', async ({ is }) => {
+  const notPinoInstance = { some: 'object', getLevel: levelsLib.getLevel }
+  const blankedLevelValue = notPinoInstance.getLevel()
+  is(blankedLevelValue, '')
+})