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
						
					
					
				| '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 () {})
 |