diff --git a/README.md b/README.md index 05be6a93d..25929756e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ ![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/) @@ -10,28 +9,18 @@ ## Documentation -* [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) -* [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) +* [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) +* [Ecosystem ⇗](/docs/ecosystem.md) +* [Help ⇗](/docs/help.md) +* [Long Term Support Policy ⇗](/docs/lts.md) ## Install @@ -75,6 +64,7 @@ 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 deleted file mode 100644 index 645b88a62..000000000 --- a/docs/typescript.md +++ /dev/null @@ -1,74 +0,0 @@ -# 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). - -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: - -```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); -``` - -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. diff --git a/docsify/sidebar.md b/docsify/sidebar.md index f5cb45c73..75d4ef0c4 100644 --- a/docsify/sidebar.md +++ b/docsify/sidebar.md @@ -7,7 +7,6 @@ * [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) diff --git a/pino.d.ts b/pino.d.ts index 2d88d3f24..fb6337783 100644 --- a/pino.d.ts +++ b/pino.d.ts @@ -13,7 +13,6 @@ // Michel Nemnom // Igor Savin // James Bromwell -// Zamiell // TypeScript Version: 4.4 import type { EventEmitter } from "events"; @@ -128,26 +127,6 @@ export interface LoggerExtras extends Event 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; -} - -/** 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 - ? [StringInterpolationLetterToType[A], ...ExtractArgs] - : ExtractArgs - : ExtractArgs - : [] declare namespace pino { //// Exported types and interfaces @@ -334,38 +313,11 @@ declare namespace pino { } interface LogFn { - // 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: number | boolean): void; + // 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; } interface LoggerOptions { diff --git a/test/types/pino-arguments.test-d.ts b/test/types/pino-arguments.test-d.ts deleted file mode 100644 index d6e52e1f8..000000000 --- a/test/types/pino-arguments.test-d.ts +++ /dev/null @@ -1,145 +0,0 @@ -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); diff --git a/test/types/pino.test-d.ts b/test/types/pino.test-d.ts index 3e9e63ba0..62fddc3e1 100644 --- a/test/types/pino.test-d.ts +++ b/test/types/pino.test-d.ts @@ -14,9 +14,7 @@ info("the answer is %d", 42); info({ obj: 42 }, "hello world"); 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 (msg: string) => void, "after setImmediate"); +setImmediate(info, "after setImmediate"); error(new Error("an error")); const writeSym = pino.symbols.writeSym; @@ -256,7 +254,7 @@ pino({ name: "my-logger" }, destinationViaOptionsObject); try { throw new Error('Some error') -} catch (err: any) { +} catch (err) { log.error(err) } @@ -265,9 +263,9 @@ interface StrictShape { err?: unknown; } -info({ +info({ activity: "Required property", -} satisfies StrictShape); +}); const logLine: pino.LogDescriptor = { level: 20, @@ -364,3 +362,19 @@ expectError( } }) ); + +function dangerous () { + throw Error('foo') +} + +try { + dangerous() +} catch (err) { + log.error(err) +} + +try { + dangerous() +} catch (err) { + log.error({ err }) +}