Repository: pinojs/pino (js)

Issue:
Consider adding stream metadata types to pino.d.ts
e.g.
```ts
const metadata = Symbol.for("pino.metadata");

export interface DestinationStreamMetadata {
  [metadata]?: boolean;
  lastLevel?: number;
  lastTime?: string;
  lastMsg?: string;
  lastObj?: object;
  lastLogger?: pino.Logger;
}
```

Though using discriminated union type narrowing might make for a better type:
```ts
const metadata = Symbol.for("pino.metadata");

interface DestinationStreamHasMetadata {
  [metadata]: true;
  lastLevel: number;
  lastTime: string;
  lastMsg: string;
  lastObj: object;
  lastLogger: pino.Logger;
}

export type DestinationStreamMetadata = { [metadata]?: boolean } | DestinationStreamHasMetadata;
```

Pull request description:
Add DestinationStreamMetadata type to pino.d.ts
Fixes #1557

Patch:
diff --git a/lib/meta.js b/lib/meta.js
index c567f7b..44f8293 100644
--- a/lib/meta.js
+++ b/lib/meta.js
@@ -1,3 +1,3 @@
 'use strict'
 
-module.exports = { version: '8.5.0' }
+module.exports = { version: '8.6.1' }
diff --git a/lib/tools.js b/lib/tools.js
index 674198b..b44a961 100644
--- a/lib/tools.js
+++ b/lib/tools.js
@@ -53,7 +53,7 @@ function genLog (level, hook) {
       }
       this[writeSym](o, format(msg, formatParams, this[formatOptsSym]), level)
     } else {
-      this[writeSym](null, format(o, n, this[formatOptsSym]), level)
+      this[writeSym](null, format(o === undefined ? n.shift() : o, n, this[formatOptsSym]), level)
     }
   }
 }
diff --git a/package.json b/package.json
index 6388d4a..a0c3489 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "pino",
-  "version": "8.5.0",
+  "version": "8.6.1",
   "description": "super fast, all natural json logger",
   "main": "pino.js",
   "type": "commonjs",
diff --git a/pino.d.ts b/pino.d.ts
index 5442034..ffd0943 100644
--- a/pino.d.ts
+++ b/pino.d.ts
@@ -274,6 +274,17 @@ declare namespace pino {
         write(msg: string): void;
     }
 
+    interface DestinationStreamHasMetadata {
+      [symbols.needsMetadataGsym]: true;
+      lastLevel: number;
+      lastTime: string;
+      lastMsg: string;
+      lastObj: object;
+      lastLogger: pino.Logger;
+    }
+
+    type DestinationStreamWithMetadata = DestinationStream & ({ [symbols.needsMetadataGsym]?: false } | DestinationStreamHasMetadata);
+
     interface StreamEntry {
         stream: DestinationStream
         level?: Level
@@ -795,6 +806,7 @@ export const version: typeof pino.version;
 
 // Types
 export type Bindings = pino.Bindings;
+export type DestinationStreamWithMetadata = pino.DestinationStreamWithMetadata;
 export type Level = pino.Level;
 export type LevelWithSilent = pino.LevelWithSilent;
 export type LevelChangeEventListener = pino.LevelChangeEventListener;


Test patch:
diff --git a/test/basic.test.js b/test/basic.test.js
index ca4ca1e..e8310d6 100644
--- a/test/basic.test.js
+++ b/test/basic.test.js
@@ -109,6 +109,22 @@ function levelTest (name, level) {
     same(Object.keys(obj), ['hello'])
   })
 
+  test(`passing a undefined and a string at level ${name}`, async ({ equal, same }) => {
+    const stream = sink()
+    const instance = pino(stream)
+    instance.level = name
+    instance[name](undefined, 'a string')
+    const result = await once(stream, 'data')
+    equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()')
+    delete result.time
+    same(result, {
+      pid,
+      hostname,
+      level,
+      msg: 'a string'
+    })
+  })
+
   test(`overriding object key by string at level ${name}`, async ({ equal, same }) => {
     const stream = sink()
     const instance = pino(stream)
diff --git a/test/types/pino-type-only.test-d.ts b/test/types/pino-type-only.test-d.ts
index 54ea04b..f106cd2 100644
--- a/test/types/pino-type-only.test-d.ts
+++ b/test/types/pino-type-only.test-d.ts
@@ -1,7 +1,7 @@
 import { expectAssignable, expectType } from "tsd";
 
 import pino from "../../";
-import type {LevelWithSilent, Logger, LogFn, P} from "../../pino";
+import type {LevelWithSilent, Logger, LogFn, P, DestinationStreamWithMetadata } from "../../pino";
 
 // NB: can also use `import * as pino`, but that form is callable as `pino()`
 // under `esModuleInterop: false` or `pino.default()` under `esModuleInterop: true`.
@@ -14,3 +14,15 @@ expectType<P.LogFn>(log.info);
 
 const level: LevelWithSilent = 'silent';
 expectAssignable<P.LevelWithSilent>(level);
+
+function createStream(): DestinationStreamWithMetadata {
+    return { write() {} };
+}
+
+const stream = createStream();
+// Argh. TypeScript doesn't seem to narrow unless we assign the symbol like so, and tsd seems to
+// break without annotating the type explicitly
+const needsMetadata: typeof pino.symbols.needsMetadataGsym = pino.symbols.needsMetadataGsym;
+if (stream[needsMetadata]) {
+    expectType<number>(stream.lastLevel);
+}