From 333e9f8b944d1cfe8a4250b39f8f9d6bf9f66e4a Mon Sep 17 00:00:00 2001 From: James <5511220+Zamiell@users.noreply.github.com> Date: Sat, 12 Aug 2023 02:10:04 -0400 Subject: [PATCH 01/12] fix: typescript types --- pino.d.ts | 57 ++++++++++- test/types/pino-arguments.test-d.ts | 145 ++++++++++++++++++++++++++++ 2 files changed, 197 insertions(+), 5 deletions(-) create mode 100644 test/types/pino-arguments.test-d.ts diff --git a/pino.d.ts b/pino.d.ts index 52f86bbac..e42c3350b 100644 --- a/pino.d.ts +++ b/pino.d.ts @@ -13,6 +13,7 @@ // Michel Nemnom // Igor Savin // James Bromwell +// Zamiell // TypeScript Version: 4.4 import type { EventEmitter } from "events"; @@ -127,6 +128,25 @@ export interface LoggerExtras extends EventEmitter { flush(cb?: (err?: Error) => void): void; } +/** + * The valid string interpolation placeholders are documented here: + * https://getpino.io/#/docs/api?id=logger + */ +interface StringInterpolationLetterToType { + s: string; + d: number; + o: object; + O: object; + j: object; +} + +type ExtractArgs = T extends `${string}%${infer R}` + ? R extends `${infer A}${infer B}` + ? A extends keyof StringInterpolationLetterToType + ? [StringInterpolationLetterToType[A], ...ExtractArgs] + : ExtractArgs + : ExtractArgs + : [] declare namespace pino { //// Exported types and interfaces @@ -313,11 +333,38 @@ declare namespace pino { } interface LogFn { - // TODO: why is this different from `obj: object` or `obj: any`? - /* tslint:disable:no-unnecessary-generics */ - (obj: T, msg?: string, ...args: any[]): void; - (obj: unknown, msg?: string, ...args: any[]): void; - (msg: string, ...args: any[]): void; + // The first overload has: + // - An object as the first argument. (But functions are explicitly disallowed, which count as objects.) + // - An optional string as the second argument. + // - N optional arguments after that corresponding to the string interpolation placeholders. + // e.g. + // logFn({ foo: "foo" }); + // logFn({ foo: "foo" }, "bar"); + // logFn({ foo: "foo" }, "Message with an interpolation value: %s", "bar"); + // logFn({ foo: "foo" }, "Message with two interpolation values: %s %d", "bar", 123); + ( + // We want to disallow functions, which count as the "object" type. + obj: never extends T ? (T extends Function ? never : T) : T, + msg?: Msg, + ...stringInterpolationArgs: ExtractArgs + ): void; + + // The second overload has: + // - A string as the first argument. + // - N optional arguments after that corresponding to the string interpolation placeholders. + // e.g. + // logFn("foo"); + // logFn("Message with an interpolation value: %s", "foo"); + // logFn("Message with two interpolation values: %s %d", "foo", 123); + (msg: Msg, ...stringInterpolationArgs: ExtractArgs): void; + + // The third overload has: + // - A `number` or `boolean` as the first argument. (`symbol` is explicitly disallowed.) + // - No additional arguments should be allowed. + // e.g. + // logFn(123); + // logFn(true); + (arg: T): void; } interface LoggerOptions { diff --git a/test/types/pino-arguments.test-d.ts b/test/types/pino-arguments.test-d.ts new file mode 100644 index 000000000..d6e52e1f8 --- /dev/null +++ b/test/types/pino-arguments.test-d.ts @@ -0,0 +1,145 @@ +import pino from "../../pino"; + +// This file tests the "LogFn" interface, located in the "pino.d.ts" file. + +const logger = pino(); + +// ---------------- +// 1 Argument Tests +// ---------------- + +// Works. +logger.info("Testing a basic string log message."); +logger.info("Using an unsupported string interpolation pattern like %x should not cause an error."); +logger.info({ foo: "foo" }); +logger.info(123); +logger.info(true); + +// Fails because these types are not supported. +// @ts-expect-error +logger.info(() => {}); +// @ts-expect-error +logger.info(Symbol("foo")); + +// ------------------------------------------- +// 2 Argument Tests (with string as first arg) +// ------------------------------------------- + +// Works +logger.info("Message with an interpolation value: %s", "foo"); +logger.info("Message with an interpolation value: %d", 123); +logger.info("Message with an interpolation value: %o", {}); + +// Fails because there isn't supposed to be a second argument. +// @ts-expect-error +logger.info("Message with no interpolation value.", "foo"); + +// Fails because we forgot the second argument entirely. +// @ts-expect-error +logger.info("Message with an interpolation value: %s"); +// @ts-expect-error +logger.info("Message with an interpolation value: %d"); +// @ts-expect-error +logger.info("Message with an interpolation value: %o"); + +// Fails because we put the wrong type as the second argument. +// @ts-expect-error +logger.info("Message with an interpolation value: %s", 123); +// @ts-expect-error +logger.info("Message with an interpolation value: %d", "foo"); +// @ts-expect-error +logger.info("Message with an interpolation value: %o", "foo"); + +// ------------------------------------------- +// 2 Argument Tests (with object as first arg) +// ------------------------------------------- + +// Works +logger.info({ foo: "foo" }, "bar"); + +// Fails because the second argument must be a string. +// @ts-expect-error +logger.info({ foo: "foo" }, 123); + +// ------------------------------------------- +// 3 Argument Tests (with string as first arg) +// ------------------------------------------- + +// Works +logger.info("Message with two interpolation values: %s %s", "foo", "bar"); +logger.info("Message with two interpolation values: %d %d", 123, 456); +logger.info("Message with two interpolation values: %o %o", {}, {}); + +// Fails because we forgot the third argument entirely. +// @ts-expect-error +logger.info("Message with two interpolation values: %s %s", "foo"); +// @ts-expect-error +logger.info("Message with two interpolation values: %d %d", 123); +// @ts-expect-error +logger.info("Message with two interpolation values: %o %o", {}); + +// Works +logger.info("Message with two interpolation values of different types: %s %d", "foo", 123); +logger.info("Message with two interpolation values of different types: %d %o", 123, {}); + +// Fails because we put the wrong type as the third argument. +// @ts-expect-error +logger.info("Message with two interpolation values of different types: %s %d", "foo", "bar"); +// @ts-expect-error +logger.info("Message with two interpolation values of different types: %d %o", 123, 456); + +// ------------------------------------------- +// 3 Argument Tests (with object as first arg) +// ------------------------------------------- + +// Works +logger.info({ foo: "foo" }, "Message with an interpolation value: %s", "foo"); +logger.info({ foo: "foo" }, "Message with an interpolation value: %d", 123); +logger.info({ foo: "foo" }, "Message with an interpolation value: %o", {}); + +// Fails because there isn't supposed to be a third argument. +// @ts-expect-error +logger.info({ foo: "foo" }, "Message with no interpolation value.", "foo"); + +// Fails because we forgot the third argument entirely. +// @ts-expect-error +logger.info({ foo: "foo" }, "Message with an interpolation value: %s"); +// @ts-expect-error +logger.info({ foo: "foo" }, "Message with an interpolation value: %d"); +// @ts-expect-error +logger.info({ foo: "foo" }, "Message with an interpolation value: %o"); + +// Fails because we put the wrong type as the third argument. +// @ts-expect-error +logger.info({ foo: "foo" }, "Message with an interpolation value: %s", 123); +// @ts-expect-error +logger.info({ foo: "foo" }, "Message with an interpolation value: %d", "foo"); +// @ts-expect-error +logger.info({ foo: "foo" }, "Message with an interpolation value: %o", "foo"); + +// ------------------------------------------- +// 4 Argument Tests (with object as first arg) +// ------------------------------------------- + +// Works +logger.info({ foo: "foo" }, "Message with two interpolation values: %s %s", "foo", "bar"); +logger.info({ foo: "foo" }, "Message with two interpolation values: %d %d", 123, 456); +logger.info({ foo: "foo" }, "Message with two interpolation values: %o %o", {}, {}); + +// Fails because we forgot the third argument entirely. +// @ts-expect-error +logger.info({ foo: "foo" }, "Message with two interpolation values: %s %s", "foo"); +// @ts-expect-error +logger.info({ foo: "foo" }, "Message with two interpolation values: %d %d", 123); +// @ts-expect-error +logger.info({ foo: "foo" }, "Message with two interpolation values: %o %o", {}); + +// Works +logger.info({ foo: "foo" }, "Message with two interpolation values of different types: %s %d", "foo", 123); +logger.info({ foo: "foo" }, "Message with two interpolation values of different types: %d %o", 123, {}); + +// Fails because we put the wrong type as the fourth argument. +// @ts-expect-error +logger.info({ foo: "foo" }, "Message with two interpolation values of different types: %s %d", "foo", "bar"); +// @ts-expect-error +logger.info({ foo: "foo" }, "Message with two interpolation values of different types: %d %o", 123, 456); From ad4347a65e94555a8581be3a1405bb0cd7c0252e Mon Sep 17 00:00:00 2001 From: James <5511220+Zamiell@users.noreply.github.com> Date: Sat, 12 Aug 2023 02:35:12 -0400 Subject: [PATCH 02/12] fix: tests --- pino.d.ts | 3 ++- test/types/pino.test-d.ts | 13 ++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/pino.d.ts b/pino.d.ts index e42c3350b..6b09218ff 100644 --- a/pino.d.ts +++ b/pino.d.ts @@ -140,6 +140,7 @@ interface StringInterpolationLetterToType { j: object; } +/** Helper type to extract the string interpolation placeholders and convert them to types. */ type ExtractArgs = T extends `${string}%${infer R}` ? R extends `${infer A}${infer B}` ? A extends keyof StringInterpolationLetterToType @@ -364,7 +365,7 @@ declare namespace pino { // e.g. // logFn(123); // logFn(true); - (arg: T): void; + (arg: number | boolean): void; } interface LoggerOptions { diff --git a/test/types/pino.test-d.ts b/test/types/pino.test-d.ts index 0641ad0cc..4124fbf2b 100644 --- a/test/types/pino.test-d.ts +++ b/test/types/pino.test-d.ts @@ -14,6 +14,8 @@ info("the answer is %d", 42); info({ obj: 42 }, "hello world"); info({ obj: 42, b: 2 }, "hello world"); info({ obj: { aa: "bbb" } }, "another"); +// @ts-expect-error The type definitions will throw an error if you call log functions using +// higher order functions. setImmediate(info, "after setImmediate"); error(new Error("an error")); @@ -108,7 +110,6 @@ pino({ }); pino({ base: null }); -// @ts-expect-error if ("pino" in log) console.log(`pino version: ${log.pino}`); expectType(log.flush()); @@ -264,9 +265,19 @@ interface StrictShape { err?: unknown; } +// The following generic parameter is no longer supported: + +/* info({ activity: "Required property", }); +*/ + +// Instead, the `satisfies` operator should be used like you would with any other TypeScript code, like in the below example. + +info({ + activity: "Required property", +} satisfies StrictShape); const logLine: pino.LogDescriptor = { level: 20, From 5a3063c7e0ef4463951a767ebf2346e38939444a Mon Sep 17 00:00:00 2001 From: James <5511220+Zamiell@users.noreply.github.com> Date: Sat, 12 Aug 2023 02:40:54 -0400 Subject: [PATCH 03/12] fix: comment --- test/types/pino.test-d.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/types/pino.test-d.ts b/test/types/pino.test-d.ts index 4124fbf2b..932c2ddb3 100644 --- a/test/types/pino.test-d.ts +++ b/test/types/pino.test-d.ts @@ -14,9 +14,9 @@ info("the answer is %d", 42); info({ obj: 42 }, "hello world"); info({ obj: 42, b: 2 }, "hello world"); info({ obj: { aa: "bbb" } }, "another"); -// @ts-expect-error The type definitions will throw an error if you call log functions using -// higher order functions. -setImmediate(info, "after setImmediate"); +// The type definitions will not work properly when using higher order functions, so we have to +// perform a manual type assertion. +setImmediate(info as (string) => void, "after setImmediate"); error(new Error("an error")); const writeSym = pino.symbols.writeSym; From 5f85511a55391af7d4df5c6d2873007e3e2d5cad Mon Sep 17 00:00:00 2001 From: James <5511220+Zamiell@users.noreply.github.com> Date: Sat, 12 Aug 2023 02:44:03 -0400 Subject: [PATCH 04/12] fix: tests --- test/types/pino.test-d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/types/pino.test-d.ts b/test/types/pino.test-d.ts index 932c2ddb3..ecb36f9c5 100644 --- a/test/types/pino.test-d.ts +++ b/test/types/pino.test-d.ts @@ -311,6 +311,7 @@ const customBaseLogger: CustomBaseLogger = { // custom levels const log3 = pino({ customLevels: { myLevel: 100 } }) +// @ts-expect-error We intentionally cause a run-time error here. expectError(log3.log()) log3.level = 'myLevel' log3.myLevel('') From b58e562c9b6027f599e0a196f0c71efffc3de927 Mon Sep 17 00:00:00 2001 From: James <5511220+Zamiell@users.noreply.github.com> Date: Sat, 12 Aug 2023 02:53:47 -0400 Subject: [PATCH 05/12] fix: tests --- test/types/pino.test-d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/types/pino.test-d.ts b/test/types/pino.test-d.ts index ecb36f9c5..d81c9220f 100644 --- a/test/types/pino.test-d.ts +++ b/test/types/pino.test-d.ts @@ -16,7 +16,7 @@ info({ obj: 42, b: 2 }, "hello world"); info({ obj: { aa: "bbb" } }, "another"); // The type definitions will not work properly when using higher order functions, so we have to // perform a manual type assertion. -setImmediate(info as (string) => void, "after setImmediate"); +setImmediate(info as (msg: string) => void, "after setImmediate"); error(new Error("an error")); const writeSym = pino.symbols.writeSym; From 0be6d5a9b7df79f53250612df994ed558a88bdb8 Mon Sep 17 00:00:00 2001 From: James <5511220+Zamiell@users.noreply.github.com> Date: Mon, 14 Aug 2023 12:07:01 -0400 Subject: [PATCH 06/12] docs: typescript info --- README.md | 3 ++- docs/typescript.md | 58 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 docs/typescript.md diff --git a/README.md b/README.md index 25929756e..bf9a8af64 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ ![banner](pino-banner.png) # pino + [![npm version](https://img.shields.io/npm/v/pino)](https://www.npmjs.com/package/pino) [![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/) @@ -18,6 +19,7 @@ * [Web Frameworks ⇗](/docs/web.md) * [Pretty Printing ⇗](/docs/pretty.md) * [Asynchronous Logging ⇗](/docs/asynchronous.md) +* [Usage With TypeScript](/docs/typescript.md) * [Ecosystem ⇗](/docs/ecosystem.md) * [Help ⇗](/docs/help.md) * [Long Term Support Policy ⇗](/docs/lts.md) @@ -64,7 +66,6 @@ For using Pino with a web framework see: * [Pino with Node core `http`](docs/web.md#http) * [Pino with Nest](docs/web.md#nest) - ## Essentials diff --git a/docs/typescript.md b/docs/typescript.md new file mode 100644 index 000000000..51b4aa293 --- /dev/null +++ b/docs/typescript.md @@ -0,0 +1,58 @@ +# Usage With TypeScript + +## Introduction + +If you are using TypeScript, Pino should work out of the box without any additional configuration. This is because even though Pino is written in JavaScript, it includes [a TypeScript definitions file](https://github.com/pinojs/pino/blob/master/pino.d.ts) as part of its bundle. + +In new TypeScript projects, you will want to use the ESM import style, like this: + +```ts +import pino from "pino"; + +const logger = pino(); + +logger.info('hello world'); +``` + +Some edge-cases are listed below. + +## String Interpolation + +The TypeScript definitions are configured to detect string interpolation arguments like this: + +```ts +const foo: string = getFoo(); +logger.info("foo: %s", foo); +``` + +In this case, `%s` refers to a string, as explained in the [documentation for logging method parameters](https://getpino.io/#/docs/api?id=logger). + +## Validating the Object + +Pino supports [logging both strings and objects](https://getpino.io/#/docs/api?id=logger). If you are passing an object to a Pino logger, you might want to validate that the object is in the correct shape. You can do this with the [`satisfies` operator](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html) in the same way that you would in other kinds of TypeScript code. For example: + +```ts +const myObject = { + foo: "someString", + bar: "someString", +} satisfies MyObject; +logger.info(strictShape); +``` + +Note that passing the object type as the first generic parameter to the logger is no longer supported. + +## Higher Order Functions + +Unfortunately, the type definitions for the Pino logger may not work properly when invoking them from a higher order function. For example: + +```ts +setTimeout(logger, 1000, "A second has passed!"); +``` + +This is a valid invocation of the logger (i.e. simply passing a single string argument), but TypeScript will throw a spurious error. To work around this, one solution is to wrap the function invocation like this: + +```ts +setTimeout(() => { + logger("A second has passed!"); +}, 1000); +``` From d2425fa1c3b31ba0be313dbddc3d1b22eb3872b3 Mon Sep 17 00:00:00 2001 From: James <5511220+Zamiell@users.noreply.github.com> Date: Mon, 14 Aug 2023 12:11:51 -0400 Subject: [PATCH 07/12] docs: add more docs --- docs/typescript.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/typescript.md b/docs/typescript.md index 51b4aa293..6fb469d21 100644 --- a/docs/typescript.md +++ b/docs/typescript.md @@ -27,6 +27,14 @@ logger.info("foo: %s", foo); In this case, `%s` refers to a string, as explained in the [documentation for logging method parameters](https://getpino.io/#/docs/api?id=logger). +If you use a string interpolation placeholder without a corresponding argument or with an argument of the wrong type, the TypeScript compiler will throw an error. For example: + +```ts +const foo: string = getFoo(); +logger.info("foo: %s"); // Error: Missing an expected argument. +logger.info("foo: %d", foo); // Error: `foo` is not a number. +``` + ## Validating the Object Pino supports [logging both strings and objects](https://getpino.io/#/docs/api?id=logger). If you are passing an object to a Pino logger, you might want to validate that the object is in the correct shape. You can do this with the [`satisfies` operator](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html) in the same way that you would in other kinds of TypeScript code. For example: From b0bc1040593c977b469328724b2704a28eb25f14 Mon Sep 17 00:00:00 2001 From: James <5511220+Zamiell@users.noreply.github.com> Date: Mon, 14 Aug 2023 12:16:35 -0400 Subject: [PATCH 08/12] docs: update typescript.md --- docs/typescript.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/typescript.md b/docs/typescript.md index 6fb469d21..645b88a62 100644 --- a/docs/typescript.md +++ b/docs/typescript.md @@ -64,3 +64,11 @@ setTimeout(() => { logger("A second has passed!"); }, 1000); ``` + +Another solution would be to perform a manual type assertion like this: + +```ts +setTimeout(logger as (message: string) => void, 1000, "A second has passed!"); +``` + +Obviously, using type assertions makes your code less safe, so use the second solution with care. From 43560ff5e273a2cbcc3bbbb004d2ccf7ed5f742c Mon Sep 17 00:00:00 2001 From: James <5511220+Zamiell@users.noreply.github.com> Date: Mon, 27 Nov 2023 19:07:04 -0500 Subject: [PATCH 09/12] chore: update tsd dep --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 5e7cd97a7..b4f11412e 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,7 @@ "tape": "^5.5.3", "through2": "^4.0.0", "ts-node": "^10.9.1", - "tsd": "^0.24.1", + "tsd": "^0.29.0", "typescript": "^5.1.3", "winston": "^3.7.2" }, @@ -115,5 +115,6 @@ }, "tsd": { "directory": "test/types" - } + }, + "packageManager": "yarn@1.22.19" } From 33247406e40d253e2441f37e98d85a4b6d99e38d Mon Sep 17 00:00:00 2001 From: James <5511220+Zamiell@users.noreply.github.com> Date: Mon, 27 Nov 2023 19:15:03 -0500 Subject: [PATCH 10/12] chore: remove yarn --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index b4f11412e..c98482c97 100644 --- a/package.json +++ b/package.json @@ -115,6 +115,5 @@ }, "tsd": { "directory": "test/types" - }, - "packageManager": "yarn@1.22.19" + } } From f92d0432f7f00d5fd2be84b84acd19473deba98d Mon Sep 17 00:00:00 2001 From: James <5511220+Zamiell@users.noreply.github.com> Date: Tue, 28 Nov 2023 07:39:14 -0500 Subject: [PATCH 11/12] fix: tests --- test/types/pino.test-d.ts | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/test/types/pino.test-d.ts b/test/types/pino.test-d.ts index d81c9220f..9331917b9 100644 --- a/test/types/pino.test-d.ts +++ b/test/types/pino.test-d.ts @@ -256,7 +256,7 @@ pino({ name: "my-logger" }, destinationViaOptionsObject); try { throw new Error('Some error') -} catch (err) { +} catch (err: any) { log.error(err) } @@ -265,16 +265,6 @@ interface StrictShape { err?: unknown; } -// The following generic parameter is no longer supported: - -/* -info({ - activity: "Required property", -}); -*/ - -// Instead, the `satisfies` operator should be used like you would with any other TypeScript code, like in the below example. - info({ activity: "Required property", } satisfies StrictShape); @@ -311,7 +301,6 @@ const customBaseLogger: CustomBaseLogger = { // custom levels const log3 = pino({ customLevels: { myLevel: 100 } }) -// @ts-expect-error We intentionally cause a run-time error here. expectError(log3.log()) log3.level = 'myLevel' log3.myLevel('') From 2885e04648f6c8f20993103102d5bc9dd09c0090 Mon Sep 17 00:00:00 2001 From: James <5511220+Zamiell@users.noreply.github.com> Date: Mon, 4 Dec 2023 07:38:07 -0500 Subject: [PATCH 12/12] fix: readme --- README.md | 33 +++++++++++++++++++++------------ docsify/sidebar.md | 1 + 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index bf9a8af64..05be6a93d 100644 --- a/README.md +++ b/README.md @@ -10,19 +10,28 @@ ## Documentation -* [Benchmarks ⇗](/docs/benchmarks.md) -* [API ⇗](/docs/api.md) -* [Browser API ⇗](/docs/browser.md) -* [Redaction ⇗](/docs/redaction.md) -* [Child Loggers ⇗](/docs/child-loggers.md) -* [Transports ⇗](/docs/transports.md) -* [Web Frameworks ⇗](/docs/web.md) -* [Pretty Printing ⇗](/docs/pretty.md) -* [Asynchronous Logging ⇗](/docs/asynchronous.md) +* [Readme](/) +* [API](/docs/api.md) +* [Browser API](/docs/browser.md) +* [Redaction](/docs/redaction.md) +* [Child Loggers](/docs/child-loggers.md) +* [Transports](/docs/transports.md) +* [Web Frameworks](/docs/web.md) +* [Pretty Printing](/docs/pretty.md) +* [Asynchronous Logging](/docs/asynchronous.md) * [Usage With TypeScript](/docs/typescript.md) -* [Ecosystem ⇗](/docs/ecosystem.md) -* [Help ⇗](/docs/help.md) -* [Long Term Support Policy ⇗](/docs/lts.md) +* [Ecosystem](/docs/ecosystem.md) +* [Benchmarks](/docs/benchmarks.md) +* [Long Term Support](/docs/lts.md) +* [Help](/docs/help.md) + * [Log rotation](/docs/help.md#rotate) + * [Reopening log files](/docs/help.md#reopening) + * [Saving to multiple files](/docs/help.md#multiple) + * [Log filtering](/docs/help.md#filter-logs) + * [Transports and systemd](/docs/help.md#transport-systemd) + * [Duplicate keys](/docs/help.md#dupe-keys) + * [Log levels as labels instead of numbers](/docs/help.md#level-string) + * [Pino with `debug`](/docs/help.md#debug) ## Install diff --git a/docsify/sidebar.md b/docsify/sidebar.md index 75d4ef0c4..f5cb45c73 100644 --- a/docsify/sidebar.md +++ b/docsify/sidebar.md @@ -7,6 +7,7 @@ * [Web Frameworks](/docs/web.md) * [Pretty Printing](/docs/pretty.md) * [Asynchronous Logging](/docs/asynchronous.md) +* [Usage With TypeScript](/docs/typescript.md) * [Ecosystem](/docs/ecosystem.md) * [Benchmarks](/docs/benchmarks.md) * [Long Term Support](/docs/lts.md)