From 5ab6e289c2eeb5869fd0fa3f90f7e83c3123308c Mon Sep 17 00:00:00 2001 From: Viktor Chernodub <37013688+chernodub@users.noreply.github.com> Date: Mon, 1 Jul 2024 17:20:41 +0000 Subject: [PATCH 1/3] feat(types): forbid using default log fn when custom only used --- pino.d.ts | 68 ++++++++++++++++++++++++++-------------------- test/types/pino.ts | 48 +++++++++++++++++++++----------- 2 files changed, 70 insertions(+), 46 deletions(-) diff --git a/pino.d.ts b/pino.d.ts index f8cad6f07..39a449cf4 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; + } & { + [level in CustomLevels]: pino.LogFn; + } & { + // This will override default log methods + [K in pino.Level]: UseOnlyCustomLevels extends true ? never : 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.ts b/test/types/pino.ts index c1cf8c0f6..6f7496d22 100644 --- a/test/types/pino.ts +++ b/test/types/pino.ts @@ -1,7 +1,7 @@ -import { StreamEntry, pino } from '../../pino' -import { join } from 'path' import { tmpdir } from 'os' +import { join } from 'path' 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, - info : 2, - network : 3, - error : 4, + customDebug : 1, + customInfo : 2, + 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,30 @@ const multistreamOpts = { }; const streams: StreamEntry[] = [ - { level : 'debug', stream : pinoPretty() }, - { level : 'info', stream : pinoPretty() }, - { level : 'network', stream : pinoPretty() }, - { level : 'error', stream : pinoPretty() }, + { level : 'customDebug', stream : pinoPretty() }, + { level : 'customInfo', stream : pinoPretty() }, + { level : 'customNetwork', stream : pinoPretty() }, + { level : 'customError', stream : pinoPretty() }, ]; const loggerCustomLevel = pino(pinoOpts, pino.multistream(streams, multistreamOpts)); -loggerCustomLevel.debug('test3') -loggerCustomLevel.info('test4') -loggerCustomLevel.error('test5') -loggerCustomLevel.network('test6') +loggerCustomLevel.customDebug('test3') +loggerCustomLevel.customInfo('test4') +loggerCustomLevel.customError('test5') +loggerCustomLevel.customNetwork('test6') + +try { + // @ts-expect-error + loggerCustomLevel.fatal('test'); + // @ts-expect-error + loggerCustomLevel.error('test'); + // @ts-expect-error + loggerCustomLevel.warn('test'); + // @ts-expect-error + loggerCustomLevel.info('test'); + // @ts-expect-error + loggerCustomLevel.debug('test'); + // @ts-expect-error + loggerCustomLevel.trace('test'); +} catch (e) { +} \ No newline at end of file From 898cceba7c99c23a2a28c1d549509a2c1c7cd384 Mon Sep 17 00:00:00 2001 From: Viktor Chernodub <37013688+chernodub@users.noreply.github.com> Date: Tue, 2 Jul 2024 13:52:20 +0000 Subject: [PATCH 2/3] test: move types test to tsd file --- test/types/pino.test-d.ts | 27 +++++++++++++++++++++++++-- test/types/pino.ts | 22 +++------------------- 2 files changed, 28 insertions(+), 21 deletions(-) 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 6f7496d22..8f17c1c0a 100644 --- a/test/types/pino.ts +++ b/test/types/pino.ts @@ -46,7 +46,7 @@ loggerMulti.info('test2') const customLevels = { customDebug : 1, - customInfo : 2, + info : 2, customNetwork : 3, customError : 4, }; @@ -66,29 +66,13 @@ const multistreamOpts = { const streams: StreamEntry[] = [ { level : 'customDebug', stream : pinoPretty() }, - { level : 'customInfo', stream : pinoPretty() }, + { level : 'info', stream : pinoPretty() }, { level : 'customNetwork', stream : pinoPretty() }, { level : 'customError', stream : pinoPretty() }, ]; const loggerCustomLevel = pino(pinoOpts, pino.multistream(streams, multistreamOpts)); loggerCustomLevel.customDebug('test3') -loggerCustomLevel.customInfo('test4') +loggerCustomLevel.info('test4') loggerCustomLevel.customError('test5') loggerCustomLevel.customNetwork('test6') - -try { - // @ts-expect-error - loggerCustomLevel.fatal('test'); - // @ts-expect-error - loggerCustomLevel.error('test'); - // @ts-expect-error - loggerCustomLevel.warn('test'); - // @ts-expect-error - loggerCustomLevel.info('test'); - // @ts-expect-error - loggerCustomLevel.debug('test'); - // @ts-expect-error - loggerCustomLevel.trace('test'); -} catch (e) { -} \ No newline at end of file From 44a6fd3fd4302b8d3928d7ff992be3e0c0533e60 Mon Sep 17 00:00:00 2001 From: Viktor Chernodub <37013688+chernodub@users.noreply.github.com> Date: Tue, 2 Jul 2024 13:58:51 +0000 Subject: [PATCH 3/3] feat: allow overriding default levels --- pino.d.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pino.d.ts b/pino.d.ts index 39a449cf4..cdc5958d3 100644 --- a/pino.d.ts +++ b/pino.d.ts @@ -40,11 +40,11 @@ type CustomLevelLogger]: UseOnlyCustomLevels extends true ? never : pino.LogFn; + } & { + [level in CustomLevels]: pino.LogFn; }; /**