diff --git a/src/lib/logger.ts b/src/lib/logger.ts index 9318484d2..6a568591e 100644 --- a/src/lib/logger.ts +++ b/src/lib/logger.ts @@ -1,15 +1,13 @@ /* eslint-disable */ -import { pad } from './utils'; - -import { fillInMsgTemplate } from './logger/parser'; -import {calculateLevel, levels, subsystems} from "./logger/levels"; +import {prettyTimestamped} from "./logger/format/pretty-timestamped"; +import {pretty} from "./logger/format/pretty"; +import {jsonFormat} from "./logger/format/json"; const cluster = require('cluster'); const Logger = require('bunyan'); const Error = require('http-errors'); const Stream = require('stream'); -const { red, yellow, cyan, magenta, green, white } = require('kleur'); const pkgJSON = require('../../package.json'); const _ = require('lodash'); const dayjs = require('dayjs'); @@ -20,24 +18,33 @@ const dayjs = require('dayjs'); class VerdaccioRotatingFileStream extends Logger.RotatingFileStream { // We depend on mv so that this is there write(obj) { - const msg = fillInMsgTemplate(obj.msg, obj, false); - super.write(JSON.stringify({ ...obj, msg }, Logger.safeCycles()) + '\n'); + super.write(jsonFormat(obj, false)); } } let logger; +export interface LoggerTarget { + type?: string; + format?: string; + level?: string; + options?: any; + path?: string; +} + +const DEFAULT_LOGGER_CONF = [{ type: 'stdout', format: 'pretty', level: 'http' }]; + /** * Setup the Buyan logger * @param {*} logs list of log configuration */ function setup(logs) { - const streams = []; + const streams: any = []; if (logs == null) { - logs = [{ type: 'stdout', format: 'pretty', level: 'http' }]; + logs = DEFAULT_LOGGER_CONF; } - logs.forEach(function(target) { + logs.forEach(function(target: LoggerTarget) { let level = target.level || 35; if (level === 'http') { level = 35; @@ -63,14 +70,16 @@ function setup(logs) { ) ); - streams.push({ + const rotateStream: any = { // @ts-ignore type: 'raw', // @ts-ignore level, // @ts-ignore stream, - }); + }; + + streams.push(rotateStream); } else { const stream = new Stream(); stream.writable = true; @@ -93,17 +102,16 @@ function setup(logs) { if (target.format === 'pretty') { // making fake stream for pretty printing stream.write = obj => { - destination.write(`${print(obj.level, obj.msg, obj, destinationIsTTY)}\n`); + destination.write(pretty(obj, destinationIsTTY)); }; } else if (target.format === 'pretty-timestamped') { // making fake stream for pretty printing stream.write = obj => { - destination.write(`[${dayjs(obj.time).format('YYYY-MM-DD HH:mm:ss')}] ${print(obj.level, obj.msg, obj, destinationIsTTY)}\n`); + destination.write(prettyTimestamped(obj, destinationIsTTY)); }; } else { stream.write = obj => { - const msg = fillInMsgTemplate(obj.msg, obj, destinationIsTTY); - destination.write(`${JSON.stringify({ ...obj, msg }, Logger.safeCycles())}\n`); + destination.write(jsonFormat(obj, destinationIsTTY)); }; } @@ -134,39 +142,4 @@ function setup(logs) { }); } -// adopted from socket.io -// this part was converted to coffee-script and back again over the years, -// so it might look weird - -let max = 0; -for (const l in levels) { - if (Object.prototype.hasOwnProperty.call(levels, l)) { - max = Math.max(max, l.length); - } -} - -/** - * Apply colors to a string based on level parameters. - * @param {*} type - * @param {*} msg - * @param {*} obj - * @param {*} colors - * @return {String} - */ - function print(type, msg, obj, colors) { - if (typeof type === 'number') { - type = calculateLevel(type); - } - const finalMessage = fillInMsgTemplate(msg, obj, colors); - - - - const sub = subsystems[colors ? 0 : 1][obj.sub] || subsystems[+!colors].default; - if (colors) { - return ` ${levels[type](pad(type, max))}${white(`${sub} ${finalMessage}`)}`; - } else { - return ` ${pad(type, max)}${sub} ${finalMessage}`; - } -} - export { setup, logger }; diff --git a/src/lib/logger/format/json.ts b/src/lib/logger/format/json.ts new file mode 100644 index 000000000..6afbca590 --- /dev/null +++ b/src/lib/logger/format/json.ts @@ -0,0 +1,10 @@ + +import {fillInMsgTemplate} from "../formatter"; + +const Logger = require('bunyan'); + +export function jsonFormat(obj, hasColors): string { + const msg = fillInMsgTemplate(obj.msg, obj, hasColors); + + return `${JSON.stringify({ ...obj, msg }, Logger.safeCycles())}\n`; +} diff --git a/src/lib/logger/format/pretty-timestamped.ts b/src/lib/logger/format/pretty-timestamped.ts new file mode 100644 index 000000000..e7b0a9e44 --- /dev/null +++ b/src/lib/logger/format/pretty-timestamped.ts @@ -0,0 +1,6 @@ +import {formatLoggingDate} from "../utils"; +import {printMessage} from "../formatter"; + +export function prettyTimestamped(obj, hasColors): string { + return `[${formatLoggingDate(obj.time)}] ${printMessage(obj.level, obj.msg, obj, hasColors)}\n`; +} diff --git a/src/lib/logger/format/pretty.ts b/src/lib/logger/format/pretty.ts new file mode 100644 index 000000000..c8f6875cc --- /dev/null +++ b/src/lib/logger/format/pretty.ts @@ -0,0 +1,5 @@ +import {printMessage} from "../formatter"; + +export function pretty(obj, hasColors): string { + return `${printMessage(obj.level, obj.msg, obj, hasColors)}\n`; +} diff --git a/src/lib/logger/formatter.ts b/src/lib/logger/formatter.ts new file mode 100644 index 000000000..eba466c37 --- /dev/null +++ b/src/lib/logger/formatter.ts @@ -0,0 +1,71 @@ +import { inspect } from 'util'; +import { isObject, pad } from '../utils'; +import { red, green } from 'kleur'; + +import { white } from 'kleur'; +import {calculateLevel, levels, subsystems} from "./levels"; + +let LEVEL_VALUE_MAX = 0; +for (const l in levels) { + if (Object.prototype.hasOwnProperty.call(levels, l)) { + LEVEL_VALUE_MAX = Math.max(LEVEL_VALUE_MAX, l.length); + } +} + +/** + * Apply colors to a string based on level parameters. + * @param {*} type + * @param {*} msg + * @param {*} templateObjects + * @param {*} hasColors + * @return {String} + */ +export function printMessage(type, msg, templateObjects, hasColors) { + if (typeof type === 'number') { + type = calculateLevel(type); + } + + const finalMessage = fillInMsgTemplate(msg, templateObjects, hasColors); + + const sub = subsystems[hasColors ? 0 : 1][templateObjects.sub] || subsystems[+!hasColors].default; + if (hasColors) { + return ` ${levels[type](pad(type, LEVEL_VALUE_MAX))}${white(`${sub} ${finalMessage}`)}`; + } else { + return ` ${pad(type, LEVEL_VALUE_MAX)}${sub} ${finalMessage}`; + } +} + +export function fillInMsgTemplate(msg, obj: unknown, colors): string { + return msg.replace(/@{(!?[$A-Za-z_][$0-9A-Za-z\._]*)}/g, (_, name): string => { + + let str = obj; + let is_error; + if (name[0] === '!') { + name = name.substr(1); + is_error = true; + } + + const _ref = name.split('.'); + for (let _i = 0; _i < _ref.length; _i++) { + const id = _ref[_i]; + if (isObject(str)) { + // @ts-ignore + str = str[id]; + } else { + str = undefined; + } + } + + if (typeof str === 'string') { + if (!colors || (str as string).includes('\n')) { + return str; + } else if (is_error) { + return red(str); + } else { + return green(str); + } + } else { + return inspect(str, undefined, null, colors); + } + }); +} diff --git a/src/lib/logger/parser.ts b/src/lib/logger/parser.ts deleted file mode 100644 index 373c2bf3c..000000000 --- a/src/lib/logger/parser.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { inspect } from 'util'; -import { isObject } from '../utils'; -import { red, green } from 'kleur'; - -export function fillInMsgTemplate(msg, obj: unknown, colors): string { - return msg.replace(/@{(!?[$A-Za-z_][$0-9A-Za-z\._]*)}/g, (_, name): string => { - - let str = obj; - let is_error; - if (name[0] === '!') { - name = name.substr(1); - is_error = true; - } - - const _ref = name.split('.'); - for (let _i = 0; _i < _ref.length; _i++) { - const id = _ref[_i]; - if (isObject(str)) { - // @ts-ignore - str = str[id]; - } else { - str = undefined; - } - } - - if (typeof str === 'string') { - if (!colors || (str as string).includes('\n')) { - return str; - } else if (is_error) { - return red(str); - } else { - return green(str); - } - } else { - return inspect(str, undefined, null, colors); - } - }); -} diff --git a/src/lib/logger/utils.ts b/src/lib/logger/utils.ts new file mode 100644 index 000000000..251bce4c1 --- /dev/null +++ b/src/lib/logger/utils.ts @@ -0,0 +1,7 @@ +import dayjs from 'dayjs'; + +export const FORMAT_DATE = 'YYYY-MM-DD HH:mm:ss'; + +export function formatLoggingDate(time: string): string { + return dayjs(time).format(FORMAT_DATE); +} diff --git a/test/unit/modules/logger/parser.spec.ts b/test/unit/modules/logger/parser.spec.ts index 1b528f43c..6e984c54a 100644 --- a/test/unit/modules/logger/parser.spec.ts +++ b/test/unit/modules/logger/parser.spec.ts @@ -1,4 +1,4 @@ -import { fillInMsgTemplate } from "../../../../src/lib/logger/parser"; +import { fillInMsgTemplate } from "../../../../src/lib/logger/formatter"; import {LOG_VERDACCIO_ERROR, LOG_VERDACCIO_BYTES} from "../../../../src/api/middleware"; import { HTTP_STATUS } from "@verdaccio/commons-api";