@appsemble/node-utils
Version:
NodeJS utilities used by Appsemble internally.
98 lines • 3.88 kB
JavaScript
import { EOL } from 'node:os';
import { inspect } from 'node:util';
import axios from 'axios';
import chalk from 'chalk';
import { highlight } from 'cli-highlight';
import winston from 'winston';
const levels = ['crit', 'error', 'warn', 'info', 'verbose', 'silly'];
const DEFAULT_LEVEL = levels.indexOf('info');
const padding = Math.max(...levels.map(({ length }) => length));
function headerCase(header) {
return header.replaceAll(/(^|-)\w/g, (a) => a.toUpperCase());
}
function httpErrorToString(error) {
const { config, request, response } = error;
return [
chalk.blue.bold('Request:'),
highlight([
`${config?.method?.toUpperCase()} ${axios.getUri(config)} HTTP/${request?.res?.httpVersion ?? '1.1'}`,
...Object.entries(config?.headers ?? {})
.map(([key, value]) => [headerCase(key), value])
.map(([key, value]) => `${key}: ${key === 'Authorization' ? 'xxxxxxxxxx' : value}`)
.sort(),
].join(EOL), { language: 'http', ignoreIllegals: true }),
'',
chalk.blue.bold('Response:'),
response
? highlight([
`HTTP/${request.res?.httpVersion} ${response.status} ${response.statusText}`,
...Object.entries(response.headers)
.map(([key, value]) => `${headerCase(key)}: ${value}`)
.sort(),
'',
response.data instanceof Object
? JSON.stringify(response.data, null, 2)
: response.data,
].join(EOL), { language: 'http', ignoreIllegals: true })
: chalk.red.bold('No response from server'),
].join(EOL);
}
function toString(info) {
if (info instanceof Error) {
if ('sql' in info) {
// @ts-expect-error 2322 null is not assignable to type (strictNullChecks)
return info.stack?.replace(/^Error/, `${info.sql}\n${info.name}: ${info.message}`);
}
if (axios.isAxiosError(info)) {
return httpErrorToString(info);
}
// @ts-expect-error 2322 null is not assignable to type (strictNullChecks)
return info.stack;
}
if (typeof info.message === 'string') {
return info.message;
}
return inspect(info.message, { colors: true });
}
/**
* The default logger for NodeJS Appsemble projects.
*/
export const logger = winston.createLogger({
level: levels[DEFAULT_LEVEL],
levels: Object.fromEntries(levels.map((level, index) => [level, index])),
format: winston.format.combine(winston.format((info) => ({
...info,
lines: toString(info)
.split(/\r?\n/)
.map((line) => `${''.padEnd(padding - info.level.length)}${line}`),
}))(), winston.format.colorize(), winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format.printf(({ level, lines, timestamp }) => lines.map((line) => `${timestamp} [${level}]: ${line}`).join(EOL))),
transports: [new winston.transports.Console()],
});
/**
* Set the logging level using a string or numeric value.
*
* @param level The logger level to set.
*/
export function setLogLevel(level = DEFAULT_LEVEL) {
logger.level = Number.isNaN(Number(level))
? level
: levels[Math.min(Math.max(level, 0), levels.length - 1)];
logger.silly(`Logging level set to ${logger.level}`);
}
/**
* Configure logging using named quiet and verbose parameters.
*
* Use this in conjunction with `yargs`.
*
* @param argv The processed command line arguments.
*/
export function configureLogger({ quiet = 0, verbose = 0 }) {
process.on('warning', (warning) => {
logger.warn(warning);
});
process.on('unhandledRejection', (error) => {
logger.error(error);
});
setLogLevel(DEFAULT_LEVEL + verbose - quiet);
}
//# sourceMappingURL=logger.js.map