From dfca887bcdf713e740e498ae3381c5f858fc39bc Mon Sep 17 00:00:00 2001 From: Igor Savin Date: Fri, 28 May 2021 09:34:26 +0300 Subject: [PATCH] Add TS types (#913) --- .eslintignore | 2 + package.json | 18 +- pino.d.ts | 876 +++++++++++++++++++++++++++++++ test/types/pino-import.test-d.ts | 29 + test/types/pino.test-d.ts | 267 ++++++++++ tsconfig.json | 13 + 6 files changed, 1201 insertions(+), 4 deletions(-) create mode 100644 .eslintignore create mode 100644 pino.d.ts create mode 100644 test/types/pino-import.test-d.ts create mode 100644 test/types/pino.test-d.ts create mode 100644 tsconfig.json diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 000000000..d058c83f1 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +pino.d.ts +test/types/pino.test-d.ts diff --git a/package.json b/package.json index 69ea52020..7cb76f558 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,12 @@ "version": "6.11.3", "description": "super fast, all natural json logger", "main": "pino.js", + "type": "commonjs", + "types": "pino.d.ts", "browser": "./browser.js", "files": [ "pino.js", + "pino.d.ts", "bin.js", "browser.js", "pretty.js", @@ -19,8 +22,9 @@ "docs": "docsify serve", "browser-test": "airtap --local 8080 test/browser*test.js", "lint": "eslint .", - "test": "npm run lint && tap --100 test/*test.js test/*/*test.js", + "test": "npm run lint && tap --100 test/*test.js test/*/*test.js && npm run test:types", "test-ci": "npm run lint && tap test/*test.js test/*/*test.js --coverage-report=lcovonly", + "test-types": "tsc && tsd", "cov-ui": "tap --coverage-report=html test/*test.js test/*/*test.js", "bench": "node benchmarks/utils/runbench all", "bench-basic": "node benchmarks/utils/runbench basic", @@ -60,6 +64,7 @@ }, "homepage": "http://getpino.io", "devDependencies": { + "@types/node": "^15.3.0", "airtap": "4.0.3", "benchmark": "^2.1.4", "bole": "^4.0.0", @@ -76,7 +81,7 @@ "import-fresh": "^3.2.1", "log": "^6.0.0", "loglevel": "^1.6.7", - "pino-pretty": "^4.1.0", + "pino-pretty": "^5.0.0", "pre-commit": "^1.2.2", "proxyquire": "^2.1.3", "pump": "^3.0.0", @@ -87,13 +92,18 @@ "tap": "^15.0.1", "tape": "^5.0.0", "through2": "^4.0.0", + "tsd": "^0.15.1", + "typescript": "^4.2.4", "winston": "^3.3.3" }, "dependencies": { "fast-redact": "^3.0.0", "fast-safe-stringify": "^2.0.7", - "pino-std-serializers": "^3.1.0", + "pino-std-serializers": "^4.0.0", "quick-format-unescaped": "^4.0.3", - "sonic-boom": "^1.0.2" + "sonic-boom": "^2.0.1" + }, + "tsd": { + "directory": "test/types" } } diff --git a/pino.d.ts b/pino.d.ts new file mode 100644 index 000000000..4f1c4fa58 --- /dev/null +++ b/pino.d.ts @@ -0,0 +1,876 @@ +// Type definitions for pino 6.3 +// Project: https://github.com/pinojs/pino.git, http://getpino.io +// Definitions by: Peter Snider +// BendingBender +// Christian Rackerseder +// GP +// Alex Ferrando +// Oleksandr Sidko +// Harris Lummis +// Raoul Jaeckel +// Cory Donkin +// Adam Vigneaux +// Austin Beer +// Michel Nemnom +// Igor Savin +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// TypeScript Version: 3.0 + +/// + +import { EventEmitter } from "events"; +import { SonicBoom } from "sonic-boom"; +import * as pinoStdSerializers from "pino-std-serializers"; +import {WriteStream} from "fs"; + +export default P; +export { P as pino } +export type { P } + +type LogDescriptor = Record; +type MessageFormatFunc = (log: LogDescriptor, messageKey: string, levelLabel: string) => string; + +/** + * @param [optionsOrStream]: an options object or 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 P(optionsOrStream?: P.LoggerOptions | P.DestinationStream): P.Logger; + +/** + * @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 P(options: P.LoggerOptions, stream: P.DestinationStream): P.Logger; + +declare namespace P { + /** + * Holds the current log format version (as output in the v property of each log record). + */ + const LOG_VERSION: number; + const levels: LevelMapping; + const symbols: { + readonly setLevelSym: unique symbol; + readonly getLevelSym: unique symbol; + readonly levelValSym: unique symbol; + readonly useLevelLabelsSym: unique symbol; + readonly mixinSym: unique symbol; + readonly lsCacheSym: unique symbol; + readonly chindingsSym: unique symbol; + readonly parsedChindingsSym: unique symbol; + readonly asJsonSym: unique symbol; + readonly writeSym: unique symbol; + readonly serializersSym: unique symbol; + readonly redactFmtSym: unique symbol; + readonly timeSym: unique symbol; + readonly timeSliceIndexSym: unique symbol; + readonly streamSym: unique symbol; + readonly stringifySym: unique symbol; + readonly stringifiersSym: unique symbol; + readonly endSym: unique symbol; + readonly formatOptsSym: unique symbol; + readonly messageKeySym: unique symbol; + readonly nestedKeySym: unique symbol; + readonly wildcardFirstSym: unique symbol; + readonly needsMetadataGsym: unique symbol; + readonly useOnlyCustomLevelsSym: unique symbol; + readonly formattersSym: unique symbol; + readonly hooksSym: unique symbol; + }; + /** + * Exposes the Pino package version. Also available on the logger instance. + */ + const version: string; + + type SerializedError = pinoStdSerializers.SerializedError; + type SerializedResponse = pinoStdSerializers.SerializedResponse; + type SerializedRequest = pinoStdSerializers.SerializedRequest; + + /** + * Provides functions for serializing objects common to many projects. + */ + const stdSerializers: { + /** + * Generates a JSONifiable object from the HTTP `request` object passed to the `createServer` callback of Node's HTTP server. + */ + req: typeof pinoStdSerializers.req; + /** + * Generates a JSONifiable object from the HTTP `response` object passed to the `createServer` callback of Node's HTTP server. + */ + res: typeof pinoStdSerializers.res; + /** + * Serializes an Error object. + */ + err: typeof pinoStdSerializers.err; + /** + * Returns an object: + * ``` + * { + * req: {} + * } + * ``` + * where req is the request as serialized by the standard request serializer. + * @param req The request to serialize + * @return An object + */ + mapHttpRequest: typeof pinoStdSerializers.mapHttpRequest; + /** + * Returns an object: + * ``` + * { + * res: {} + * } + * ``` + * where res is the response as serialized by the standard response serializer. + * @param res The response to serialize. + * @return An object. + */ + mapHttpResponse: typeof pinoStdSerializers.mapHttpResponse; + /** + * A utility method for wrapping the default error serializer. Allows custom serializers to work with the + * already serialized object. + * @param customSerializer The custom error serializer. Accepts a single parameter: the newly serialized + * error object. Returns the new (or updated) error object. + * @return A new error serializer. + */ + wrapErrorSerializer: typeof pinoStdSerializers.wrapErrorSerializer; + /** + * A utility method for wrapping the default request serializer. Allows custom serializers to work with the + * already serialized object. + * @param customSerializer The custom request serializer. Accepts a single parameter: the newly serialized + * request object. Returns the new (or updated) request object. + * @return A new error serializer. + */ + wrapRequestSerializer: typeof pinoStdSerializers.wrapRequestSerializer; + /** + * A utility method for wrapping the default response serializer. Allows custom serializers to work with the + * already serialized object. + * @param customSerializer The custom response serializer. Accepts a single parameter: the newly serialized + * response object. Returns the new (or updated) response object. + * @return A new error serializer. + */ + wrapResponseSerializer: typeof pinoStdSerializers.wrapResponseSerializer; + }; + /** + * Provides functions for generating the timestamp property in the log output. You can set the `timestamp` option during + * initialization to one of these functions to adjust the output format. Alternatively, you can specify your own time function. + * A time function must synchronously return a string that would be a valid component of a JSON string. For example, + * the default function returns a string like `,"time":1493426328206`. + */ + const stdTimeFunctions: { + /** + * The default time function for Pino. Returns a string like `,"time":1493426328206`. + */ + epochTime: TimeFn; + /* + * Returns the seconds since Unix epoch + */ + unixTime: TimeFn; + /** + * Returns an empty string. This function is used when the `timestamp` option is set to `false`. + */ + nullTime: TimeFn; + /* + * Returns ISO 8601-formatted time in UTC + */ + isoTime: TimeFn; + }; + + /** + * Equivalent of SonicBoom constructor options object + */ + // TODO: use SonicBoom constructor options interface when available + interface DestinationObjectOptions { + fd?: string | number; + dest?: string; + minLength?: number; + sync?: boolean; + } + + /** + * Create a Pino Destination instance: a stream-like object with significantly more throughput (over 30%) than a standard Node.js stream. + * @param [dest]: The `destination` parameter, at a minimum must be an object with a `write` method. An ordinary Node.js + * `stream` can be passed as the destination (such as the result of `fs.createWriteStream`) but for peak log + * writing performance it is strongly recommended to use `pino.destination` to create the destination stream. + * @returns A Sonic-Boom stream to be used as destination for the pino function + */ + function destination( + dest?: string | number | DestinationObjectOptions | DestinationStream | NodeJS.WritableStream, + ): SonicBoom; + + interface MultiStreamOptions { + levels?: Record + dedupe?: boolean + } + + interface StreamEntry { + stream: WriteStream + level: Level + } + + interface MultiStreamRes { + write: (data: any) => void, + add: (dest: Record) => MultiStreamRes, + flushSync: () => void, + minLevel: number, + streams: WriteStream[], + clone(level: Level): MultiStreamRes, + } + + function multistream( + streamsArray: StreamEntry[], opts: P.MultiStreamOptions + ): MultiStreamRes + + /** + * The pino.final method can be used to create an exit listener function. + * This listener function can be supplied to process exit events. + * The exit listener function will call the handler with + * @param [logger]: pino logger that serves as reference for the final logger + * @param [handler]: Function that will be called by the handler returned from this function + * @returns Exit listener function that can be supplied to process exit events and will call the supplied handler function + */ + function final( + logger: Logger, + handler: (error: Error, finalLogger: Logger, ...args: any[]) => void, + ): (error: Error | null, ...args: any[]) => void; + + /** + * The pino.final method can be used to acquire a final logger instance that synchronously flushes on every write. + * @param [logger]: pino logger that serves as reference for the final logger + * @returns Final, synchronous logger + */ + function final(logger: Logger): Logger; + + interface LevelMapping { + /** + * Returns the mappings of level names to their respective internal number representation. + */ + values: { [level: string]: number }; + /** + * Returns the mappings of level internal level numbers to their string representations. + */ + labels: { [level: number]: string }; + } + type TimeFn = () => string; + type MixinFn = () => object; + + interface DestinationStream { + write(msg: string): void; + } + + interface LoggerOptions { + /** + * Avoid error causes by circular references in the object tree. Default: `true`. + */ + safe?: boolean; + /** + * The name of the logger. Default: `undefined`. + */ + name?: string; + /** + * an object containing functions for custom serialization of objects. + * These functions should return an JSONifiable object and they should never throw. When logging an object, + * each top-level property matching the exact key of a serializer will be serialized using the defined serializer. + */ + serializers?: { [key: string]: SerializerFn }; + /** + * Enables or disables the inclusion of a timestamp in the log message. If a function is supplied, it must + * synchronously return a JSON string representation of the time. If set to `false`, no timestamp will be included in the output. + * See stdTimeFunctions for a set of available functions for passing in as a value for this option. + * Caution: any sort of formatted time will significantly slow down Pino's performance. + */ + timestamp?: TimeFn | boolean; + /** + * One of the supported levels or `silent` to disable logging. Any other value defines a custom level and + * requires supplying a level value via `levelVal`. Default: 'info'. + */ + level?: LevelWithSilent | string; + /** + * Outputs the level as a string instead of integer. Default: `false`. + */ + useLevelLabels?: boolean; + /** + * Changes the property `level` to any string value you pass in. Default: 'level' + */ + levelKey?: string; + /** + * (DEPRECATED, use `levelKey`) Changes the property `level` to any string value you pass in. Default: 'level' + */ + changeLevelName?: string; + /** + * Use this option to define additional logging levels. + * The keys of the object correspond the namespace of the log level, and the values should be the numerical value of the level. + */ + customLevels?: { [key: string]: 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?: boolean; + + /** + * If provided, the `mixin` function is called each time one of the active logging methods + * is called. The function must synchronously return an object. The properties of the + * returned object will be added to the logged JSON. + */ + mixin?: MixinFn; + + /** + * As an array, the redact option specifies paths that should have their values redacted from any log output. + * + * Each path must be a string using a syntax which corresponds to JavaScript dot and bracket notation. + * + * If an object is supplied, three options can be specified: + * + * paths (String[]): Required. An array of paths + * censor (String): Optional. A value to overwrite key which are to be redacted. Default: '[Redacted]' + * remove (Boolean): Optional. Instead of censoring the value, remove both the key and the value. Default: false + */ + redact?: string[] | redactOptions; + + /** + * When defining a custom log level via level, set to an integer value to define the new level. Default: `undefined`. + */ + levelVal?: number; + /** + * The string key for the 'message' in the JSON object. Default: "msg". + */ + messageKey?: string; + /** + * The string key to place any logged object under. + */ + nestedKey?: string; + /** + * Enables pino.pretty. This is intended for non-production configurations. This may be set to a configuration + * object as outlined in http://getpino.io/#/docs/API?id=pretty. Default: `false`. + */ + prettyPrint?: boolean | PrettyOptions; + /** + * Allows to optionally define which prettifier module to use. + */ + // TODO: use type definitions from 'pino-pretty' when available. + prettifier?: any; + /** + * Enables logging. Default: `true`. + */ + enabled?: boolean; + /** + * Browser only, see http://getpino.io/#/docs/browser. + */ + browser?: { + /** + * The `asObject` option will create a pino-like log object instead of passing all arguments to a console + * method. When `write` is set, `asObject` will always be true. + * + * @example + * pino.info('hi') // creates and logs {msg: 'hi', level: 30, time: } + */ + asObject?: boolean; + /** + * Instead of passing log messages to `console.log` they can be passed to a supplied function. If `write` is + * set to a single function, all logging objects are passed to this function. If `write` is an object, it + * can have methods that correspond to the levels. When a message is logged at a given level, the + * corresponding method is called. If a method isn't present, the logging falls back to using the `console`. + * + * @example + * const pino = require('pino')({ + * browser: { + * write: (o) => { + * // do something with o + * } + * } + * }) + * + * @example + * const pino = require('pino')({ + * browser: { + * write: { + * info: function (o) { + * //process info log object + * }, + * error: function (o) { + * //process error log object + * } + * } + * } + * }) + */ + write?: + | WriteFn + | ({ + fatal?: WriteFn; + error?: WriteFn; + warn?: WriteFn; + info?: WriteFn; + debug?: WriteFn; + trace?: WriteFn; + } & { [logLevel: string]: WriteFn }); + + /** + * The serializers provided to `pino` are ignored by default in the browser, including the standard + * serializers provided with Pino. Since the default destination for log messages is the console, values + * such as `Error` objects are enhanced for inspection, which they otherwise wouldn't be if the Error + * serializer was enabled. We can turn all serializers on or we can selectively enable them via an array. + * + * When `serialize` is `true` the standard error serializer is also enabled (see + * {@link https://github.com/pinojs/pino/blob/master/docs/api.md#pino-stdserializers}). This is a global + * serializer which will apply to any `Error` objects passed to the logger methods. + * + * If `serialize` is an array the standard error serializer is also automatically enabled, it can be + * explicitly disabled by including a string in the serialize array: `!stdSerializers.err` (see example). + * + * The `serialize` array also applies to any child logger serializers (see + * {@link https://github.com/pinojs/pino/blob/master/docs/api.md#bindingsserializers-object} for how to + * set child-bound serializers). + * + * Unlike server pino the serializers apply to every object passed to the logger method, if the `asObject` + * option is `true`, this results in the serializers applying to the first object (as in server pino). + * + * For more info on serializers see + * {@link https://github.com/pinojs/pino/blob/master/docs/api.md#serializers-object}. + * + * @example + * const pino = require('pino')({ + * browser: { + * serialize: true + * } + * }) + * + * @example + * const pino = require('pino')({ + * serializers: { + * custom: myCustomSerializer, + * another: anotherSerializer + * }, + * browser: { + * serialize: ['custom'] + * } + * }) + * // following will apply myCustomSerializer to the custom property, + * // but will not apply anotherSerializer to another key + * pino.info({custom: 'a', another: 'b'}) + * + * @example + * const pino = require('pino')({ + * serializers: { + * custom: myCustomSerializer, + * another: anotherSerializer + * }, + * browser: { + * serialize: ['!stdSerializers.err', 'custom'] //will not serialize Errors, will serialize `custom` keys + * } + * }) + */ + serialize?: boolean | string[]; + + /** + * Options for transmission of logs. + * + * @example + * const pino = require('pino')({ + * browser: { + * transmit: { + * level: 'warn', + * send: function (level, logEvent) { + * if (level === 'warn') { + * // maybe send the logEvent to a separate endpoint + * // or maybe analyse the messages further before sending + * } + * // we could also use the `logEvent.level.value` property to determine + * // numerical value + * if (logEvent.level.value >= 50) { // covers error and fatal + * + * // send the logEvent somewhere + * } + * } + * } + * } + * }) + */ + transmit?: { + /** + * Specifies the minimum level (inclusive) of when the `send` function should be called, if not supplied + * the `send` function will be called based on the main logging `level` (set via `options.level`, + * defaulting to `info`). + */ + level?: Level | string; + /** + * Remotely record log messages. + * + * @description Called after writing the log message. + */ + send: (level: Level, logEvent: LogEvent) => void; + }; + }; + /** + * key-value object added as child logger to each log line. If set to null the base child logger is not added + */ + base?: { [key: string]: any } | null; + + /** + * An object containing functions for formatting the shape of the log lines. + * These functions should return a JSONifiable object and should never throw. + * These functions allow for full customization of the resulting log lines. + * For example, they can be used to change the level key name or to enrich the default metadata. + */ + formatters?: { + /** + * Changes the shape of the log level. + * The default shape is { level: number }. + * The function takes two arguments, the label of the level (e.g. 'info') and the numeric value (e.g. 30). + */ + level?: (label: string, number: number) => object; + /** + * Changes the shape of the bindings. + * The default shape is { pid, hostname }. + * The function takes a single argument, the bindings object. + * It will be called every time a child logger is created. + */ + bindings?: (bindings: Bindings) => object; + /** + * Changes the shape of the log object. + * This function will be called every time one of the log methods (such as .info) is called. + * All arguments passed to the log method, except the message, will be pass to this function. + * By default it does not change the shape of the log object. + */ + log?: (object: object) => object; + }; + + /** + * An object mapping to hook functions. Hook functions allow for customizing internal logger operations. + * Hook functions must be synchronous functions. + */ + hooks?: { + /** + * Allows for manipulating the parameters passed to logger methods. The signature for this hook is + * logMethod (args, method, level) {}, where args is an array of the arguments that were passed to the + * log method and method is the log method itself, and level is the log level. This hook must invoke the method function by + * using apply, like so: method.apply(this, newArgumentsArray). + */ + logMethod?: (args: any[], method: LogFn, level: number) => void; + }; + } + + // Copied from "pino-pretty" types + type PrettyOptions = { + /** + * Hide objects from output (but not error object). + * @default false + */ + hideObject?: boolean; + /** + * Translate the epoch time value into a human readable date and time string. This flag also can set the format + * string to apply when translating the date to human readable format. For a list of available pattern letters + * see the {@link https://www.npmjs.com/package/dateformat|dateformat documentation}. + * - The default format is `yyyy-mm-dd HH:MM:ss.l o` in UTC. + * - Requires a `SYS:` prefix to translate time to the local system's timezone. Use the shortcut `SYS:standard` + * to translate time to `yyyy-mm-dd HH:MM:ss.l o` in system timezone. + * @default false + */ + translateTime?: boolean | string; + /** + * If set to true, it will print the name of the log level as the first field in the log line. + * @default false + */ + levelFirst?: boolean; + /** + * Define the key that contains the level of the log. + * @default "level" + */ + levelKey?: string; + /** + * Output the log level using the specified label. + * @default "levelLabel" + */ + levelLabel?: string; + /** + * The key in the JSON object to use as the highlighted message. + * @default "msg" + */ + messageKey?: string; + /** + * Print each log message on a single line (errors will still be multi-line). + * @default false + */ + singleLine?: boolean; + /** + * The key in the JSON object to use for timestamp display. + * @default "time" + */ + timestampKey?: string; + /** + * Format output of message, e.g. {level} - {pid} will output message: INFO - 1123 + * @default false + * + * @example + * ```typescript + * { + * messageFormat: (log, messageKey) => { + * const message = log[messageKey]; + * if (log.requestId) return `[${log.requestId}] ${message}`; + * return message; + * } + * } + * ``` + */ + messageFormat?: false | string | MessageFormatFunc; + /** + * If set to true, will add color information to the formatted output message. + * @default false + */ + colorize?: boolean; + /** + * Appends carriage return and line feed, instead of just a line feed, to the formatted log line. + * @default false + */ + crlf?: boolean; + /** + * Define the log keys that are associated with error like objects. + * @default ["err", "error"] + */ + errorLikeObjectKeys?: string[]; + /** + * When formatting an error object, display this list of properties. + * The list should be a comma separated list of properties. + * @default "" + */ + errorProps?: string; + /** + * Specify a search pattern according to {@link http://jmespath.org|jmespath} + */ + search?: string; + /** + * Ignore one or several keys. + * @example "time,hostname" + */ + ignore?: string; + } + + type Level = "fatal" | "error" | "warn" | "info" | "debug" | "trace"; + type LevelWithSilent = Level | "silent"; + + type SerializerFn = (value: any) => any; + type WriteFn = (o: object) => void; + + /** + * Describes a log line. + */ + type LogDescriptor = Record; // TODO replace `any` with `unknown` when TypeScript version >= 3.0 + + interface Bindings { + level?: Level | string; + serializers?: { [key: string]: SerializerFn }; + [key: string]: any; + } + + /** + * A data structure representing a log message, it represents the arguments passed to a logger statement, the level + * at which they were logged and the hierarchy of child bindings. + * + * @description By default serializers are not applied to log output in the browser, but they will always be applied + * to `messages` and `bindings` in the `logEvent` object. This allows us to ensure a consistent format for all + * values between server and client. + */ + interface LogEvent { + /** + * Unix epoch timestamp in milliseconds, the time is taken from the moment the logger method is called. + */ + ts: number; + /** + * All arguments passed to logger method, (for instance `logger.info('a', 'b', 'c')` would result in `messages` + * array `['a', 'b', 'c']`). + */ + messages: any[]; + /** + * Represents each child logger (if any), and the relevant bindings. + * + * @description For instance, given `logger.child({a: 1}).child({b: 2}).info({c: 3})`, the bindings array would + * hold `[{a: 1}, {b: 2}]` and the `messages` array would be `[{c: 3}]`. The `bindings` are ordered according to + * their position in the child logger hierarchy, with the lowest index being the top of the hierarchy. + */ + bindings: Bindings[]; + /** + * Holds the `label` (for instance `info`), and the corresponding numerical `value` (for instance `30`). + * This could be important in cases where client side level values and labels differ from server side. + */ + level: { + label: string; + value: number; + }; + } + + type Logger = BaseLogger & { [key: string]: LogFn }; + + interface BaseLogger extends EventEmitter { + /** + * Exposes the current version of Pino. + */ + readonly pino: string; + /** + * Holds the current log format version (as output in the v property of each log record). + */ + readonly LOG_VERSION: number; + /** + * Exposes the Pino package version. Also available on the exported pino function. + */ + readonly version: string; + + levels: LevelMapping; + + /** + * Set this property to the desired logging level. In order of priority, available levels are: + * + * - 'fatal' + * - 'error' + * - 'warn' + * - 'info' + * - 'debug' + * - 'trace' + * + * The logging level is a __minimum__ level. For instance if `logger.level` is `'info'` then all `'fatal'`, `'error'`, `'warn'`, + * and `'info'` logs will be enabled. + * + * You can pass `'silent'` to disable logging. + */ + level: LevelWithSilent | string; + /** + * Outputs the level as a string instead of integer. + */ + useLevelLabels: boolean; + /** + * Define additional logging levels. + */ + customLevels: { [key: string]: number }; + /** + * Use only defined `customLevels` and omit Pino's levels. + */ + useOnlyCustomLevels: boolean; + /** + * Returns the integer value for the logger instance's logging level. + */ + levelVal: number; + + /** + * Registers a listener function that is triggered when the level is changed. + * Note: When browserified, this functionality will only be available if the `events` module has been required elsewhere + * (e.g. if you're using streams in the browser). This allows for a trade-off between bundle size and functionality. + * + * @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: LevelChangeEventListener): this; + addListener(event: "level-change", listener: LevelChangeEventListener): this; + once(event: "level-change", listener: LevelChangeEventListener): this; + prependListener(event: "level-change", listener: LevelChangeEventListener): this; + prependOnceListener(event: "level-change", listener: LevelChangeEventListener): this; + removeListener(event: "level-change", listener: LevelChangeEventListener): this; + + /** + * Creates a child logger, setting all key-value pairs in `bindings` as properties in the log lines. All serializers will be applied to the given pair. + * Child loggers use the same output stream as the parent and inherit the current log level of the parent at the time they are spawned. + * From v2.x.x the log level of a child is mutable (whereas in v1.x.x it was immutable), and can be set independently of the parent. + * If a `level` property is present in the object passed to `child` it will override the child logger level. + * + * @param bindings: an object of key-value pairs to include in log lines as properties. + * @returns a child logger instance. + */ + child(bindings: Bindings): Logger; + + /** + * Log at `'fatal'` level the given msg. If the first argument is an object, all its properties will be included in the JSON line. + * If more args follows `msg`, these will be used to format `msg` using `util.format`. + * + * @typeParam T: the interface of the object being serialized. Default is object. + * @param obj: object to be serialized + * @param msg: the log message to write + * @param ...args: format string values when `msg` is a format string + */ + fatal: LogFn; + /** + * Log at `'error'` level the given msg. If the first argument is an object, all its properties will be included in the JSON line. + * If more args follows `msg`, these will be used to format `msg` using `util.format`. + * + * @typeParam T: the interface of the object being serialized. Default is object. + * @param obj: object to be serialized + * @param msg: the log message to write + * @param ...args: format string values when `msg` is a format string + */ + error: LogFn; + /** + * Log at `'warn'` level the given msg. If the first argument is an object, all its properties will be included in the JSON line. + * If more args follows `msg`, these will be used to format `msg` using `util.format`. + * + * @typeParam T: the interface of the object being serialized. Default is object. + * @param obj: object to be serialized + * @param msg: the log message to write + * @param ...args: format string values when `msg` is a format string + */ + warn: LogFn; + /** + * Log at `'info'` level the given msg. If the first argument is an object, all its properties will be included in the JSON line. + * If more args follows `msg`, these will be used to format `msg` using `util.format`. + * + * @typeParam T: the interface of the object being serialized. Default is object. + * @param obj: object to be serialized + * @param msg: the log message to write + * @param ...args: format string values when `msg` is a format string + */ + info: LogFn; + /** + * Log at `'debug'` level the given msg. If the first argument is an object, all its properties will be included in the JSON line. + * If more args follows `msg`, these will be used to format `msg` using `util.format`. + * + * @typeParam T: the interface of the object being serialized. Default is object. + * @param obj: object to be serialized + * @param msg: the log message to write + * @param ...args: format string values when `msg` is a format string + */ + debug: LogFn; + /** + * Log at `'trace'` level the given msg. If the first argument is an object, all its properties will be included in the JSON line. + * If more args follows `msg`, these will be used to format `msg` using `util.format`. + * + * @typeParam T: the interface of the object being serialized. Default is object. + * @param obj: object to be serialized + * @param msg: the log message to write + * @param ...args: format string values when `msg` is a format string + */ + trace: LogFn; + /** + * Noop function. + */ + silent: LogFn; + + /** + * A utility method for determining if a given log level will write to the destination. + */ + isLevelEnabled(level: LevelWithSilent | string): boolean; + + /** + * Returns an object containing all the current bindings, cloned from the ones passed in via logger.child(). + */ + bindings(): Bindings; + } + + type LevelChangeEventListener = ( + lvl: LevelWithSilent | string, + val: number, + prevLvl: LevelWithSilent | string, + prevVal: number, + ) => void; + + interface LogFn { + /* tslint:disable:no-unnecessary-generics */ + (obj: T, msg?: string, ...args: any[]): void; + (msg: string, ...args: any[]): void; + } + + interface redactOptions { + paths: string[]; + censor?: string | ((v: any) => any); + remove?: boolean; + } +} diff --git a/test/types/pino-import.test-d.ts b/test/types/pino-import.test-d.ts new file mode 100644 index 000000000..82f78c0d0 --- /dev/null +++ b/test/types/pino-import.test-d.ts @@ -0,0 +1,29 @@ +import { expectType } from "tsd"; + +import pino from '../../pino'; +import { pino as pinoNamed, P } from "../../pino"; +import * as pinoStar from "../../pino"; +import pinoCjsImport = require ("../../pino"); +const pinoCjs = require("../../pino"); +const { P: pinoCjsNamed } = require('pino') + +const log = pino(); +expectType(log.info); +expectType(log.error); + +expectType(pinoNamed()); +expectType(pinoNamed()); +expectType(pinoStar.default()); +expectType(pinoStar.pino()); +expectType(pinoCjsImport.default()); +expectType(pinoCjsImport.pino()); +expectType(pinoCjsNamed()); +expectType(pinoCjs()); + +const levelChangeEventListener: P.LevelChangeEventListener = ( + lvl: P.LevelWithSilent | string, + val: number, + prevLvl: P.LevelWithSilent | string, + prevVal: number, +) => {} +expectType(levelChangeEventListener) diff --git a/test/types/pino.test-d.ts b/test/types/pino.test-d.ts new file mode 100644 index 000000000..b7dc6deff --- /dev/null +++ b/test/types/pino.test-d.ts @@ -0,0 +1,267 @@ +import P, { pino } from "../../"; +import { IncomingMessage, ServerResponse } from "http"; +import { Socket } from "net"; + +const log = pino(); +const info = log.info; +const error = log.error; + +info("hello world"); +error("this is at error level"); +info("the answer is %d", 42); +info({ obj: 42 }, "hello world"); +info({ obj: 42, b: 2 }, "hello world"); +info({ obj: { aa: "bbb" } }, "another"); +setImmediate(info, "after setImmediate"); +error(new Error("an error")); + +const writeSym = pino.symbols.writeSym; + +const testUniqSymbol = { + [pino.symbols.needsMetadataGsym]: true, +}[pino.symbols.needsMetadataGsym]; + +const log2: P.Logger = pino({ + name: "myapp", + safe: true, + serializers: { + req: pino.stdSerializers.req, + res: pino.stdSerializers.res, + err: pino.stdSerializers.err, + }, +}); + +pino({ + write(o) {}, +}); + +pino({ + mixin() { + return { customName: "unknown", customId: 111 }; + }, +}); + +pino({ + mixin: () => ({ customName: "unknown", customId: 111 }), +}); + +pino({ + redact: { paths: [], censor: "SECRET" }, +}); + +pino({ + redact: { paths: [], censor: () => "SECRET" }, +}); + +pino({ + browser: { + write(o) {}, + }, +}); + +pino({ + browser: { + write: { + info(o) {}, + error(o) {}, + }, + serialize: true, + asObject: true, + transmit: { + level: "fatal", + send: (level, logEvent) => { + level; + logEvent.bindings; + logEvent.level; + logEvent.ts; + logEvent.messages; + }, + }, + }, +}); + +pino({ base: null }); +pino({ base: { foo: "bar" }, changeLevelName: "severity" }); +pino({ base: { foo: "bar" }, levelKey: "severity" }); +if ("pino" in log) console.log(`pino version: ${log.pino}`); + +log.child({ a: "property" }).info("hello child!"); +log.level = "error"; +log.info("nope"); +const child = log.child({ foo: "bar" }); +child.info("nope again"); +child.level = "info"; +child.info("hooray"); +log.info("nope nope nope"); +log.child({ foo: "bar", level: "debug" }).debug("debug!"); +child.bindings(); +const customSerializers = { + test() { + return "this is my serializer"; + }, +}; +pino().child({ serializers: customSerializers }).info({ test: "should not show up" }); +const child2 = log.child({ father: true }); +const childChild = child2.child({ baby: true }); + +log.level = "info"; +if (log.levelVal === 30) { + console.log("logger level is `info`"); +} + +log.level = "myLevel"; +log.myLevel("a message"); + +const listener = (lvl: any, val: any, prevLvl: any, prevVal: any) => { + console.log(lvl, val, prevLvl, prevVal); +}; +log.on("level-change", (lvl, val, prevLvl, prevVal) => { + console.log(lvl, val, prevLvl, prevVal); +}); +log.level = "trace"; +log.removeListener("level-change", listener); +log.level = "info"; + +pino.levels.values.error === 50; +pino.levels.labels[50] === "error"; + +const logstderr: pino.Logger = pino(process.stderr); +logstderr.error("on stderr instead of stdout"); + +log.useLevelLabels = true; +log.info("lol"); +log.level === "info"; +const isEnabled: boolean = log.isLevelEnabled("info"); + +const extremeDest = pino.extreme(); +const logExtreme = pino(extremeDest); + +const handler = pino.final(logExtreme, (err: Error, finalLogger: pino.BaseLogger) => { + if (err) { + finalLogger.error(err, "error caused exit"); + } +}); + +handler(new Error("error")); + +const redacted = pino({ + redact: ["path"], +}); + +redacted.info({ + msg: "logged with redacted properties", + path: "Not shown", +}); + +const anotherRedacted = pino({ + redact: { + paths: ["anotherPath"], + censor: "Not the log you\re looking for", + }, +}); + +anotherRedacted.info({ + msg: "another logged with redacted properties", + anotherPath: "Not shown", +}); + +const pretty = pino({ + prettyPrint: { + colorize: true, + crlf: false, + errorLikeObjectKeys: ["err", "error"], + errorProps: "", + messageFormat: false, + ignore: "", + levelFirst: false, + messageKey: "msg", + timestampKey: "timestamp", + translateTime: "UTC:h:MM:ss TT Z", + search: "foo == `bar`", + }, +}); + +const withMessageFormatFunc = pino({ + prettyPrint: { + ignore: "requestId", + messageFormat: (log, messageKey: string) => { + const message = log[messageKey] as string; + if (log.requestId) return `[${log.requestId}] ${message}`; + return message; + }, + }, +}); + +const withTimeFn = pino({ + timestamp: pino.stdTimeFunctions.isoTime, +}); + +const withNestedKey = pino({ + nestedKey: "payload", +}); + +const withHooks = pino({ + hooks: { + logMethod(args, method, level) { + return method.apply(this, ['msg', ...args]); + }, + }, +}); + +// Properties/types imported from pino-std-serializers +const wrappedErrSerializer = pino.stdSerializers.wrapErrorSerializer((err: pino.SerializedError) => { + return { ...err, newProp: "foo" }; +}); +const wrappedReqSerializer = pino.stdSerializers.wrapRequestSerializer((req: pino.SerializedRequest) => { + return { ...req, newProp: "foo" }; +}); +const wrappedResSerializer = pino.stdSerializers.wrapResponseSerializer((res: pino.SerializedResponse) => { + return { ...res, newProp: "foo" }; +}); + +const socket = new Socket(); +const incomingMessage = new IncomingMessage(socket); +const serverResponse = new ServerResponse(incomingMessage); + +const mappedHttpRequest: { req: pino.SerializedRequest } = pino.stdSerializers.mapHttpRequest(incomingMessage); +const mappedHttpResponse: { res: pino.SerializedResponse } = pino.stdSerializers.mapHttpResponse(serverResponse); + +const serializedErr: pino.SerializedError = pino.stdSerializers.err(new Error()); +const serializedReq: pino.SerializedRequest = pino.stdSerializers.req(incomingMessage); +const serializedRes: pino.SerializedResponse = pino.stdSerializers.res(serverResponse); + +/** + * Destination static method + */ +const destinationViaDefaultArgs = pino.destination(); +const destinationViaStrFileDescriptor = pino.destination("/log/path"); +const destinationViaNumFileDescriptor = pino.destination(2); +const destinationViaStream = pino.destination(process.stdout); +const destinationViaOptionsObject = pino.destination({ dest: "/log/path", sync: false }); + +pino(destinationViaDefaultArgs); +pino({ name: "my-logger" }, destinationViaDefaultArgs); +pino(destinationViaStrFileDescriptor); +pino({ name: "my-logger" }, destinationViaStrFileDescriptor); +pino(destinationViaNumFileDescriptor); +pino({ name: "my-logger" }, destinationViaNumFileDescriptor); +pino(destinationViaStream); +pino({ name: "my-logger" }, destinationViaStream); +pino(destinationViaOptionsObject); +pino({ name: "my-logger" }, destinationViaOptionsObject); + +interface StrictShape { + activity: string; + err?: unknown; +} + +info({ + activity: "Required property", +}); + +const logLine: pino.LogDescriptor = { + level: 20, + msg: "A log message", + time: new Date().getTime(), + aCustomProperty: true, +}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..be75b109a --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "es6", + "lib": [ "es2015" ], + "module": "commonjs", + "noEmit": true, + "strict": true, + }, + "include": [ + "./test/types/*.test-d.ts", + "./*.d.ts" + ] +}