2 var Progress = require('are-we-there-yet')
3 var Gauge = require('gauge')
4 var EE = require('events').EventEmitter
5 var log = exports = module.exports = new EE()
6 var util = require('util')
8 var setBlocking = require('set-blocking')
9 var consoleControl = require('console-control-strings')
12 var stream = process.stderr
13 Object.defineProperty(log, 'stream', {
14 set: function (newStream) {
16 if (this.gauge) this.gauge.setWriteTo(stream, stream)
23 // by default, decide based on tty-ness.
25 log.useColor = function () {
26 return colorEnabled != null ? colorEnabled : stream.isTTY
29 log.enableColor = function () {
31 this.gauge.setTheme({hasColor: colorEnabled, hasUnicode: unicodeEnabled})
33 log.disableColor = function () {
35 this.gauge.setTheme({hasColor: colorEnabled, hasUnicode: unicodeEnabled})
41 log.gauge = new Gauge(stream, {
42 enabled: false, // no progress bars unless asked
43 theme: {hasColor: log.useColor()},
45 {type: 'progressbar', length: 20},
46 {type: 'activityIndicator', kerning: 1, length: 1},
47 {type: 'section', default: ''},
49 {type: 'logline', kerning: 1, default: ''}
53 log.tracker = new Progress.TrackerGroup()
55 // we track this separately as we may need to temporarily disable the
56 // display of the status bar for our own loggy purposes.
57 log.progressEnabled = log.gauge.isEnabled()
61 log.enableUnicode = function () {
63 this.gauge.setTheme({hasColor: this.useColor(), hasUnicode: unicodeEnabled})
66 log.disableUnicode = function () {
67 unicodeEnabled = false
68 this.gauge.setTheme({hasColor: this.useColor(), hasUnicode: unicodeEnabled})
71 log.setGaugeThemeset = function (themes) {
72 this.gauge.setThemeset(themes)
75 log.setGaugeTemplate = function (template) {
76 this.gauge.setTemplate(template)
79 log.enableProgress = function () {
80 if (this.progressEnabled) return
81 this.progressEnabled = true
82 this.tracker.on('change', this.showProgress)
83 if (this._pause) return
87 log.disableProgress = function () {
88 if (!this.progressEnabled) return
89 this.progressEnabled = false
90 this.tracker.removeListener('change', this.showProgress)
94 var trackerConstructors = ['newGroup', 'newItem', 'newStream']
96 var mixinLog = function (tracker) {
97 // mixin the public methods from log into the tracker
98 // (except: conflicts and one's we handle specially)
99 Object.keys(log).forEach(function (P) {
100 if (P[0] === '_') return
101 if (trackerConstructors.filter(function (C) { return C === P }).length) return
102 if (tracker[P]) return
103 if (typeof log[P] !== 'function') return
105 tracker[P] = function () {
106 return func.apply(log, arguments)
109 // if the new tracker is a group, make sure any subtrackers get
111 if (tracker instanceof Progress.TrackerGroup) {
112 trackerConstructors.forEach(function (C) {
113 var func = tracker[C]
114 tracker[C] = function () { return mixinLog(func.apply(tracker, arguments)) }
120 // Add tracker constructors to the top level log object
121 trackerConstructors.forEach(function (C) {
122 log[C] = function () { return mixinLog(this.tracker[C].apply(this.tracker, arguments)) }
125 log.clearProgress = function (cb) {
126 if (!this.progressEnabled) return cb && process.nextTick(cb)
130 log.showProgress = function (name, completed) {
131 if (!this.progressEnabled) return
133 if (name) values.section = name
134 var last = log.record[log.record.length - 1]
136 values.subsection = last.prefix
137 var disp = log.disp[last.level] || last.level
138 var logline = this._format(disp, log.style[last.level])
139 if (last.prefix) logline += ' ' + this._format(last.prefix, this.prefixStyle)
140 logline += ' ' + last.message.split(/\r?\n/)[0]
141 values.logline = logline
143 values.completed = completed || this.tracker.completed()
144 this.gauge.show(values)
145 }.bind(log) // bind for use in tracker's on-change listener
147 // temporarily stop emitting, but don't drop
148 log.pause = function () {
150 if (this.progressEnabled) this.gauge.disable()
153 log.resume = function () {
154 if (!this._paused) return
159 b.forEach(function (m) {
162 if (this.progressEnabled) this.gauge.enable()
169 log.maxRecordSize = 10000
170 log.log = function (lvl, prefix, message) {
171 var l = this.levels[lvl]
172 if (l === undefined) {
173 return this.emit('error', new Error(util.format(
174 'Undefined log level: %j', lvl)))
177 var a = new Array(arguments.length - 2)
179 for (var i = 2; i < arguments.length; i++) {
180 var arg = a[i - 2] = arguments[i]
182 // resolve stack traces to a plain string.
183 if (typeof arg === 'object' && arg &&
184 (arg instanceof Error) && arg.stack) {
185 arg.stack = stack = arg.stack + ''
188 if (stack) a.unshift(stack + '\n')
189 message = util.format.apply(util, a)
193 prefix: String(prefix || ''),
198 this.emit('log.' + lvl, m)
199 if (m.prefix) this.emit(m.prefix, m)
202 var mrs = this.maxRecordSize
203 var n = this.record.length - mrs
205 var newSize = Math.floor(mrs * 0.9)
206 this.record = this.record.slice(-1 * newSize)
212 log.emitLog = function (m) {
217 if (this.progressEnabled) this.gauge.pulse(m.prefix)
218 var l = this.levels[m.level]
219 if (l === undefined) return
220 if (l < this.levels[this.level]) return
221 if (l > 0 && !isFinite(l)) return
223 // If 'disp' is null or undefined, use the lvl as a default
224 // Allows: '', 0 as valid disp
225 var disp = log.disp[m.level] != null ? log.disp[m.level] : m.level
227 m.message.split(/\r?\n/).forEach(function (line) {
229 this.write(this.heading, this.headingStyle)
232 this.write(disp, log.style[m.level])
233 var p = m.prefix || ''
234 if (p) this.write(' ')
235 this.write(p, this.prefixStyle)
236 this.write(' ' + line + '\n')
241 log._format = function (msg, style) {
245 if (this.useColor()) {
248 if (style.fg) settings.push(style.fg)
249 if (style.bg) settings.push('bg' + style.bg[0].toUpperCase() + style.bg.slice(1))
250 if (style.bold) settings.push('bold')
251 if (style.underline) settings.push('underline')
252 if (style.inverse) settings.push('inverse')
253 if (settings.length) output += consoleControl.color(settings)
254 if (style.beep) output += consoleControl.beep()
257 if (this.useColor()) {
258 output += consoleControl.color('reset')
263 log.write = function (msg, style) {
266 stream.write(this._format(msg, style))
269 log.addLevel = function (lvl, n, style, disp) {
270 // If 'disp' is null or undefined, use the lvl as a default
271 if (disp == null) disp = lvl
273 this.style[lvl] = style
275 this[lvl] = function () {
276 var a = new Array(arguments.length + 1)
278 for (var i = 0; i < arguments.length; i++) {
279 a[i + 1] = arguments[i]
281 return this.log.apply(this, a)
284 this.disp[lvl] = disp
287 log.prefixStyle = { fg: 'magenta' }
288 log.headingStyle = { fg: 'white', bg: 'black' }
293 log.addLevel('silly', -Infinity, { inverse: true }, 'sill')
294 log.addLevel('verbose', 1000, { fg: 'blue', bg: 'black' }, 'verb')
295 log.addLevel('info', 2000, { fg: 'green' })
296 log.addLevel('http', 3000, { fg: 'green', bg: 'black' })
297 log.addLevel('warn', 4000, { fg: 'black', bg: 'yellow' }, 'WARN')
298 log.addLevel('error', 5000, { fg: 'red', bg: 'black' }, 'ERR!')
299 log.addLevel('silent', Infinity)
301 // allow 'error' prefix
302 log.on('error', function () {})