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
			
		
		
			
		
	
	
					404 lines
				
				8.8 KiB
			| 
											3 years 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 () {}) |