Repository: pinojs/pino
Pull request #1507 (2022-07-26 12:00:04)

feat: add errorKey option
We have custom `messageKey` and `serializers`, makes sense to have custom `errorKey` as well.
Not sure if that should affect `browser` part

mcollina: There seem to be a significant decrease of throughput in our benchmarks.

Current:

```
benchPino*10000: 148.753ms
```

this branch:

```
benchPino*10000: 166.715ms
```
mcollina: CI is failing too.
qwelias: Changes have minimal logic, so I don't think there's any room for perf improvement.
They don't affect any transport functionality either, I assume tests may be flaky, may need a rerun
jsumners: > @mcollina How did you run your benchmarks? 1st few runs are usually faster because CPU is cold. Make sure to do a few consecutive runs on each branch to avoid CPU throttling bias.
> 
> For me both versions run `bench-basic` with `Pino average` in 96-104 (master actually peaked at 107 once, so even slower, did about dozen runs each)

See https://github.com/pinojs/pino/runs/7528251758?check_suite_focus=true
qwelias: @jsumners 
I can see no significant difference:
- `benchmark branch` BASIC `benchPino*10000: 183.329ms`
- `benchmark current` BASIC `benchPino*10000: 188.812ms`

So idk where @mcollina took the numbers from
mcollina: we run the benchmarks for every CI run
mcollina: the first in this PR
qwelias: @mcollina 
I'm sorry, but I see `benchPino*10000: 188.812ms` [here's link](https://github.com/pinojs/pino/runs/7528251758?check_suite_focus=true#step:5:16).

Can you link yours? I literally have no idea where you see those numbers, search doesn't find them either
mcollina: current: https://github.com/pinojs/pino/runs/7528251758?check_suite_focus=true#step:5:24
this branch: https://github.com/pinojs/pino/runs/7528251630?check_suite_focus=true#step:5:24
qwelias: Thanks!
But why are they called the same!?
jsumners: @mcollina check the averages:

+ current: https://github.com/pinojs/pino/runs/7528251758?check_suite_focus=true#step:5:154
+ branch: https://github.com/pinojs/pino/runs/7528251630?check_suite_focus=true#step:5:154
mcollina: There is still a difference.
qwelias: What's average margin of error? Is the difference more than average?
Is it significant to block the feature?
mcollina: I tested on a dedicated workstation, and there is no perceptible slowdown. We can land.
mcollina: Don't worry about the flaky test.

Patch:
diff --git a/docs/api.md b/docs/api.md
index 06ab0a2..2cf0c6c 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -381,6 +381,7 @@ matching the exact key of a serializer will be serialized using the defined seri
 The serializers are applied when a property in the logged object matches a property
 in the serializers. The only exception is the `err` serializer as it is also applied in case
 the object is an instance of `Error`, e.g. `logger.info(new Error('kaboom'))`.
+See `errorKey` option to change `err` namespace.
 
 * See [pino.stdSerializers](#pino-stdserializers)
 
@@ -434,6 +435,13 @@ Default: `'msg'`
 
 The string key for the 'message' in the JSON object.
 
+<a id=opt-messagekey></a>
+#### `errorKey` (String)
+
+Default: `'err'`
+
+The string key for the 'error' in the JSON object.
+
 <a id=opt-nestedkey></a>
 #### `nestedKey` (String)
 
@@ -603,6 +611,9 @@ logger.info({MIX: {IN: true}})
 If the object is of type Error, it is wrapped in an object containing a property err (`{ err: mergingObject }`).
 This allows for a unified error handling flow.
 
+Options `serializers` and `errorKey` could be used at instantiation time to change the namespace
+from `err` to another string as preferred.
+
 <a id="message"></a>
 #### `message` (String)
 
@@ -625,7 +636,7 @@ See [Avoid Message Conflict](/docs/help.md#avoid-message-conflict) for informati
 on how to overcome this limitation.
 
 If no `message` parameter is provided, and the `mergingObject` is of type `Error` or it has a property named `err`, the
-`message` parameter is set to the `message` value of the error.
+`message` parameter is set to the `message` value of the error. See option `errorKey` if you want to change the namespace.
 
 The `messageKey` option can be used at instantiation time to change the namespace
 from `msg` to another string as preferred.
@@ -689,6 +700,9 @@ const logger = pino(pinoOptions)
 
 Errors can be supplied as either the first parameter or if already using `mergingObject` then as the `err` property on the `mergingObject`.
 
+Options `serializers` and `errorKey` could be used at instantiation time to change the namespace
+from `err` to another string as preferred.
+
 > ## Note
 > This section describes the default configuration. The error serializer can be
 > mapped to a different key using the [`serializers`](#opt-serializers) option.
diff --git a/lib/proto.js b/lib/proto.js
index abbeef8..25c0c5b 100644
--- a/lib/proto.js
+++ b/lib/proto.js
@@ -19,6 +19,7 @@ const {
   streamSym,
   serializersSym,
   formattersSym,
+  errorKeySym,
   useOnlyCustomLevelsSym,
   needsMetadataGsym,
   redactFmtSym,
@@ -174,20 +175,21 @@ function defaultMixinMergeStrategy (mergeObject, mixinObject) {
 function write (_obj, msg, num) {
   const t = this[timeSym]()
   const mixin = this[mixinSym]
+  const errorKey = this[errorKeySym]
   const mixinMergeStrategy = this[mixinMergeStrategySym] || defaultMixinMergeStrategy
   let obj
 
   if (_obj === undefined || _obj === null) {
     obj = {}
   } else if (_obj instanceof Error) {
-    obj = { err: _obj }
+    obj = { [errorKey]: _obj }
     if (msg === undefined) {
       msg = _obj.message
     }
   } else {
     obj = _obj
-    if (msg === undefined && _obj.err) {
-      msg = _obj.err.message
+    if (msg === undefined && _obj[errorKey]) {
+      msg = _obj[errorKey].message
     }
   }
 
diff --git a/lib/symbols.js b/lib/symbols.js
index 84ddadb..2a0228e 100644
--- a/lib/symbols.js
+++ b/lib/symbols.js
@@ -23,6 +23,7 @@ const stringifiersSym = Symbol('pino.stringifiers')
 const endSym = Symbol('pino.end')
 const formatOptsSym = Symbol('pino.formatOpts')
 const messageKeySym = Symbol('pino.messageKey')
+const errorKeySym = Symbol('pino.errorKey')
 const nestedKeySym = Symbol('pino.nestedKey')
 const nestedKeyStrSym = Symbol('pino.nestedKeyStr')
 const mixinMergeStrategySym = Symbol('pino.mixinMergeStrategy')
@@ -57,6 +58,7 @@ module.exports = {
   endSym,
   formatOptsSym,
   messageKeySym,
+  errorKeySym,
   nestedKeySym,
   wildcardFirstSym,
   needsMetadataGsym,
diff --git a/pino.d.ts b/pino.d.ts
index 221ebd5..a977a1c 100644
--- a/pino.d.ts
+++ b/pino.d.ts
@@ -375,6 +375,10 @@ declare namespace pino {
          * The string key for the 'message' in the JSON object. Default: "msg".
          */
         messageKey?: string;
+        /**
+         * The string key for the 'error' in the JSON object. Default: "err".
+         */
+        errorKey?: string;
         /**
          * The string key to place any logged object under.
          */
@@ -682,6 +686,7 @@ declare namespace pino {
         readonly endSym: unique symbol;
         readonly formatOptsSym: unique symbol;
         readonly messageKeySym: unique symbol;
+        readonly errorKeySym: unique symbol;
         readonly nestedKeySym: unique symbol;
         readonly wildcardFirstSym: unique symbol;
         readonly needsMetadataGsym: unique symbol;
diff --git a/pino.js b/pino.js
index 7dcb489..9ace3a7 100644
--- a/pino.js
+++ b/pino.js
@@ -33,6 +33,7 @@ const {
   endSym,
   formatOptsSym,
   messageKeySym,
+  errorKeySym,
   nestedKeySym,
   mixinSym,
   useOnlyCustomLevelsSym,
@@ -49,6 +50,7 @@ const defaultOptions = {
   level: 'info',
   levels,
   messageKey: 'msg',
+  errorKey: 'err',
   nestedKey: null,
   enabled: true,
   base: { pid, hostname },
@@ -88,6 +90,7 @@ function pino (...args) {
     serializers,
     timestamp,
     messageKey,
+    errorKey,
     nestedKey,
     base,
     name,
@@ -162,6 +165,7 @@ function pino (...args) {
     [endSym]: end,
     [formatOptsSym]: formatOpts,
     [messageKeySym]: messageKey,
+    [errorKeySym]: errorKey,
     [nestedKeySym]: nestedKey,
     // protect against injection
     [nestedKeyStrSym]: nestedKey ? `,${JSON.stringify(nestedKey)}:{` : '',


Test patch:
diff --git a/test/errorKey.test.js b/test/errorKey.test.js
new file mode 100644
index 0000000..5b3d411
--- /dev/null
+++ b/test/errorKey.test.js
@@ -0,0 +1,20 @@
+'use strict'
+const { test } = require('tap')
+const { sink, once } = require('./helper')
+const stdSerializers = require('pino-std-serializers')
+const pino = require('../')
+
+test('set the errorKey with error serializer', async ({ equal, same }) => {
+  const stream = sink()
+  const errorKey = 'error'
+  const instance = pino({
+    errorKey,
+    serializers: { [errorKey]: stdSerializers.err }
+  }, stream)
+  instance.error(new ReferenceError('test'))
+  const o = await once(stream, 'data')
+  equal(typeof o[errorKey], 'object')
+  equal(o[errorKey].type, 'ReferenceError')
+  equal(o[errorKey].message, 'test')
+  equal(typeof o[errorKey].stack, 'string')
+})
diff --git a/test/transport/core.test.js b/test/transport/core.test.js
index dcfff30..1fe2212 100644
--- a/test/transport/core.test.js
+++ b/test/transport/core.test.js
@@ -395,12 +395,9 @@ test('stdout in worker', async ({ not }) => {
   let actual = ''
   const child = execa(process.argv[0], [join(__dirname, '..', 'fixtures', 'transport-main.js')])
 
-  child.stdout.pipe(writer((s, enc, cb) => {
-    actual += s
-    cb()
-  }))
-  await once(child, 'close')
-  await immediate()
+  for await (const chunk of child.stdout) {
+    actual += chunk
+  }
   not(strip(actual).match(/Hello/), null)
 })