211 lines
6.6 KiB
JavaScript
211 lines
6.6 KiB
JavaScript
const moment = require('moment');
|
|
const Transform = require('stream').Transform;
|
|
const format = require('util').format;
|
|
const prettyjson = require('prettyjson');
|
|
const each = require('lodash/each');
|
|
const omit = require('lodash/omit');
|
|
const get = require('lodash/get');
|
|
const isArray = require('lodash/isArray');
|
|
const isEmpty = require('lodash/isEmpty');
|
|
const isObject = require('lodash/isObject');
|
|
const isString = require('lodash/isString');
|
|
const _private = {
|
|
levelFromName: {
|
|
10: 'trace',
|
|
20: 'debug',
|
|
30: 'info',
|
|
40: 'warn',
|
|
50: 'error',
|
|
60: 'fatal'
|
|
},
|
|
colorForLevel: {
|
|
10: 'grey',
|
|
20: 'grey',
|
|
30: 'cyan',
|
|
40: 'magenta',
|
|
50: 'red',
|
|
60: 'inverse'
|
|
},
|
|
colors: {
|
|
default: [39, 39],
|
|
bold: [1, 22],
|
|
italic: [3, 23],
|
|
underline: [4, 24],
|
|
inverse: [7, 27],
|
|
white: [37, 39],
|
|
grey: [90, 39],
|
|
black: [30, 39],
|
|
blue: [34, 39],
|
|
cyan: [36, 39],
|
|
green: [32, 39],
|
|
magenta: [35, 39],
|
|
red: [31, 39],
|
|
yellow: [33, 39]
|
|
}
|
|
};
|
|
|
|
function colorize(colors, value) {
|
|
if (isArray(colors)) {
|
|
return colors.reduce((acc, color) => colorize(color, acc), value);
|
|
} else {
|
|
return '\x1B[' + _private.colors[colors][0] + 'm' + value + '\x1B[' + _private.colors[colors][1] + 'm';
|
|
}
|
|
}
|
|
|
|
function statusCode(status) {
|
|
/* eslint-disable indent */
|
|
const color = status >= 500 ? 'red'
|
|
: status >= 400 ? 'yellow'
|
|
: status >= 300 ? 'cyan'
|
|
: status >= 200 ? 'green'
|
|
: 'default'; // no color
|
|
/* eslint-enable indent */
|
|
|
|
return colorize(color, status);
|
|
}
|
|
|
|
class PrettyStream extends Transform {
|
|
constructor(options) {
|
|
options = options || {};
|
|
super(options);
|
|
|
|
this.mode = options.mode || 'short';
|
|
}
|
|
|
|
write(data, enc, cb) {
|
|
// Bunyan sometimes passes things as objects. Because of this, we need to make sure
|
|
// the data is converted to JSON
|
|
if (isObject(data) && !(data instanceof Buffer)) {
|
|
data = JSON.stringify(data);
|
|
}
|
|
|
|
super.write(data, enc, cb);
|
|
}
|
|
|
|
_transform(data, enc, cb) {
|
|
if (!isString(data)) {
|
|
data = data.toString();
|
|
}
|
|
|
|
// Remove trailing newline if any
|
|
data = data.replace(/\\n$/, '');
|
|
|
|
try {
|
|
data = JSON.parse(data);
|
|
} catch (err) {
|
|
cb(err);
|
|
// If data is not JSON we don't want to continue processing as if it is
|
|
return;
|
|
}
|
|
|
|
let output = '';
|
|
const time = moment(data.time).format('YYYY-MM-DD HH:mm:ss');
|
|
let logLevel = _private.levelFromName[data.level].toUpperCase();
|
|
const codes = _private.colors[_private.colorForLevel[data.level]];
|
|
let bodyPretty = '';
|
|
|
|
logLevel = '\x1B[' + codes[0] + 'm' + logLevel + '\x1B[' + codes[1] + 'm';
|
|
|
|
if (data.req) {
|
|
output += format('[%s] %s "%s %s" %s %s\n',
|
|
time,
|
|
logLevel,
|
|
data.req.method.toUpperCase(),
|
|
get(data, 'req.originalUrl'),
|
|
statusCode(get(data, 'res.statusCode')),
|
|
get(data, 'res.responseTime')
|
|
);
|
|
} else if (data.msg === undefined) {
|
|
output += format('[%s] %s\n',
|
|
time,
|
|
logLevel
|
|
);
|
|
} else {
|
|
bodyPretty += data.msg;
|
|
output += format('[%s] %s %s\n', time, logLevel, bodyPretty);
|
|
}
|
|
|
|
each(omit(data, ['time', 'level', 'name', 'hostname', 'pid', 'v', 'msg']), function (value, key) {
|
|
// we always output errors for now
|
|
if (isObject(value) && value.message && value.stack) {
|
|
let error = '\n';
|
|
|
|
if (value.errorType) {
|
|
error += colorize(_private.colorForLevel[data.level], 'Type: ' + value.errorType) + '\n';
|
|
}
|
|
|
|
error += colorize(_private.colorForLevel[data.level], value.message) + '\n\n';
|
|
|
|
if (value.context) {
|
|
error += colorize('white', value.context) + '\n';
|
|
}
|
|
|
|
if (value.help) {
|
|
error += colorize('yellow', value.help) + '\n';
|
|
}
|
|
|
|
if (value.context || value.help) {
|
|
error += '\n';
|
|
}
|
|
|
|
if (value.id) {
|
|
error += colorize(['white', 'bold'], 'Error ID:') + '\n';
|
|
error += ' ' + colorize('grey', value.id) + '\n\n';
|
|
}
|
|
|
|
if (value.code) {
|
|
error += colorize(['white', 'bold'], 'Error Code: ') + '\n';
|
|
error += ' ' + colorize('grey', value.code) + '\n\n';
|
|
}
|
|
|
|
if (value.errorDetails) {
|
|
let details = value.errorDetails;
|
|
|
|
try {
|
|
const jsonDetails = JSON.parse(value.errorDetails);
|
|
details = isArray(jsonDetails) ? jsonDetails[0] : jsonDetails;
|
|
} catch (err) {
|
|
// no need for special handling as we default to unparsed 'errorDetails'
|
|
}
|
|
|
|
const pretty = prettyjson.render(details, {
|
|
noColor: true
|
|
}, 4);
|
|
|
|
error += colorize(['white', 'bold'], 'Details:') + '\n';
|
|
error += colorize('grey', pretty) + '\n\n';
|
|
}
|
|
|
|
if (value.stack && !value.hideStack) {
|
|
error += colorize('grey', '----------------------------------------') + '\n\n';
|
|
error += colorize('grey', value.stack) + '\n';
|
|
}
|
|
|
|
output += format('%s\n', colorize(_private.colorForLevel[data.level], error));
|
|
} else if (isObject(value)) {
|
|
bodyPretty += '\n' + colorize('yellow', key.toUpperCase()) + '\n';
|
|
|
|
let sanitized = {};
|
|
|
|
each(value, function (innerValue, innerKey) {
|
|
if (!isEmpty(innerValue)) {
|
|
sanitized[innerKey] = innerValue;
|
|
}
|
|
});
|
|
|
|
bodyPretty += prettyjson.render(sanitized, {}) + '\n';
|
|
} else {
|
|
bodyPretty += prettyjson.render(value, {}) + '\n';
|
|
}
|
|
});
|
|
|
|
if (this.mode !== 'short' && (bodyPretty !== data.msg)) {
|
|
output += format('%s\n', colorize('grey', bodyPretty));
|
|
}
|
|
|
|
cb(null, output);
|
|
}
|
|
}
|
|
|
|
module.exports = PrettyStream;
|