Skip to content

Commit

Permalink
feat(types): forbid using default log fn when custom only used (#1998) (
Browse files Browse the repository at this point in the history
#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 <hello@matteocollina.com>
  • Loading branch information
chernodub and mcollina authored Aug 21, 2024
1 parent 6afd186 commit 470df82
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 44 deletions.
68 changes: 38 additions & 30 deletions pino.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,21 @@ type TimeFn = () => string;
type MixinFn<CustomLevels extends string = never> = (mergeObject: object, level: number, logger:pino.Logger<CustomLevels>) => object;
type MixinMergeStrategyFn = (mergeObject: object, mixinObject: object) => object;

type CustomLevelLogger<CustomLevels extends string> = { [level in CustomLevels]: LogFn }
type CustomLevelLogger<CustomLevels extends string, UseOnlyCustomLevels extends boolean = boolean> = {
/**
* 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<pino.Level, CustomLevels>]: UseOnlyCustomLevels extends true ? never : pino.LogFn;
} & {
[level in CustomLevels]: pino.LogFn;
};

/**
* A synchronous callback that will run on each creation of a new child.
Expand All @@ -45,7 +59,7 @@ export interface redactOptions {
remove?: boolean;
}

export interface LoggerExtras<CustomLevels extends string = never> extends EventEmitter {
export interface LoggerExtras<CustomLevels extends string = never, UseOnlyCustomLevels extends boolean = boolean> extends EventEmitter {
/**
* Exposes the Pino package version. Also available on the exported pino function.
*/
Expand All @@ -57,14 +71,6 @@ export interface LoggerExtras<CustomLevels extends string = never> 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.
*/
Expand Down Expand Up @@ -95,12 +101,12 @@ export interface LoggerExtras<CustomLevels extends string = never> 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<CustomLevels>): this;
addListener(event: "level-change", listener: pino.LevelChangeEventListener<CustomLevels>): this;
once(event: "level-change", listener: pino.LevelChangeEventListener<CustomLevels>): this;
prependListener(event: "level-change", listener: pino.LevelChangeEventListener<CustomLevels>): this;
prependOnceListener(event: "level-change", listener: pino.LevelChangeEventListener<CustomLevels>): this;
removeListener(event: "level-change", listener: pino.LevelChangeEventListener<CustomLevels>): this;
on(event: "level-change", listener: pino.LevelChangeEventListener<CustomLevels, UseOnlyCustomLevels>): this;
addListener(event: "level-change", listener: pino.LevelChangeEventListener<CustomLevels, UseOnlyCustomLevels>): this;
once(event: "level-change", listener: pino.LevelChangeEventListener<CustomLevels, UseOnlyCustomLevels>): this;
prependListener(event: "level-change", listener: pino.LevelChangeEventListener<CustomLevels, UseOnlyCustomLevels>): this;
prependOnceListener(event: "level-change", listener: pino.LevelChangeEventListener<CustomLevels, UseOnlyCustomLevels>): this;
removeListener(event: "level-change", listener: pino.LevelChangeEventListener<CustomLevels, UseOnlyCustomLevels>): this;

/**
* A utility method for determining if a given log level will write to the destination.
Expand Down Expand Up @@ -225,17 +231,17 @@ declare namespace pino {
type SerializerFn = (value: any) => any;
type WriteFn = (o: object) => void;

type LevelChangeEventListener<CustomLevels extends string = never> = (
type LevelChangeEventListener<CustomLevels extends string = never, UseOnlyCustomLevels extends boolean = boolean> = (
lvl: LevelWithSilentOrString,
val: number,
prevLvl: LevelWithSilentOrString,
prevVal: number,
logger: Logger<CustomLevels>
logger: Logger<CustomLevels, UseOnlyCustomLevels>
) => void;

type LogDescriptor = Record<string, any>;

type Logger<CustomLevels extends string = never> = BaseLogger & LoggerExtras<CustomLevels> & CustomLevelLogger<CustomLevels>;
type Logger<CustomLevels extends string = never, UseOnlyCustomLevels extends boolean = boolean> = BaseLogger & LoggerExtras<CustomLevels> & CustomLevelLogger<CustomLevels, UseOnlyCustomLevels>;

type SerializedError = pinoStdSerializers.SerializedError;
type SerializedResponse = pinoStdSerializers.SerializedResponse;
Expand Down Expand Up @@ -321,7 +327,7 @@ declare namespace pino {
(msg: string, ...args: any[]): void;
}

interface LoggerOptions<CustomLevels extends string = never> {
interface LoggerOptions<CustomLevels extends string = never, UseOnlyCustomLevels extends boolean = boolean> {
transport?: TransportSingleOptions | TransportMultiOptions | TransportPipelineOptions
/**
* Avoid error causes by circular references in the object tree. Default: `true`.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -809,15 +817,15 @@ declare namespace pino {
* relative protocol is enabled. Default: process.stdout
* @returns a new logger instance.
*/
declare function pino<CustomLevels extends string = never>(optionsOrStream?: LoggerOptions<CustomLevels> | DestinationStream): Logger<CustomLevels>;
declare function pino<CustomLevels extends string = never, UseOnlyCustomLevels extends boolean = boolean>(optionsOrStream?: LoggerOptions<CustomLevels, UseOnlyCustomLevels> | DestinationStream): Logger<CustomLevels, UseOnlyCustomLevels>;

/**
* @param [options]: an options object
* @param [stream]: a writable stream where the logs will be written. It can also receive some log-line metadata, if the
* relative protocol is enabled. Default: process.stdout
* @returns a new logger instance.
*/
declare function pino<CustomLevels extends string = never>(options: LoggerOptions<CustomLevels>, stream?: DestinationStream | undefined): Logger<CustomLevels>;
declare function pino<CustomLevels extends string = never, UseOnlyCustomLevels extends boolean = boolean>(options: LoggerOptions<CustomLevels, UseOnlyCustomLevels>, stream?: DestinationStream | undefined): Logger<CustomLevels, UseOnlyCustomLevels>;


// Pass through all the top-level exports, allows `import {version} from "pino"`
Expand All @@ -840,7 +848,7 @@ export type LevelWithSilent = pino.LevelWithSilent;
export type LevelWithSilentOrString = pino.LevelWithSilentOrString;
export type LevelChangeEventListener<CustomLevels extends string> = pino.LevelChangeEventListener<CustomLevels>;
export type LogDescriptor = pino.LogDescriptor;
export type Logger<CustomLevels extends string = never> = pino.Logger<CustomLevels>;
export type Logger<CustomLevels extends string = never, UseOnlyCustomLevels extends boolean = boolean> = pino.Logger<CustomLevels, UseOnlyCustomLevels>;
export type SerializedError = pino.SerializedError;
export type SerializerFn = pino.SerializerFn;
export type SerializedRequest = pino.SerializedRequest;
Expand All @@ -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<CustomLevels extends string = never> extends pino.LoggerOptions<CustomLevels> {}
export interface LoggerOptions<CustomLevels extends string = never, UseOnlyCustomLevels extends boolean = boolean> extends pino.LoggerOptions<CustomLevels, UseOnlyCustomLevels> {}
export interface MultiStreamOptions extends pino.MultiStreamOptions {}
export interface MultiStreamRes<TLevel = Level> extends pino.MultiStreamRes<TLevel> {}
export interface StreamEntry<TLevel = Level> extends pino.StreamEntry<TLevel> {}
Expand Down
27 changes: 25 additions & 2 deletions test/types/pino.test-d.ts
Original file line number Diff line number Diff line change
@@ -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();
Expand Down Expand Up @@ -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);
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'));
24 changes: 12 additions & 12 deletions test/types/pino.ts
Original file line number Diff line number Diff line change
@@ -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(),
Expand Down Expand Up @@ -45,34 +45,34 @@ 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,
levels: customLevels
};

const streams: StreamEntry<CustomLevels>[] = [
{ 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')

0 comments on commit 470df82

Please sign in to comment.