| 'use strict' |
| var Progress = require('are-we-there-yet') |
| var Gauge = require('gauge') |
| var EE = require('events').EventEmitter |
| var log = exports = module.exports = new EE |
| var util = require('util') |
| |
| var ansi = require('ansi') |
| log.cursor = ansi(process.stderr) |
| log.stream = process.stderr |
| |
| // by default, let ansi decide based on tty-ness. |
| var colorEnabled = undefined |
| log.enableColor = function () { |
| colorEnabled = true |
| this.cursor.enabled = true |
| } |
| log.disableColor = function () { |
| colorEnabled = false |
| this.cursor.enabled = false |
| } |
| |
| // default level |
| log.level = 'info' |
| |
| log.gauge = new Gauge(log.cursor) |
| log.tracker = new Progress.TrackerGroup() |
| |
| // no progress bars unless asked |
| log.progressEnabled = false |
| |
| var gaugeTheme = undefined |
| |
| log.enableUnicode = function () { |
| gaugeTheme = Gauge.unicode |
| log.gauge.setTheme(gaugeTheme) |
| } |
| |
| log.disableUnicode = function () { |
| gaugeTheme = Gauge.ascii |
| log.gauge.setTheme(gaugeTheme) |
| } |
| |
| var gaugeTemplate = undefined |
| log.setGaugeTemplate = function (template) { |
| gaugeTemplate = template |
| log.gauge.setTemplate(gaugeTemplate) |
| } |
| |
| log.enableProgress = function () { |
| if (this.progressEnabled) return |
| this.progressEnabled = true |
| if (this._pause) return |
| this.tracker.on('change', this.showProgress) |
| this.gauge.enable() |
| this.showProgress() |
| } |
| |
| log.disableProgress = function () { |
| if (!this.progressEnabled) return |
| this.clearProgress() |
| this.progressEnabled = false |
| this.tracker.removeListener('change', this.showProgress) |
| this.gauge.disable() |
| } |
| |
| var trackerConstructors = ['newGroup', 'newItem', 'newStream'] |
| |
| var mixinLog = function (tracker) { |
| // mixin the public methods from log into the tracker |
| // (except: conflicts and one's we handle specially) |
| Object.keys(log).forEach(function (P) { |
| if (P[0] === '_') return |
| if (trackerConstructors.filter(function (C) { return C === P }).length) return |
| if (tracker[P]) return |
| if (typeof log[P] !== 'function') return |
| var func = log[P] |
| tracker[P] = function () { |
| return func.apply(log, arguments) |
| } |
| }) |
| // if the new tracker is a group, make sure any subtrackers get |
| // mixed in too |
| if (tracker instanceof Progress.TrackerGroup) { |
| trackerConstructors.forEach(function (C) { |
| var func = tracker[C] |
| tracker[C] = function () { return mixinLog(func.apply(tracker, arguments)) } |
| }) |
| } |
| return tracker |
| } |
| |
| // Add tracker constructors to the top level log object |
| trackerConstructors.forEach(function (C) { |
| log[C] = function () { return mixinLog(this.tracker[C].apply(this.tracker, arguments)) } |
| }) |
| |
| log.clearProgress = function () { |
| if (!this.progressEnabled) return |
| this.gauge.hide() |
| } |
| |
| log.showProgress = function (name, completed) { |
| if (!this.progressEnabled) return |
| if (completed == null) completed = this.tracker.completed() |
| this.gauge.show(name, completed) |
| }.bind(log) // bind for use in tracker's on-change listener |
| |
| // temporarily stop emitting, but don't drop |
| log.pause = function () { |
| this._paused = true |
| } |
| |
| log.resume = function () { |
| if (!this._paused) return |
| this._paused = false |
| |
| var b = this._buffer |
| this._buffer = [] |
| b.forEach(function (m) { |
| this.emitLog(m) |
| }, this) |
| if (this.progressEnabled) this.enableProgress() |
| } |
| |
| log._buffer = [] |
| |
| var id = 0 |
| log.record = [] |
| log.maxRecordSize = 10000 |
| log.log = function (lvl, prefix, message) { |
| var l = this.levels[lvl] |
| if (l === undefined) { |
| return this.emit('error', new Error(util.format( |
| 'Undefined log level: %j', lvl))) |
| } |
| |
| var a = new Array(arguments.length - 2) |
| var stack = null |
| for (var i = 2; i < arguments.length; i ++) { |
| var arg = a[i-2] = arguments[i] |
| |
| // resolve stack traces to a plain string. |
| if (typeof arg === 'object' && arg && |
| (arg instanceof Error) && arg.stack) { |
| arg.stack = stack = arg.stack + '' |
| } |
| } |
| if (stack) a.unshift(stack + '\n') |
| message = util.format.apply(util, a) |
| |
| var m = { id: id++, |
| level: lvl, |
| prefix: String(prefix || ''), |
| message: message, |
| messageRaw: a } |
| |
| this.emit('log', m) |
| this.emit('log.' + lvl, m) |
| if (m.prefix) this.emit(m.prefix, m) |
| |
| this.record.push(m) |
| var mrs = this.maxRecordSize |
| var n = this.record.length - mrs |
| if (n > mrs / 10) { |
| var newSize = Math.floor(mrs * 0.9) |
| this.record = this.record.slice(-1 * newSize) |
| } |
| |
| this.emitLog(m) |
| }.bind(log) |
| |
| log.emitLog = function (m) { |
| if (this._paused) { |
| this._buffer.push(m) |
| return |
| } |
| if (this.progressEnabled) this.gauge.pulse(m.prefix) |
| var l = this.levels[m.level] |
| if (l === undefined) return |
| if (l < this.levels[this.level]) return |
| if (l > 0 && !isFinite(l)) return |
| |
| var style = log.style[m.level] |
| var disp = log.disp[m.level] || m.level |
| this.clearProgress() |
| m.message.split(/\r?\n/).forEach(function (line) { |
| if (this.heading) { |
| this.write(this.heading, this.headingStyle) |
| this.write(' ') |
| } |
| this.write(disp, log.style[m.level]) |
| var p = m.prefix || '' |
| if (p) this.write(' ') |
| this.write(p, this.prefixStyle) |
| this.write(' ' + line + '\n') |
| }, this) |
| this.showProgress() |
| } |
| |
| log.write = function (msg, style) { |
| if (!this.cursor) return |
| if (this.stream !== this.cursor.stream) { |
| this.cursor = ansi(this.stream, { enabled: colorEnabled }) |
| var options = {} |
| if (gaugeTheme != null) options.theme = gaugeTheme |
| if (gaugeTemplate != null) options.template = gaugeTemplate |
| this.gauge = new Gauge(options, this.cursor) |
| } |
| |
| style = style || {} |
| if (style.fg) this.cursor.fg[style.fg]() |
| if (style.bg) this.cursor.bg[style.bg]() |
| if (style.bold) this.cursor.bold() |
| if (style.underline) this.cursor.underline() |
| if (style.inverse) this.cursor.inverse() |
| if (style.beep) this.cursor.beep() |
| this.cursor.write(msg).reset() |
| } |
| |
| log.addLevel = function (lvl, n, style, disp) { |
| if (!disp) disp = lvl |
| this.levels[lvl] = n |
| this.style[lvl] = style |
| if (!this[lvl]) this[lvl] = function () { |
| var a = new Array(arguments.length + 1) |
| a[0] = lvl |
| for (var i = 0; i < arguments.length; i ++) { |
| a[i + 1] = arguments[i] |
| } |
| return this.log.apply(this, a) |
| }.bind(this) |
| this.disp[lvl] = disp |
| } |
| |
| log.prefixStyle = { fg: 'magenta' } |
| log.headingStyle = { fg: 'white', bg: 'black' } |
| |
| log.style = {} |
| log.levels = {} |
| log.disp = {} |
| log.addLevel('silly', -Infinity, { inverse: true }, 'sill') |
| log.addLevel('verbose', 1000, { fg: 'blue', bg: 'black' }, 'verb') |
| log.addLevel('info', 2000, { fg: 'green' }) |
| log.addLevel('http', 3000, { fg: 'green', bg: 'black' }) |
| log.addLevel('warn', 4000, { fg: 'black', bg: 'yellow' }, 'WARN') |
| log.addLevel('error', 5000, { fg: 'red', bg: 'black' }, 'ERR!') |
| log.addLevel('silent', Infinity) |
| |
| // allow 'error' prefix |
| log.on('error', function(){}) |