You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

404 lines
8.8 KiB

1 month ago
'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 setBlocking = require('set-blocking')
var consoleControl = require('console-control-strings')
setBlocking(true)
var stream = process.stderr
Object.defineProperty(log, 'stream', {
set: function (newStream) {
stream = newStream
if (this.gauge) {
this.gauge.setWriteTo(stream, stream)
}
},
get: function () {
return stream
},
})
// by default, decide based on tty-ness.
var colorEnabled
log.useColor = function () {
return colorEnabled != null ? colorEnabled : stream.isTTY
}
log.enableColor = function () {
colorEnabled = true
this.gauge.setTheme({hasColor: colorEnabled, hasUnicode: unicodeEnabled})
}
log.disableColor = function () {
colorEnabled = false
this.gauge.setTheme({hasColor: colorEnabled, hasUnicode: unicodeEnabled})
}
// default level
log.level = 'info'
log.gauge = new Gauge(stream, {
enabled: false, // no progress bars unless asked
theme: {hasColor: log.useColor()},
template: [
{type: 'progressbar', length: 20},
{type: 'activityIndicator', kerning: 1, length: 1},
{type: 'section', default: ''},
':',
{type: 'logline', kerning: 1, default: ''},
],
})
log.tracker = new Progress.TrackerGroup()
// we track this separately as we may need to temporarily disable the
// display of the status bar for our own loggy purposes.
log.progressEnabled = log.gauge.isEnabled()
var unicodeEnabled
log.enableUnicode = function () {
unicodeEnabled = true
this.gauge.setTheme({hasColor: this.useColor(), hasUnicode: unicodeEnabled})
}
log.disableUnicode = function () {
unicodeEnabled = false
this.gauge.setTheme({hasColor: this.useColor(), hasUnicode: unicodeEnabled})
}
log.setGaugeThemeset = function (themes) {
this.gauge.setThemeset(themes)
}
log.setGaugeTemplate = function (template) {
this.gauge.setTemplate(template)
}
log.enableProgress = function () {
if (this.progressEnabled) {
return
}
this.progressEnabled = true
this.tracker.on('change', this.showProgress)
if (this._paused) {
return
}
this.gauge.enable()
}
log.disableProgress = function () {
if (!this.progressEnabled) {
return
}
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 (cb) {
if (!this.progressEnabled) {
return cb && process.nextTick(cb)
}
this.gauge.hide(cb)
}
log.showProgress = function (name, completed) {
if (!this.progressEnabled) {
return
}
var values = {}
if (name) {
values.section = name
}
var last = log.record[log.record.length - 1]
if (last) {
values.subsection = last.prefix
var disp = log.disp[last.level] || last.level
var logline = this._format(disp, log.style[last.level])
if (last.prefix) {
logline += ' ' + this._format(last.prefix, this.prefixStyle)
}
logline += ' ' + last.message.split(/\r?\n/)[0]
values.logline = logline
}
values.completed = completed || this.tracker.completed()
this.gauge.show(values)
}.bind(log) // bind for use in tracker's on-change listener
// temporarily stop emitting, but don't drop
log.pause = function () {
this._paused = true
if (this.progressEnabled) {
this.gauge.disable()
}
}
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.gauge.enable()
}
}
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 instanceof Error && arg.stack) {
Object.defineProperty(arg, 'stack', {
value: stack = arg.stack + '',
enumerable: true,
writable: true,
})
}
}
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
}
// If 'disp' is null or undefined, use the lvl as a default
// Allows: '', 0 as valid disp
var disp = log.disp[m.level] != null ? 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._format = function (msg, style) {
if (!stream) {
return
}
var output = ''
if (this.useColor()) {
style = style || {}
var settings = []
if (style.fg) {
settings.push(style.fg)
}
if (style.bg) {
settings.push('bg' + style.bg[0].toUpperCase() + style.bg.slice(1))
}
if (style.bold) {
settings.push('bold')
}
if (style.underline) {
settings.push('underline')
}
if (style.inverse) {
settings.push('inverse')
}
if (settings.length) {
output += consoleControl.color(settings)
}
if (style.beep) {
output += consoleControl.beep()
}
}
output += msg
if (this.useColor()) {
output += consoleControl.color('reset')
}
return output
}
log.write = function (msg, style) {
if (!stream) {
return
}
stream.write(this._format(msg, style))
}
log.addLevel = function (lvl, n, style, disp) {
// If 'disp' is null or undefined, use the lvl as a default
if (disp == null) {
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('timing', 2500, { fg: 'green', bg: 'black' })
log.addLevel('http', 3000, { fg: 'green', bg: 'black' })
log.addLevel('notice', 3500, { fg: 'blue', 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 () {})