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.
403 lines
8.8 KiB
403 lines
8.8 KiB
'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 () {})
|
|
|