Home Reference Source

slogger-repo/lib/LogPrinter.js

const path = require('path');
const fs = require('fs');
const config = require('./config');
const LogstashSender = require('./senders/LogstashSender');
const LOG_LEVEL_MAP = config.LOG_LEVEL_MAP;
const NEW_LINE_SEPARATOR = require('os').EOL;
// var LOG_CACHE = '';
const consoleStream = process.stdout;

/**
 * 
 * @typedef LogFileItem
 * 
 * @property {String} filename The file to save the log.
 * @property {String} category The category of the log , it can be the log level, such as `debug` `info` `warn` `error`, or it can be a custom string.
 */

 /**
  * @typedef LogstashItem
  * 
  * @property {Logstash} server The `Logstash` server.
  * @property {String} category The category of the log , it can be the log level, such as `debug` `info` `warn` `error`, or it can be a custom string.
  */

/**
 * Format the Date to a string.
 * 
 * @private
 * @param {Date} date 
 */
function dateFormat(date) {
    return date.getFullYear()+'-' + (date.getMonth() + 1) + '-'+date.getDate() + ' '+date.getHours()+':'+date.getMinutes() + ':'+date.getSeconds() + '.' + date.getMilliseconds();
}

function myFormat(params) {
    var str = '';
    if (!params) {
        return str;
    }
    var element;
    for (var i=0,len=params.length;i<len;i++) {
        element = params[i];
        if (element instanceof Error) {
            str += ' ' + element.stack + NEW_LINE_SEPARATOR;
        } else if (typeof(element) === 'object') {
            str += ' ' + JSON.stringify(element);
        } else {
            str += ' ' + element;
        }
    }
    return str;
}
function getRealFilename(filename,clusterNum) {
    if (!clusterNum) {
        return filename;
    }
    const dir = path.dirname(filename);
    const ext = path.extname(filename);
    if (ext) {
        return dir + '/' + path.basename(filename,ext) + '-' + clusterNum + ext;
    }
    return filename + '-' + clusterNum;
}

/**
 * @private
 * @param {Object} options 
 * @param {Number} [options.flushInterval=0]
 * @param {LogFileItem[]}  [options.logFiles=undefined]
 * @param {LogstashItem[]} [options.logstashes=undefined]
 * @param {Boolean=}  [options.disableTimePrefix=false] Whether disable the time perfix.
 * @param {String=} [options.projectName=''] The name of project which use slogger.
 */
function LogPrinter(options) {

    this._flushInteval = options.flushInterval|| 0;
    this._logCache = '';
    const logFiles = options.logFiles || [];
    this._disableTimePrefix = options.disableTimePrefix;
    this._levelFileMap = {};
    this._levelFileLen = 0;
    this._levelLogstashMap = {};
    this._clusterNum = process.env.NODE_APP_INSTANCE;
    this._projectName = options.projectName || '';

    if (logFiles.length > 0) {
        for(var i=0,len=logFiles.length;i<len;i++) {
            const logFileConfig = logFiles[i];
            const filename = logFileConfig.filename;
            const category = logFileConfig.category;
            if (!this._levelFileMap[category]) {
                this._levelFileMap[category] = {
                    logCache:'',
                    streams:[]
                };
                this._levelFileLen++;
            }
            this._levelFileMap[category].streams.push(
                fs.createWriteStream(getRealFilename(filename,this._clusterNum), {'flags': 'a'})
            ); 
        }
    }
    const logstashes = options.logstashes || [];
    for (var i=0,len=logstashes.length;i<len;i++) {
        const lsConfig = logstashes[i];
        this._levelLogstashMap[lsConfig.category] = new LogstashSender({
            logstash:lsConfig.server,
            delayTime:this._flushInteval
        }) ;
    }

    
    if (this._flushInteval > 0) {
        this._flushLog();
    }
}

LogPrinter.prototype._wirteToStream = function(fileConfig) {
    var streams = fileConfig.streams;
    var content = fileConfig.logCache;
    for(var i=0,len=streams.length;i<len;i++) {
        streams[i].write(content);
    }
};

LogPrinter.prototype._flushFileStream = function() {
    var _this = this;
    setImmediate(function doFlushFileStream() {
        for (var level in _this._levelFileMap) {
            var fileConfig = _this._levelFileMap[level];
            if (!fileConfig.logCache) {
                continue;
            }
            _this._wirteToStream(fileConfig);
            fileConfig.logCache = '';
        }
    });
};

LogPrinter.prototype._flushLog = function() {
    var _this = this;
    setTimeout(function flushTimeout() {
        if (_this._logCache) {
            consoleStream.write(_this._logCache);
            // console.info(_this._logCache);
            _this._logCache = '';
            // stdout.uncork();
        }
        if (_this._levelFileLen > 0) {
            _this._flushFileStream();
        }
        _this._flushLog();
    },this._flushInteval);
};

LogPrinter.prototype.print = function(args,level) {

    var config = LOG_LEVEL_MAP[level] || {color : ''};
    var prefix = config.color;
    if (!this._disableTimePrefix) {
        prefix += dateFormat(new Date());
    }
    prefix += ' ['+(config.showName || level)+'] \x1b[0m';
    var len = args.length;
    var params = new Array(len+1);
    params[0] = prefix;
    for (var i=1;i<=len;i++) {
        params[i] = args[i-1];
    }
    var logContent = myFormat(params) + NEW_LINE_SEPARATOR;
    var fileConfig = this._levelFileMap[level];
    if (this._flushInteval > 0) {
        this._logCache += logContent;
        
        if (fileConfig) {
            fileConfig.logCache += logContent;
        }
    } else {
        consoleStream.write(logContent);
        if (fileConfig) {
            fileConfig.logCache = logContent;
            this._wirteToStream(fileConfig);
        }
    }
    var logstashStream = this._levelLogstashMap[level];
    if (logstashStream) {
        logstashStream.addData({projectName:this._projectName,level,logContent});
    }
};

module.exports = LogPrinter;