Repository: pinojs/pino (js)

Issue:
Pino logger mutate (pollute) my input objects with logging information (immutability)
I'm using pino as logger. Everything was fine util i discovered that pino mutate my object injecting "logging" properties.

Here here a snippet

```
const logger = require('pino')()

let useful_object = {test: true}
logger.info(useful_object, "my logging information)

console.log(useful_object)

//{"level":30,"time":1554358981834,"pid":27753,"hostname":"mother","test":true,"msg":"my logging information","v":1}
//{ test: true, msg: 'my logging information' }
```

(check that useful_object now has the msg property)

There is a way to configure pino to use immutable objects instead append properties to them? Or i have to clone my objects?

```js
// create an object copy before logging
logger.info({...useful_object}, "my logging information")
```

*That object pollution is worst when i integrate pino in express.js with `express-pino-logger` where a lot of properties are attached to it*


Pull request:
Remove the msg property after logging
This problem was added in https://github.com/pinojs/pino/commit/e3866516b19667f6ba717eaf2a9fa21a1be24ea0.

Fixes #633.

cc @wdalmut 

Patch:
diff --git a/lib/tools.js b/lib/tools.js
index 55da42c..b27324c 100644
--- a/lib/tools.js
+++ b/lib/tools.js
@@ -71,7 +71,11 @@ function asJson (obj, msg, num, time) {
   const objError = obj instanceof Error
   const messageKey = this[messageKeySym]
   if (obj === undefined || obj === null) obj = {}
+  let oldMessage
   if (msg) {
+    if (obj.hasOwnProperty(messageKey)) {
+      oldMessage = obj[messageKey]
+    }
     obj[messageKey] = msg
   } else if (objError === true) {
     obj[messageKey] = obj.message
@@ -131,6 +135,15 @@ function asJson (obj, msg, num, time) {
     }
   }
 
+  if (oldMessage !== undefined) {
+    obj[messageKey] = oldMessage
+  } else {
+    // This delete is not going to cause problems,
+    // because the messageKey is the last property
+    // added. Turbofan <3
+    delete obj[messageKey]
+  }
+
   return data + end
 }
 


Test patch:
diff --git a/test/basic.test.js b/test/basic.test.js
index 7f06bd0..98b02bf 100644
--- a/test/basic.test.js
+++ b/test/basic.test.js
@@ -43,11 +43,12 @@ function levelTest (name, level) {
     check(is, await once(stream, 'data'), level, 'hello world')
   })
 
-  test(`passing objects at level ${name}`, async ({ is }) => {
+  test(`passing objects at level ${name}`, async ({ is, same }) => {
     const stream = sink()
     const instance = pino(stream)
     instance.level = name
-    instance[name]({ hello: 'world' })
+    const obj = { hello: 'world' }
+    instance[name](obj)
 
     const result = await once(stream, 'data')
     is(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()')
@@ -56,13 +57,15 @@ function levelTest (name, level) {
     is(result.level, level)
     is(result.hello, 'world')
     is(result.v, 1)
+    same(Object.keys(obj), [ 'hello' ])
   })
 
   test(`passing an object and a string at level ${name}`, async ({ is, same }) => {
     const stream = sink()
     const instance = pino(stream)
     instance.level = name
-    instance[name]({ hello: 'world' }, 'a string')
+    const obj = { hello: 'world' }
+    instance[name](obj, 'a string')
     const result = await once(stream, 'data')
     is(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()')
     delete result.time
@@ -74,6 +77,7 @@ function levelTest (name, level) {
       hello: 'world',
       v: 1
     })
+    same(Object.keys(obj), [ 'hello' ])
   })
 
   test(`overriding object key by string at level ${name}`, async ({ is, same }) => {
diff --git a/test/metadata.test.js b/test/metadata.test.js
index 729e733..48d138a 100644
--- a/test/metadata.test.js
+++ b/test/metadata.test.js
@@ -16,7 +16,7 @@ test('metadata works', async ({ ok, same, is }) => {
       is(30, this.lastLevel)
       is('a msg', this.lastMsg)
       ok(Number(this.lastTime) >= now)
-      same(this.lastObj, { hello: 'world', msg: 'a msg' })
+      same(this.lastObj, { hello: 'world' })
       const result = JSON.parse(chunk)
       ok(new Date(result.time) <= new Date(), 'time is greater than Date.now()')
       delete result.time
@@ -41,7 +41,7 @@ test('child loggers works', async ({ ok, same, is }) => {
       is(child, this.lastLogger)
       is(30, this.lastLevel)
       is('a msg', this.lastMsg)
-      same(this.lastObj, { from: 'child', msg: 'a msg' })
+      same(this.lastObj, { from: 'child' })
       const result = JSON.parse(chunk)
       ok(new Date(result.time) <= new Date(), 'time is greater than Date.now()')
       delete result.time