/*! * logger.js - basic logger for bcoin * Copyright (c) 2014-2016, Christopher Jeffrey (MIT License). * https://github.com/bcoin-org/bcoin */ 'use strict'; var utils = require('../utils/utils'); var assert = require('assert'); var fs; if (!utils.isBrowser) fs = require('fs'); /** * Basic stdout and file logger. * @exports Logger * @constructor * @param {(String|Object)?} options/level * @param {String?} options.level * @param {Boolean} [options.colors=true] */ function Logger(options) { if (!(this instanceof Logger)) return new Logger(options); if (!options) options = {}; if (typeof options === 'string') options = { level: options }; this.level = Logger.levels.warning; this.colors = options.colors !== false; this.console = options.console !== false; this.file = options.file; this.stream = options.stream; this.closed = false; assert(!this.file || typeof this.file === 'string', 'Bad file.'); assert(!this.stream || typeof this.stream.write === 'function', 'Bad stream.'); if (!process.stdout || !process.stdout.isTTY) this.colors = false; if (options.level != null) this.setLevel(options.level); } /** * Available log levels. * @enum {Number} */ Logger.levels = { none: 0, error: 1, warning: 2, info: 3, debug: 4, spam: 5 }; /** * Default CSI colors. * @enum {String} */ Logger.colors = { error: '1;31', warning: '1;33', info: '94', debug: '90', spam: '90' }; /** * Open the logger. */ Logger.prototype.open = function open() { this.closed = false; if (this.stream) this.stream.open(); }; /** * Destroy the write stream. */ Logger.prototype.close = function close() { if (!this.stream) return; try { this.stream.close(); } catch (e) { ; } this.closed = true; }; /** * Set or reset the log level. * @param {String} level */ Logger.prototype.setLevel = function setLevel(level) { level = Logger.levels[level]; assert(level != null, 'Invalid log level.'); this.level = level; }; /** * Output a log to the `error` log level. * @param {String|Object|Error} err * @param {...Object} args */ Logger.prototype.error = function error(err) { var i, args; if (this.level < Logger.levels.error) return; if (err instanceof Error) return this._error(err); args = new Array(arguments.length); for (i = 0; i < args.length; i++) args[i] = arguments[i]; this.log('error', args); }; /** * Output a log to the `warning` log level. * @param {String|Object} obj * @param {...Object} args */ Logger.prototype.warning = function warning() { var i, args; if (this.level < Logger.levels.warning) return; args = new Array(arguments.length); for (i = 0; i < args.length; i++) args[i] = arguments[i]; this.log('warning', args); }; /** * Output a log to the `info` log level. * @param {String|Object} obj * @param {...Object} args */ Logger.prototype.info = function info() { var i, args; if (this.level < Logger.levels.info) return; args = new Array(arguments.length); for (i = 0; i < args.length; i++) args[i] = arguments[i]; this.log('info', args); }; /** * Output a log to the `debug` log level. * @param {String|Object} obj * @param {...Object} args */ Logger.prototype.debug = function debug() { var i, args; if (this.level < Logger.levels.debug) return; args = new Array(arguments.length); for (i = 0; i < args.length; i++) args[i] = arguments[i]; this.log('debug', args); }; /** * Output a log to the `spam` log level. * @param {String|Object} obj * @param {...Object} args */ Logger.prototype.spam = function spam() { var i, args; if (this.level < Logger.levels.spam) return; args = new Array(arguments.length); for (i = 0; i < args.length; i++) args[i] = arguments[i]; this.log('spam', args); }; /** * Output a log to the desired log level. * Note that this bypasses the level check. * @param {String} level * @param {Object[]} args */ Logger.prototype.log = function log(level, args) { if (this.closed) return; assert(Logger.levels[level] != null, 'Invalid log level.'); this.writeConsole(level, args); this.writeStream(level, args); }; /** * Write log to the console. * @param {String} level * @param {Object[]} args */ Logger.prototype.writeConsole = function writeConsole(level, args) { var prefix, msg, color; if (!this.console) return; prefix = '[' + level + '] '; if (utils.isBrowser) { msg = typeof args[0] !== 'object' ? utils.format(args, false) : args[0]; msg = prefix + msg; return level === 'error' ? console.error(msg) : console.log(msg); } if (this.colors) { color = Logger.colors[level]; prefix = '\x1b[' + color + 'm' + prefix + '\x1b[m'; } msg = prefix + utils.format(args, this.colors); return level === 'error' ? process.stderr.write(msg + '\n') : process.stdout.write(msg + '\n'); }; /** * Write a string to the output stream (usually a file). * @param {String} level * @param {Object[]} args */ Logger.prototype.writeStream = function writeStream(level, args) { var prefix, msg; if (this.closed) return; if (!this.stream) { if (!this.file) return; if (utils.isBrowser) return; utils.mkdir(this.file, true); this.stream = fs.createWriteStream(this.file, { flags: 'a' }); this.stream.on('error', function() {}); } prefix = '[' + level + '] '; msg = prefix + utils.format(args, false); msg = '(' + utils.date() + '): ' + msg + '\n'; if (!utils.isBrowser) msg = process.pid + ' ' + msg; this.stream.write(msg); }; /** * Helper to parse an error into a nicer * format. Call's `log` internally. * @private * @param {Error} err */ Logger.prototype._error = function error(err) { var msg; if (this.closed) return; if (utils.isBrowser && this.console) console.error(err); msg = (err.message + '').replace(/^ *Error: */, ''); this.log('error', [msg]); if (this.level >= Logger.levels.debug) { if (this.stream) this.stream.write(err.stack + '\n'); } }; /** * Log the current memory usage. */ Logger.prototype.memory = function memory() { var mem; if (!process.memoryUsage) return; mem = process.memoryUsage(); this.debug('Memory: rss=%dmb, js-heap=%d/%dmb native-heap=%dmb', utils.mb(mem.rss), utils.mb(mem.heapUsed), utils.mb(mem.heapTotal), utils.mb(mem.rss - mem.heapTotal)); }; /* * Default */ Logger.global = new Logger('none'); /* * Expose */ module.exports = Logger;