|
|
|
'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) {
|
|
|
|
if (!this.progressEnabled) return
|
|
|
|
this.gauge.show(name, this.tracker.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)
|