From 470df826d07b2511e9ee71305f0550f311758a58 Mon Sep 17 00:00:00 2001 From: Viktor Chernodub <37013688+chernodub@users.noreply.github.com> Date: Wed, 21 Aug 2024 16:51:50 +0300 Subject: [PATCH] feat(types): forbid using default log fn when custom only used (#1998) (#1999) * feat(types): forbid using default log fn when custom only used * test: move types test to tsd file * feat: allow overriding default levels --------- Co-authored-by: Matteo Collina --- pino.d.ts | 68 ++++++++++++++++++++++----------------- test/types/pino.test-d.ts | 27 ++++++++++++++-- test/types/pino.ts | 24 +++++++------- 3 files changed, 75 insertions(+), 44 deletions(-) diff --git a/pino.d.ts b/pino.d.ts index f8cad6f07..cdc5958d3 100644 --- a/pino.d.ts +++ b/pino.d.ts @@ -31,7 +31,21 @@ type TimeFn = () => string; type MixinFn = (mergeObject: object, level: number, logger:pino.Logger) => object; type MixinMergeStrategyFn = (mergeObject: object, mixinObject: object) => object; -type CustomLevelLogger = { [level in CustomLevels]: LogFn } +type CustomLevelLogger = { + /** + * Define additional logging levels. + */ + customLevels: { [level in CustomLevels]: number }; + /** + * Use only defined `customLevels` and omit Pino's levels. + */ + useOnlyCustomLevels: UseOnlyCustomLevels; + } & { + // This will override default log methods + [K in Exclude]: UseOnlyCustomLevels extends true ? never : pino.LogFn; + } & { + [level in CustomLevels]: pino.LogFn; + }; /** * A synchronous callback that will run on each creation of a new child. @@ -45,7 +59,7 @@ export interface redactOptions { remove?: boolean; } -export interface LoggerExtras extends EventEmitter { +export interface LoggerExtras extends EventEmitter { /** * Exposes the Pino package version. Also available on the exported pino function. */ @@ -57,14 +71,6 @@ export interface LoggerExtras extends Event * Outputs the level as a string instead of integer. */ useLevelLabels: boolean; - /** - * Define additional logging levels. - */ - customLevels: { [level in CustomLevels]: number }; - /** - * Use only defined `customLevels` and omit Pino's levels. - */ - useOnlyCustomLevels: boolean; /** * Returns the integer value for the logger instance's logging level. */ @@ -95,12 +101,12 @@ export interface LoggerExtras extends Event * @param event: only ever fires the `'level-change'` event * @param listener: The listener is passed four arguments: `levelLabel`, `levelValue`, `previousLevelLabel`, `previousLevelValue`. */ - on(event: "level-change", listener: pino.LevelChangeEventListener): this; - addListener(event: "level-change", listener: pino.LevelChangeEventListener): this; - once(event: "level-change", listener: pino.LevelChangeEventListener): this; - prependListener(event: "level-change", listener: pino.LevelChangeEventListener): this; - prependOnceListener(event: "level-change", listener: pino.LevelChangeEventListener): this; - removeListener(event: "level-change", listener: pino.LevelChangeEventListener): this; + on(event: "level-change", listener: pino.LevelChangeEventListener): this; + addListener(event: "level-change", listener: pino.LevelChangeEventListener): this; + once(event: "level-change", listener: pino.LevelChangeEventListener): this; + prependListener(event: "level-change", listener: pino.LevelChangeEventListener): this; + prependOnceListener(event: "level-change", listener: pino.LevelChangeEventListener): this; + removeListener(event: "level-change", listener: pino.LevelChangeEventListener): this; /** * A utility method for determining if a given log level will write to the destination. @@ -225,17 +231,17 @@ declare namespace pino { type SerializerFn = (value: any) => any; type WriteFn = (o: object) => void; - type LevelChangeEventListener = ( + type LevelChangeEventListener = ( lvl: LevelWithSilentOrString, val: number, prevLvl: LevelWithSilentOrString, prevVal: number, - logger: Logger + logger: Logger ) => void; type LogDescriptor = Record; - type Logger = BaseLogger & LoggerExtras & CustomLevelLogger; + type Logger = BaseLogger & LoggerExtras & CustomLevelLogger; type SerializedError = pinoStdSerializers.SerializedError; type SerializedResponse = pinoStdSerializers.SerializedResponse; @@ -321,7 +327,7 @@ declare namespace pino { (msg: string, ...args: any[]): void; } - interface LoggerOptions { + interface LoggerOptions { transport?: TransportSingleOptions | TransportMultiOptions | TransportPipelineOptions /** * Avoid error causes by circular references in the object tree. Default: `true`. @@ -355,18 +361,20 @@ declare namespace pino { * The keys of the object correspond the namespace of the log level, and the values should be the numerical value of the level. */ customLevels?: { [level in CustomLevels]: number }; + + /** + * Use this option to only use defined `customLevels` and omit Pino's levels. + * Logger's default `level` must be changed to a value in `customLevels` in order to use `useOnlyCustomLevels` + * Warning: this option may not be supported by downstream transports. + */ + useOnlyCustomLevels?: UseOnlyCustomLevels; + /** * Use this option to define custom comparison of log levels. * Useful to compare custom log levels or non-standard level values. * Default: "ASC" */ levelComparison?: "ASC" | "DESC" | ((current: number, expected: number) => boolean); - /** - * Use this option to only use defined `customLevels` and omit Pino's levels. - * Logger's default `level` must be changed to a value in `customLevels` in order to use `useOnlyCustomLevels` - * Warning: this option may not be supported by downstream transports. - */ - useOnlyCustomLevels?: boolean; /** * If provided, the `mixin` function is called each time one of the active logging methods @@ -809,7 +817,7 @@ declare namespace pino { * relative protocol is enabled. Default: process.stdout * @returns a new logger instance. */ -declare function pino(optionsOrStream?: LoggerOptions | DestinationStream): Logger; +declare function pino(optionsOrStream?: LoggerOptions | DestinationStream): Logger; /** * @param [options]: an options object @@ -817,7 +825,7 @@ declare function pino(optionsOrStream?: Log * relative protocol is enabled. Default: process.stdout * @returns a new logger instance. */ -declare function pino(options: LoggerOptions, stream?: DestinationStream | undefined): Logger; +declare function pino(options: LoggerOptions, stream?: DestinationStream | undefined): Logger; // Pass through all the top-level exports, allows `import {version} from "pino"` @@ -840,7 +848,7 @@ export type LevelWithSilent = pino.LevelWithSilent; export type LevelWithSilentOrString = pino.LevelWithSilentOrString; export type LevelChangeEventListener = pino.LevelChangeEventListener; export type LogDescriptor = pino.LogDescriptor; -export type Logger = pino.Logger; +export type Logger = pino.Logger; export type SerializedError = pino.SerializedError; export type SerializerFn = pino.SerializerFn; export type SerializedRequest = pino.SerializedRequest; @@ -854,7 +862,7 @@ export interface DestinationStream extends pino.DestinationStream {} export interface LevelMapping extends pino.LevelMapping {} export interface LogEvent extends pino.LogEvent {} export interface LogFn extends pino.LogFn {} -export interface LoggerOptions extends pino.LoggerOptions {} +export interface LoggerOptions extends pino.LoggerOptions {} export interface MultiStreamOptions extends pino.MultiStreamOptions {} export interface MultiStreamRes extends pino.MultiStreamRes {} export interface StreamEntry extends pino.StreamEntry {} diff --git a/test/types/pino.test-d.ts b/test/types/pino.test-d.ts index ece69cae9..02284ccbd 100644 --- a/test/types/pino.test-d.ts +++ b/test/types/pino.test-d.ts @@ -1,7 +1,7 @@ import { IncomingMessage, ServerResponse } from "http"; import { Socket } from "net"; import { expectError, expectType } from 'tsd'; -import P, { pino } from "../../"; +import P, { LoggerOptions, pino } from "../../"; import Logger = P.Logger; const log = pino(); @@ -438,4 +438,27 @@ expectError(pino({ levelComparison: 123}), process.stdout); // with wrong custom level comparison return type expectError(pino({ levelComparison: () => null }), process.stdout); expectError(pino({ levelComparison: () => 1 }), process.stdout); -expectError(pino({ levelComparison: () => 'string' }), process.stdout); \ No newline at end of file +expectError(pino({ levelComparison: () => 'string' }), process.stdout); + +const customLevelsOnlyOpts = { + useOnlyCustomLevels: true, + customLevels: { + customDebug: 10, + info: 20, // to make sure the default names are also available for override + customNetwork: 30, + customError: 40, + }, + level: 'customDebug', +} satisfies LoggerOptions; + +const loggerWithCustomLevelOnly = pino(customLevelsOnlyOpts); +loggerWithCustomLevelOnly.customDebug('test3') +loggerWithCustomLevelOnly.info('test4') +loggerWithCustomLevelOnly.customError('test5') +loggerWithCustomLevelOnly.customNetwork('test6') + +expectError(loggerWithCustomLevelOnly.fatal('test')); +expectError(loggerWithCustomLevelOnly.error('test')); +expectError(loggerWithCustomLevelOnly.warn('test')); +expectError(loggerWithCustomLevelOnly.debug('test')); +expectError(loggerWithCustomLevelOnly.trace('test')); diff --git a/test/types/pino.ts b/test/types/pino.ts index a167309ef..d0f37ac61 100644 --- a/test/types/pino.ts +++ b/test/types/pino.ts @@ -1,7 +1,7 @@ -import { StreamEntry, pino } from '../../pino' import { join } from 'node:path' import { tmpdir } from 'node:os' import pinoPretty from 'pino-pretty' +import { LoggerOptions, StreamEntry, pino } from '../../pino' const destination = join( tmpdir(), @@ -45,19 +45,19 @@ loggerMulti.info('test2') // custom levels const customLevels = { - debug : 1, + customDebug : 1, info : 2, - network : 3, - error : 4, + customNetwork : 3, + customError : 4, }; type CustomLevels = keyof typeof customLevels; const pinoOpts = { - level: 'debug', useOnlyCustomLevels: true, customLevels: customLevels, -}; + level: 'customDebug', +} satisfies LoggerOptions; const multistreamOpts = { dedupe: true, @@ -65,14 +65,14 @@ const multistreamOpts = { }; const streams: StreamEntry[] = [ - { level : 'debug', stream : pinoPretty() }, + { level : 'customDebug', stream : pinoPretty() }, { level : 'info', stream : pinoPretty() }, - { level : 'network', stream : pinoPretty() }, - { level : 'error', stream : pinoPretty() }, + { level : 'customNetwork', stream : pinoPretty() }, + { level : 'customError', stream : pinoPretty() }, ]; const loggerCustomLevel = pino(pinoOpts, pino.multistream(streams, multistreamOpts)); -loggerCustomLevel.debug('test3') +loggerCustomLevel.customDebug('test3') loggerCustomLevel.info('test4') -loggerCustomLevel.error('test5') -loggerCustomLevel.network('test6') +loggerCustomLevel.customError('test5') +loggerCustomLevel.customNetwork('test6')