Repository: pinojs/pino (js)

Issue:
`browser.disabled` missing from `pino.LoggerOptions` type deffinition
Hi There, 

Just been setting up Pino in my project and I'm a bit of a typescript head... Noticed that [`browser.disabled`](https://github.com/pinojs/pino/blob/master/docs/browser.md#disabled-boolean) is not present in the `pino.LoggerOptions` type. 

```ts
const logger = pino({
      ...transport,
      browser: {
       disabled: process.env.NEXT_PUBLIC_BROWSER_LOGGING_DISABLED === 'true' // Not present in types!
        level: process.env.NEXT_PUBLIC_BROWSER_TRANSMIT_LOG_LEVEL === 'true',
        transmit: {
          send: (level, logEvent) => {
           // ....
            })
          },
        },
      },
    } as LoggerOptions)

```

I think this is probably just something that has been missed? 

Happy to do a quick PR and include the text from the docs to the typing if I'm right! 

Thanks for the awesome project!! 🙌



Pull request:
fix(type): add `disabled` to `pino.LoggerOptions`
fixes #1628

Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ad4064b..21cc825 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -42,7 +42,7 @@ jobs:
       fail-fast: false
       matrix:
         os: [macOS-latest, windows-latest, ubuntu-latest]
-        node-version: [14, 16, 18]
+        node-version: [14, 16, 18, 19]
     steps:
       - name: Check out repo
         uses: actions/checkout@v3
@@ -60,6 +60,12 @@ jobs:
       - name: Run tests
         run: npm run test-ci
 
+      - name: Run smoke test
+        if: >
+          matrix.os != 'windows-latest' &&
+          matrix.node-version > 14
+        run: npm run test:smoke
+
   automerge:
     name: Automerge Dependabot PRs
     if: >
