UNPKG

pino-princess

Version:

Prettiest Pino Prettifier in all the land

207 lines (206 loc) 7.3 kB
import _highlight from 'cli-highlight'; import { Chalk, supportsColor as chalkSupportsColor, } from 'chalk'; import prettyMs from 'pretty-ms'; import pcStringify from 'json-stringify-pretty-compact'; import { format } from 'date-fns'; import getValue from 'get-value'; import isUnicodeSupported from 'is-unicode-supported'; import isObject from "./utils/is-object.js"; const nl = '\n'; const defaultTimeFormat = 'h:mm:ss.SSS aaa'; /** key map cache - assigned once at startup if user has custom keymap */ const highlight = _highlight.default; const colorMap = { warn: 'yellow', info: 'cyan', error: 'red', debug: 'blue', trace: 'white', fatal: 'red', }; const numLevelsMapping = { 10: 'trace', 20: 'debug', 30: 'info', 40: 'warn', 50: 'error', 60: 'fatal', }; export class Formatter { chalk; supportsUnicode; keyMap; emojiMap = { warn(supportsUnicode) { return supportsUnicode ? '⚠️' : '!'; }, info(supportsUnicode) { return supportsUnicode ? '✨' : '*'; }, error(supportsUnicode) { return supportsUnicode ? '🚨' : 'X'; }, debug(supportsUnicode) { return supportsUnicode ? '🐛' : '+'; }, fatal(supportsUnicode) { return supportsUnicode ? '💀' : '!'; }, trace(supportsUnicode) { return supportsUnicode ? '🔍' : '.'; }, }; constructor({ keyMap, supportsColor, supportsUnicode, }) { this.keyMap = keyMap; this.supportsUnicode = supportsUnicode ?? isUnicodeSupported(); let level = 0; if (supportsColor === false) { level = 0; } else if (supportsColor === true) { level = 3; } else if (typeof chalkSupportsColor === 'object' && typeof chalkSupportsColor.level === 'number') { level = chalkSupportsColor.level; } this.chalk = new Chalk({ level, }); } stringify = (obj, indent, theme) => { const stringified = highlight(pcStringify(obj, { indent }), { language: 'json', ignoreIllegals: true, theme: { attr: this.chalk.cyanBright, string: this.chalk.yellow, ...theme, }, }); if (indent === Infinity || indent === '') { return stringified; } return /^{.*"/.test(stringified) ? ' ' + stringified.replace(/^{/, '').replace(/}$/, '') : stringified.replace(/^{\n/, '').replace(/\n}$/, ''); }; formatLevel = (_level) => { const level = typeof _level === 'number' ? numLevelsMapping[_level] : _level?.toLowerCase(); if (!level || !this.emojiMap?.[level]) return ''; const endlen = 5; const emoji = this.emojiMap[level]; const padding = ' '; const formattedLevel = this.chalk[colorMap[level]](level.toUpperCase()); const endPadding = endlen - level.length; return (emoji(this.supportsUnicode) + padding + formattedLevel + ''.padEnd(endPadding, ' ')); }; formatLoadTime = (elapsedTime) => { const elapsed = typeof elapsedTime === 'string' ? Number.parseInt(elapsedTime, 10) : (elapsedTime ?? 0); const time = prettyMs(elapsed); return elapsed > 750 ? this.chalk.red(time) : elapsed > 450 ? this.chalk.yellow(time) : this.chalk.green(time); }; formatTime = (instant, timeFormat = defaultTimeFormat) => { return this.chalk.gray(`[${format(new Date(instant ?? Date.now()), timeFormat)}]`); }; formatName = (name) => { if (!name) return ''; return this.chalk.blue(`[${name}]`); }; formatMessage = (message, { level } = {}) => { if (message === undefined) return ''; let pretty = ''; if (level === 50 || level === 'error') pretty = this.chalk.red(message); if (level === 10 || level === 'trace') pretty = this.chalk.cyan(message); if (level === 40 || level === 'warn') pretty = this.chalk.yellow(message); if (level === 20 || level === 'debug') pretty = this.chalk.white(message); if (level === 30 || level === 'info') pretty = this.chalk.white(message); if (level === 60 || level === 'fatal') pretty = this.chalk.white.bgRedBright(message); return pretty || message; }; formatBundleSize = (bundle) => { const bytes = Number.parseInt(bundle ?? '0', 10); const size = `${bytes}B`; return this.chalk.gray(size); }; formatUrl = (url, logObj = {}) => { const statusCode = getValue(logObj, this.keyMap['res.statusCode'] ?? 'res.statusCode'); return statusCode ? this.chalk.magenta(url) : ` ${this.chalk.magenta(url)}`; }; formatMethod = (method) => { if (!method) return ''; if (method.toLowerCase() === 'delete') { method = 'DEL'; } return method ? this.chalk.white(method.toUpperCase().padEnd(4)) : ''; }; formatStatusCode = (statusCode = 'xxx') => { return this.chalk[typeof statusCode === 'number' && statusCode < 300 ? 'green' : typeof statusCode === 'number' && statusCode < 500 ? 'yellow' : 'red'](statusCode); }; formatStack = (stack) => { return stack ? this.chalk.grey(nl + ' ' + stack) : ''; }; formatErrorProp = (errorPropValue) => { if (Array.isArray(errorPropValue?.aggregateErrors)) { const { aggregateErrors, ...ogErr } = errorPropValue; return ([isObject(ogErr) ? this.formatErrorProp(ogErr) : undefined] // eslint-disable-next-line unicorn/prefer-spread .concat(aggregateErrors.map((err) => ' ' + this.formatErrorProp(err))) .filter(Boolean) .join(nl)); } let stack = ''; if (errorPropValue?.type) delete errorPropValue.type; if (errorPropValue?.stack) { stack += this.formatStack(errorPropValue.stack); delete errorPropValue.stack; } if (errorPropValue?.message) delete errorPropValue.message; const hasExtraData = Object.keys(errorPropValue ?? {}).length > 0; if (!stack && !hasExtraData) return ''; return (stack + (stack ? nl : '') + (hasExtraData ? this.chalk.grey(this.stringify(errorPropValue, 4)) : '')); }; formatExtraFields = (extraFields, options) => { if (options?.singleLine) { return (' ' + this.chalk.grey(this.stringify(extraFields, '', options?.theme?.(this.chalk)))); } return (nl + this.chalk.grey(this.stringify(extraFields, undefined, options?.theme?.(this.chalk)))); }; formatId = (id) => { return id ? this.chalk.yellow(`[ID:${id}]`) : ''; }; }