pino-princess
Version:
Prettiest Pino Prettifier in all the land
207 lines (206 loc) • 7.3 kB
JavaScript
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}]`) : '';
};
}