diff --git a/README.md b/README.md
index fa8c701..2592975 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
 
 # pino
 [![npm version](https://img.shields.io/npm/v/pino)](https://www.npmjs.com/package/pino)
-[![Build Status](https://img.shields.io/github/workflow/status/pinojs/pino/CI)](https://github.com/pinojs/pino/actions)
+[![Build Status](https://img.shields.io/github/actions/workflow/status/pinojs/pino/ci.yml)](https://github.com/pinojs/pino/actions)
 [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://standardjs.com/)
 
 [Very low overhead](#low-overhead) Node.js logger.
diff --git a/docs/api.md b/docs/api.md
index cfd1ca7..e410c2a 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -384,6 +384,24 @@ See `errorKey` option to change `err` namespace.
 
 * See [pino.stdSerializers](#pino-stdserializers)
 
+#### `msgPrefix` (String)
+
+Default: `undefined`
+
+The `msgPrefix` property allows you to specify a prefix for every message of the logger and its children.
+
+```js
+const logger = pino({
+  msgPrefix: '[HTTP] '
+})
+logger.info('got new request!')
+// >  [HTTP] got new request!
+
+const child = logger.child({})
+child.info('User authenticated!')
+// >  [HTTP] User authenticated!
+```
+
 #### `base` (Object)
 
 Default: `{pid: process.pid, hostname: os.hostname}`
@@ -842,6 +860,26 @@ const child = logger.child({foo: 'bar'}, {level: 'debug'})
 child.debug('debug!') // will log as the `level` property set the level to debug
 ```
 
+##### `options.msgPrefix` (String)
+
+Default: `undefined`
+
+The `msgPrefix` property allows you to specify a prefix for every message of the child logger.
+By default, the parent prefix is inherited.
+If the parent already has a prefix, the prefix of the parent and then the child will be displayed.
+
+```js
+const logger = pino({
+  msgPrefix: '[HTTP] '
+})
+logger.info('got new request!')
+// >  [HTTP] got new request!
+
+const child = logger.child({avengers: 'assemble'}, {msgPrefix: '[Proxy] '})
+child.info('message proxied!')
+// >  [HTTP] [Proxy] message proxied!
+```
+
 ##### `options.redact` (Array | Object)
 
 Setting `options.redact` to an array or object will override the parent `redact` options. To remove `redact` options inherited from the parent logger set this value as an empty array (`[]`).
@@ -1162,6 +1200,7 @@ For more on transports, how they work, and how to create them see the [`Transpor
 * `worker`: [Worker thread](https://nodejs.org/api/worker_threads.html#worker_threads_new_worker_filename_options) configuration options. Additionally, the `worker` option supports `worker.autoEnd`. If this is set to `false` logs will not be flushed on process exit. It is then up to the developer to call `transport.end()` to flush logs.
 * `targets`: May be specified instead of `target`. Must be an array of transport configurations. Transport configurations include the aforementioned `options` and `target` options plus a `level` option which will send only logs above a specified level to a transport.
 * `pipeline`: May be specified instead of `target`. Must be an array of transport configurations. Transport configurations include the aforementioned `options` and `target` options. All intermediate steps in the pipeline _must_ be `Transform` streams and not `Writable`.
+* `dedupe`: See [pino.multistream options](#pino-multistream)
 
 <a id="pino-multistream"></a>
 
@@ -1191,7 +1230,7 @@ log.info('this will be written to /tmp/debug.stream.out and /tmp/info.stream.out
 log.fatal('this will be written to /tmp/debug.stream.out, /tmp/info.stream.out and /tmp/fatal.stream.out')
 ```
 
-In order for `multistream` to work, the log level __must__ be set to the lowest level used in the streams array.
+In order for `multistream` to work, the log level __must__ be set to the lowest level used in the streams array. Default is `info`.
 
 #### Options
 
@@ -1205,7 +1244,7 @@ In order for `multistream` to work, the log level __must__ be set to the lowest
     var pino = require('pino')
     var multistream = pino.multistream
     var streams = [
-      {stream: process.stdout},
+      {level: 'debug', stream: process.stdout},
       {level: 'error', stream: process.stderr},
     ]
 
diff --git a/docs/ecosystem.md b/docs/ecosystem.md
index b1611b1..34174b4 100644
--- a/docs/ecosystem.md
+++ b/docs/ecosystem.md
@@ -74,3 +74,4 @@ prettifier inspired by the [logrus](https://github.com/sirupsen/logrus) logger.
 + [`@newrelic/pino-enricher`](https://github.com/newrelic/newrelic-node-log-extensions/blob/main/packages/pino-log-enricher): a log customization to add New Relic context to use [Logs In Context](https://docs.newrelic.com/docs/logs/logs-context/logs-in-context/)
 + [`pino-lambda`](https://github.com/FormidableLabs/pino-lambda): log transport for cloudwatch support inside aws-lambda 
 + [`cloud-pine`](https://github.com/metcoder95/cloud-pine): transport that provides abstraction and compatibility with [`@google-cloud/logging`](https://www.npmjs.com/package/@google-cloud/logging).
++ [`crawlee-pino`](https://github.com/imyelo/crawlee-pino): use Pino to log within Crawlee
diff --git a/docs/transports.md b/docs/transports.md
index 32518d3..8a2239b 100644
--- a/docs/transports.md
+++ b/docs/transports.md
@@ -15,6 +15,8 @@ now referred to as [Legacy Transports](#legacy-transports).
 
 From Pino v7 and upwards transports can also operate inside a [Worker Thread][worker-thread]
 and can be used or configured via the options object passed to `pino` on initialization.
+In this case the transports would always operate asynchronously, and logs would be
+flushed as quickly as possible (there is nothing to do).
 
 [worker-thread]: https://nodejs.org/dist/latest-v14.x/docs/api/worker_threads.html
 
@@ -109,6 +111,19 @@ const transport = pino.transport({
 pino(transport)
 ```
 
+It is also possible to use the `dedupe` option to send logs only to the stream with the higher level.
+```js
+const pino = require('pino')
+const transport = pino.transport({
+  targets: [
+    { target: '/absolute/path/to/my-transport.mjs', level: 'error' },
+    { target: 'some-file-transport', options: { destination: '/dev/null' }
+  ],
+  dedupe: true
+})
+pino(transport)
+```
+
 For more details on `pino.transport` see the [API docs for `pino.transport`][pino-transport].
 
 [pino-transport]: /docs/api.md#pino-transport
@@ -375,6 +390,7 @@ PRs to this document are welcome for any new transports!
 
 ### Pino v7+ Compatible
 
++ [@logtail/pino](#@logtail/pino)
 + [pino-elasticsearch](#pino-elasticsearch)
 + [pino-pretty](#pino-pretty)
 + [pino-loki](#pino-loki)
@@ -409,6 +425,12 @@ PRs to this document are welcome for any new transports!
 + [pino-websocket](#pino-websocket)
 
 
+<a id="@logtail/pino"></a>
+### @logtail/pino
+
+The [@logtail/pino](https://www.npmjs.com/package/@logtail/pino) NPM package is a transport that forwards logs to [Logtail](https://logtail.com) by [Better Stack](https://betterstack.com).
+
+[Quick start guide ⇗](https://betterstack.com/docs/logs/javascript/pino)
 
 <a id="pino-applicationinsights"></a>
 ### pino-applicationinsights
diff --git a/inc-version.sh b/inc-version.sh
new file mode 100755
index 0000000..2ad7f52
--- /dev/null
+++ b/inc-version.sh
@@ -0,0 +1,42 @@
+#!/bin/bash 
+
+set -e
+
+PATH=./node_modules/.bin:${PATH}
+CURRENT_VERSION=$(jq -r .version package.json)
+
+case ${1} in
+  Major | MAJOR | major)
+    LEVEL=major
+    ;;
+
+  Minor | MINOR | minor)
+    LEVEL=minor
+    ;;
+
+  Patch | PATCH | patch)
+    LEVEL=patch
+    ;;
+
+  *)
+    LEVEL=patch
+    ;;
+esac
+
+NEW_VERSION=$(semver -i ${LEVEL} ${CURRENT_VERSION})
+echo "${CURRENT_VERSION} => ${NEW_VERSION}"
+read -n 1 -s -r -p "Press any key to continue (ctrl+c to abort)..."
+echo ""
+
+echo "Patching package.json..."
+cat package.json | \
+  jq --arg vers "${NEW_VERSION}" '.version = $vers' | \
+  tee package.json 1>/dev/null
+
+echo "Patching lib/meta.js ..."
+SED_SCRIPT=$(printf 's/%s/%s/' ${CURRENT_VERSION//\./\\.} ${NEW_VERSION//\./\\.})
+cat ./lib/meta.js | \
+  sed -e ${SED_SCRIPT} | \
+  tee ./lib/meta.js 1>/dev/null
+
+echo "Done."
diff --git a/lib/meta.js b/lib/meta.js
index 3a763b4..8583a50 100644
--- a/lib/meta.js
+++ b/lib/meta.js
@@ -1,3 +1,3 @@
 'use strict'
 
-module.exports = { version: '8.8.0' }
+module.exports = { version: '8.11.0' }
diff --git a/lib/proto.js b/lib/proto.js
index 4009452..d1dc3ff 100644
--- a/lib/proto.js
+++ b/lib/proto.js
@@ -25,7 +25,8 @@ const {
   redactFmtSym,
   stringifySym,
   formatOptsSym,
-  stringifiersSym
+  stringifiersSym,
+  msgPrefixSym
 } = require('./symbols')
 const {
   getLevel,
@@ -138,6 +139,10 @@ function child (bindings, options) {
     instance[formatOptsSym] = formatOpts
   }
 
+  if (typeof options.msgPrefix === 'string') {
+    instance[msgPrefixSym] = (this[msgPrefixSym] || '') + options.msgPrefix
+  }
+
   instance[chindingsSym] = asChindings(instance, bindings)
   const childLevel = options.level || this.level
   instance[setLevelSym](childLevel)
@@ -188,7 +193,7 @@ function write (_obj, msg, num) {
     }
   } else {
     obj = _obj
-    if (msg === undefined && _obj[errorKey]) {
+    if (msg === undefined && _obj.msg === undefined && _obj[errorKey]) {
       msg = _obj[errorKey].message
     }
   }
diff --git a/lib/symbols.js b/lib/symbols.js
index 2a0228e..08c0e88 100644
--- a/lib/symbols.js
+++ b/lib/symbols.js
@@ -27,6 +27,7 @@ const errorKeySym = Symbol('pino.errorKey')
 const nestedKeySym = Symbol('pino.nestedKey')
 const nestedKeyStrSym = Symbol('pino.nestedKeyStr')
 const mixinMergeStrategySym = Symbol('pino.mixinMergeStrategy')
+const msgPrefixSym = Symbol('pino.msgPrefix')
 
 const wildcardFirstSym = Symbol('pino.wildcardFirst')
 
@@ -66,5 +67,6 @@ module.exports = {
   formattersSym,
   hooksSym,
   nestedKeyStrSym,
-  mixinMergeStrategySym
+  mixinMergeStrategySym,
+  msgPrefixSym
 }
diff --git a/lib/tools.js b/lib/tools.js
index e16c0c7..92bd23b 100644
--- a/lib/tools.js
+++ b/lib/tools.js
@@ -21,12 +21,14 @@ const {
   formattersSym,
   messageKeySym,
   errorKeySym,
-  nestedKeyStrSym
+  nestedKeyStrSym,
+  msgPrefixSym
 } = require('./symbols')
 const { isMainThread } = require('worker_threads')
 const transport = require('./transport')
 
-function noop () {}
+function noop () {
+}
 
 function genLog (level, hook) {
   if (!hook) return LOG
@@ -52,9 +54,21 @@ function genLog (level, hook) {
         msg = n.shift()
         formatParams = n
       }
+      // We do not use a coercive check for `msg` as it is
+      // measurably slower than the explicit checks.
+      if (typeof this[msgPrefixSym] === 'string' && msg !== undefined && msg !== null) {
+        msg = this[msgPrefixSym] + msg
+      }
       this[writeSym](o, format(msg, formatParams, this[formatOptsSym]), level)
     } else {
-      this[writeSym](null, format(o === undefined ? n.shift() : o, n, this[formatOptsSym]), level)
+      let msg = o === undefined ? n.shift() : o
+
+      // We do not use a coercive check for `msg` as it is
+      // measurably slower than the explicit checks.
+      if (typeof this[msgPrefixSym] === 'string' && msg !== undefined && msg !== null) {
+        msg = this[msgPrefixSym] + msg
+      }
+      this[writeSym](null, format(msg, n, this[formatOptsSym]), level)
     }
   }
 }
diff --git a/lib/transport.js b/lib/transport.js
index e15ef7e..39de490 100644
--- a/lib/transport.js
+++ b/lib/transport.js
@@ -71,7 +71,7 @@ function flush (stream) {
 }
 
 function transport (fullOptions) {
-  const { pipeline, targets, levels, options = {}, worker = {}, caller = getCallers() } = fullOptions
+  const { pipeline, targets, levels, dedupe, options = {}, worker = {}, caller = getCallers() } = fullOptions
 
   // Backwards compatibility
   const callers = typeof caller === 'string' ? [caller] : caller
@@ -107,6 +107,10 @@ function transport (fullOptions) {
     options.levels = levels
   }
 
+  if (dedupe) {
+    options.dedupe = dedupe
+  }
+
   return buildStream(fixTarget(target), options, worker)
 
   function fixTarget (origin) {
diff --git a/lib/worker.js b/lib/worker.js
index 38c37d8..c20c19a 100644
--- a/lib/worker.js
+++ b/lib/worker.js
@@ -9,7 +9,7 @@ const loadTransportStreamBuilder = require('./transport-stream')
 
 /* istanbul ignore file */
 
-module.exports = async function ({ targets, levels }) {
+module.exports = async function ({ targets, levels, dedupe }) {
   targets = await Promise.all(targets.map(async (t) => {
     const fn = await loadTransportStreamBuilder(t.target)
     const stream = await fn(t.options)
@@ -38,7 +38,7 @@ module.exports = async function ({ targets, levels }) {
   })
 
   function process (stream) {
-    const multi = pino.multistream(targets, { levels })
+    const multi = pino.multistream(targets, { levels, dedupe })
     // TODO manage backpressure
     stream.on('data', function (chunk) {
       const { lastTime, lastMsg, lastObj, lastLevel } = this
diff --git a/package.json b/package.json
index 8616056..808b3cd 100644
--- a/package.json
+++ b/package.json
@@ -1,24 +1,11 @@
 {
   "name": "pino",
-  "version": "8.8.0",
+  "version": "8.11.0",
   "description": "super fast, all natural json logger",
   "main": "pino.js",
   "type": "commonjs",
   "types": "pino.d.ts",
   "browser": "./browser.js",
-  "files": [
-    "pino.js",
-    "file.js",
-    "pino.d.ts",
-    "bin.js",
-    "browser.js",
-    "pretty.js",
-    "usage.txt",
-    "test",
-    "docs",
-    "example.js",
-    "lib"
-  ],
   "scripts": {
     "docs": "docsify serve",
     "browser-test": "airtap --local 8080 test/browser*test.js",
@@ -29,6 +16,10 @@
     "test-ci-pnpm": "pnpm run lint && npm run transpile && tap --ts --no-coverage --no-check-coverage && pnpm run test-types",
     "test-ci-yarn-pnp": "yarn run lint && npm run transpile && tap --ts --no-check-coverage --coverage-report=lcovonly",
     "test-types": "tsc && tsd && ts-node test/types/pino.ts",
+    "test:smoke": "smoker smoke:pino && smoker smoke:browser && smoker smoke:file",
+    "smoke:pino": "node ./pino.js",
+    "smoke:browser": "node ./browser.js",
+    "smoke:file": "node ./file.js",
     "transpile": "node ./test/fixtures/ts/transpile.cjs",
     "cov-ui": "tap --ts --coverage-report=html",
     "bench": "node benchmarks/utils/runbench all",
@@ -91,6 +82,7 @@
     "jest": "^29.0.3",
     "log": "^6.0.0",
     "loglevel": "^1.6.7",
+    "midnight-smoker": "^2.0.1",
     "pino-pretty": "^9.0.0",
     "pre-commit": "^1.2.2",
     "proxyquire": "^2.1.3",
diff --git a/pino.d.ts b/pino.d.ts
index 8ac3ec4..285c4b3 100644
--- a/pino.d.ts
+++ b/pino.d.ts
@@ -262,6 +262,7 @@ declare namespace pino {
     interface TransportMultiOptions<TransportOptions = Record<string, any>> extends TransportBaseOptions<TransportOptions>{
         targets: readonly TransportTargetOptions<TransportOptions>[],
         levels?: Record<string, number>
+        dedupe?: boolean
     }
 
     interface MultiStreamOptions {
@@ -553,6 +554,13 @@ declare namespace pino {
                  */
                 send: (level: Level, logEvent: LogEvent) => void;
             };
+            /**
+             * The disabled option will disable logging in browser if set to true, by default it is set to false.
+             *
+             * @example
+             * const pino = require('pino')({browser: {disabled: true}})
+             */
+            disabled?: boolean;
         };
         /**
          * key-value object added as child logger to each log line. If set to null the base child logger is not added
@@ -588,6 +596,11 @@ declare namespace pino {
             log?: (object: Record<string, unknown>) => Record<string, unknown>;
         };
 
+        /**
+         * A string that would be prefixed to every message (and child message)
+         */
+        msgPrefix?: string
+
         /**
          * An object mapping to hook functions. Hook functions allow for customizing internal logger operations.
          * Hook functions must be synchronous functions.
@@ -628,6 +641,7 @@ declare namespace pino {
             log?: (object: object) => object;
         };
         redact?: string[] | redactOptions;
+        msgPrefix?: string
     }
 
     /**
diff --git a/pino.js b/pino.js
index c16a745..63500e4 100644
--- a/pino.js
+++ b/pino.js
@@ -40,7 +40,8 @@ const {
   formattersSym,
   hooksSym,
   nestedKeyStrSym,
-  mixinMergeStrategySym
+  mixinMergeStrategySym,
+  msgPrefixSym
 } = symbols
 const { epochTime, nullTime } = time
 const { pid } = process
@@ -103,7 +104,8 @@ function pino (...args) {
     hooks,
     depthLimit,
     edgeLimit,
-    onChild
+    onChild,
+    msgPrefix
   } = opts
 
   const stringifySafe = configure({
@@ -150,6 +152,7 @@ function pino (...args) {
 
   if (useOnlyCustomLevels && !customLevels) throw Error('customLevels is required if useOnlyCustomLevels is set true')
   if (mixin && typeof mixin !== 'function') throw Error(`Unknown mixin type "${typeof mixin}" - expected "function"`)
+  if (msgPrefix && typeof msgPrefix !== 'string') throw Error(`Unknown msgPrefix type "${typeof msgPrefix}" - expected "string"`)
 
   assertDefaultLevelFound(level, customLevels, useOnlyCustomLevels)
   const levels = mappings(customLevels, useOnlyCustomLevels)
@@ -177,7 +180,8 @@ function pino (...args) {
     [formattersSym]: allFormatters,
     [hooksSym]: hooks,
     silent: noop,
-    onChild
+    onChild,
+    [msgPrefixSym]: msgPrefix
   })
 
   Object.setPrototypeOf(instance, proto())


Test patch:
diff --git a/test/basic.test.js b/test/basic.test.js
index e8310d6..c8a6962 100644
--- a/test/basic.test.js
+++ b/test/basic.test.js
@@ -751,3 +751,74 @@ test('Should invoke `onChild` with the newly created child', async ({ equal }) =
   }).child({ foo: 'bar' })
   equal(child, innerChild)
 })
+
+test('logger message should have the prefix message that defined in the logger creation', async ({ equal }) => {
+  const stream = sink()
+  const logger = pino({
+    msgPrefix: 'My name is Bond '
+  }, stream)
+  logger.info('James Bond')
+  const { msg } = await once(stream, 'data')
+  equal(msg, 'My name is Bond James Bond')
+})
+
+test('child message should have the prefix message that defined in the child creation', async ({ equal }) => {
+  const stream = sink()
+  const instance = pino(stream)
+  const child = instance.child({}, { msgPrefix: 'My name is Bond ' })
+  child.info('James Bond')
+  const { msg } = await once(stream, 'data')
+  equal(msg, 'My name is Bond James Bond')
+})
+
+test('child message should have the prefix message that defined in the child creation when logging with log meta', async ({ equal }) => {
+  const stream = sink()
+  const instance = pino(stream)
+  const child = instance.child({}, { msgPrefix: 'My name is Bond ' })
+  child.info({ hello: 'world' }, 'James Bond')
+  const { msg, hello } = await once(stream, 'data')
+  equal(hello, 'world')
+  equal(msg, 'My name is Bond James Bond')
+})
+
+test('logged message should not have the prefix when not providing any message', async ({ equal }) => {
+  const stream = sink()
+  const instance = pino(stream)
+  const child = instance.child({}, { msgPrefix: 'This should not be shown ' })
+  child.info({ hello: 'world' })
+  const { msg, hello } = await once(stream, 'data')
+  equal(hello, 'world')
+  equal(msg, undefined)
+})
+
+test('child message should append parent prefix to current prefix that defined in the child creation', async ({ equal }) => {
+  const stream = sink()
+  const instance = pino({
+    msgPrefix: 'My name is Bond '
+  }, stream)
+  const child = instance.child({}, { msgPrefix: 'James ' })
+  child.info('Bond')
+  const { msg } = await once(stream, 'data')
+  equal(msg, 'My name is Bond James Bond')
+})
+
+test('child message should inherent parent prefix', async ({ equal }) => {
+  const stream = sink()
+  const instance = pino({
+    msgPrefix: 'My name is Bond '
+  }, stream)
+  const child = instance.child({})
+  child.info('James Bond')
+  const { msg } = await once(stream, 'data')
+  equal(msg, 'My name is Bond James Bond')
+})
+
+test('grandchild message should inherent parent prefix', async ({ equal }) => {
+  const stream = sink()
+  const instance = pino(stream)
+  const child = instance.child({}, { msgPrefix: 'My name is Bond ' })
+  const grandchild = child.child({})
+  grandchild.info('James Bond')
+  const { msg } = await once(stream, 'data')
+  equal(msg, 'My name is Bond James Bond')
+})
diff --git a/test/error.test.js b/test/error.test.js
index 0eb0b56..1869c39 100644
--- a/test/error.test.js
+++ b/test/error.test.js
@@ -356,3 +356,23 @@ test('correctly adds msg on error when nestedKey is used', async ({ same }) => {
     msg: 'msg message'
   })
 })
+
+test('msg should take precedence over error message on mergingObject', async ({ same }) => {
+  const err = new Error('myerror')
+  const stream = sink()
+  const instance = pino(stream)
+  instance.error({ msg: 'my message', err })
+  const result = await once(stream, 'data')
+  delete result.time
+  same(result, {
+    pid,
+    hostname,
+    level: 50,
+    err: {
+      type: 'Error',
+      stack: err.stack,
+      message: err.message
+    },
+    msg: 'my message'
+  })
+})
diff --git a/test/transport/core.test.js b/test/transport/core.test.js
index 1fe2212..286d744 100644
--- a/test/transport/core.test.js
+++ b/test/transport/core.test.js
@@ -201,6 +201,44 @@ test('pino.transport with two files and custom levels', async ({ same, teardown
   })
 })
 
+test('pino.transport with two files and dedupe', async ({ same, teardown }) => {
+  const dest1 = file()
+  const dest2 = file()
+  const transport = pino.transport({
+    dedupe: true,
+    targets: [{
+      level: 'info',
+      target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'),
+      options: { destination: dest1 }
+    }, {
+      level: 'error',
+      target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'),
+      options: { destination: dest2 }
+    }]
+  })
+  teardown(transport.end.bind(transport))
+  const instance = pino(transport)
+  instance.info('hello')
+  instance.error('world')
+  await Promise.all([watchFileCreated(dest1), watchFileCreated(dest2)])
+  const result1 = JSON.parse(await readFile(dest1))
+  delete result1.time
+  same(result1, {
+    pid,
+    hostname,
+    level: 30,
+    msg: 'hello'
+  })
+  const result2 = JSON.parse(await readFile(dest2))
+  delete result2.time
+  same(result2, {
+    pid,
+    hostname,
+    level: 50,
+    msg: 'world'
+  })
+})
+
 test('pino.transport with an array including a pino-pretty destination', async ({ same, match, teardown }) => {
   const dest1 = file()
   const dest2 = file()
diff --git a/test/types/pino-transport.test-d.ts b/test/types/pino-transport.test-d.ts
index 60f452f..8666846 100644
--- a/test/types/pino-transport.test-d.ts
+++ b/test/types/pino-transport.test-d.ts
@@ -120,3 +120,9 @@ pino.transport({
     },
     options: { id: 'abc' }
 })
+
+// Dedupe
+pino.transport({
+    targets: [],
+    dedupe: true,
+})