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;
 |