Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(types): forbid using default log fn when custom only used (#1998) #1999

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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')