123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327 |
- import Types from './types.js'
- import { isLogObj } from './utils/index.js'
- let paused = false
- const queue = []
- class Consola {
- constructor (options = {}) {
- this._reporters = options.reporters || []
- this._types = options.types || Types
- this.level = options.level !== undefined ? options.level : 3
- this._defaults = options.defaults || {}
- this._async = options.async !== undefined ? options.async : undefined
- this._stdout = options.stdout
- this._stderr = options.stderr
- this._mockFn = options.mockFn
- this._throttle = options.throttle || 1000
- this._throttleMin = options.throttleMin || 5
- // Create logger functions for current instance
- for (const type in this._types) {
- const defaults = {
- type,
- ...this._types[type],
- ...this._defaults
- }
- this[type] = this._wrapLogFn(defaults)
- this[type].raw = this._wrapLogFn(defaults, true)
- }
- // Use _mockFn if is set
- if (this._mockFn) {
- this.mockTypes()
- }
- // Keep serialized version of last log
- this._lastLogSerialized = undefined
- this._lastLog = undefined
- this._lastLogTime = undefined
- this._lastLogCount = 0
- this._throttleTimeout = undefined
- }
- get stdout () {
- return this._stdout || console._stdout // eslint-disable-line no-console
- }
- get stderr () {
- return this._stderr || console._stderr // eslint-disable-line no-console
- }
- create (options) {
- return new Consola(Object.assign({
- reporters: this._reporters,
- level: this.level,
- types: this._types,
- defaults: this._defaults,
- stdout: this._stdout,
- stderr: this._stderr,
- mockFn: this._mockFn
- }, options))
- }
- withDefaults (defaults) {
- return this.create({
- defaults: Object.assign({}, this._defaults, defaults)
- })
- }
- withTag (tag) {
- return this.withDefaults({
- tag: this._defaults.tag ? (this._defaults.tag + ':' + tag) : tag
- })
- }
- addReporter (reporter) {
- this._reporters.push(reporter)
- return this
- }
- removeReporter (reporter) {
- if (reporter) {
- const i = this._reporters.indexOf(reporter)
- if (i >= 0) {
- return this._reporters.splice(i, 1)
- }
- } else {
- this._reporters.splice(0)
- }
- return this
- }
- setReporters (reporters) {
- this._reporters = Array.isArray(reporters)
- ? reporters
- : [reporters]
- return this
- }
- wrapAll () {
- this.wrapConsole()
- this.wrapStd()
- }
- restoreAll () {
- this.restoreConsole()
- this.restoreStd()
- }
- wrapConsole () {
- for (const type in this._types) {
- // Backup original value
- if (!console['__' + type]) { // eslint-disable-line no-console
- console['__' + type] = console[type] // eslint-disable-line no-console
- }
- // Override
- console[type] = this[type].raw // eslint-disable-line no-console
- }
- }
- restoreConsole () {
- for (const type in this._types) {
- // Restore if backup is available
- if (console['__' + type]) { // eslint-disable-line no-console
- console[type] = console['__' + type] // eslint-disable-line no-console
- delete console['__' + type] // eslint-disable-line no-console
- }
- }
- }
- wrapStd () {
- this._wrapStream(this.stdout, 'log')
- this._wrapStream(this.stderr, 'log')
- }
- _wrapStream (stream, type) {
- if (!stream) {
- return
- }
- // Backup original value
- if (!stream.__write) {
- stream.__write = stream.write
- }
- // Override
- stream.write = (data) => {
- this[type].raw(String(data).trim())
- }
- }
- restoreStd () {
- this._restoreStream(this.stdout)
- this._restoreStream(this.stderr)
- }
- _restoreStream (stream) {
- if (!stream) {
- return
- }
- if (stream.__write) {
- stream.write = stream.__write
- delete stream.__write
- }
- }
- pauseLogs () {
- paused = true
- }
- resumeLogs () {
- paused = false
- // Process queue
- const _queue = queue.splice(0)
- for (const item of _queue) {
- item[0]._logFn(item[1], item[2])
- }
- }
- mockTypes (mockFn) {
- this._mockFn = mockFn || this._mockFn
- if (typeof this._mockFn !== 'function') {
- return
- }
- for (const type in this._types) {
- this[type] = this._mockFn(type, this._types[type]) || this[type]
- this[type].raw = this[type]
- }
- }
- _wrapLogFn (defaults, isRaw) {
- return (...args) => {
- if (paused) {
- queue.push([this, defaults, args, isRaw])
- return
- }
- return this._logFn(defaults, args, isRaw)
- }
- }
- _logFn (defaults, args, isRaw) {
- if (defaults.level > this.level) {
- return this._async ? Promise.resolve(false) : false
- }
- // Construct a new log object
- const logObj = Object.assign({
- date: new Date(),
- args: []
- }, defaults)
- // Consume arguments
- if (!isRaw && args.length === 1 && isLogObj(args[0])) {
- Object.assign(logObj, args[0])
- } else {
- logObj.args = Array.from(args)
- }
- // Aliases
- if (logObj.message) {
- logObj.args.unshift(logObj.message)
- delete logObj.message
- }
- if (logObj.additional) {
- if (!Array.isArray(logObj.additional)) {
- logObj.additional = logObj.additional.split('\n')
- }
- logObj.args.push('\n' + logObj.additional.join('\n'))
- delete logObj.additional
- }
- // Normalize type and tag to lowercase
- logObj.type = typeof logObj.type === 'string' ? logObj.type.toLowerCase() : ''
- logObj.tag = typeof logObj.tag === 'string' ? logObj.tag.toLowerCase() : ''
- // Resolve log
- /**
- * @param newLog false if the throttle expired and
- * we don't want to log a duplicate
- */
- const resolveLog = (newLog = false) => {
- const repeated = this._lastLogCount - this._throttleMin
- if (this._lastLog && repeated > 0) {
- const args = [...this._lastLog.args]
- if (repeated > 1) {
- args.push(`(repeated ${repeated} times)`)
- }
- this._log({ ...this._lastLog, args })
- this._lastLogCount = 1
- }
- // Log
- if (newLog) {
- this._lastLog = logObj
- if (this._async) {
- return this._logAsync(logObj)
- } else {
- this._log(logObj)
- }
- }
- }
- // Throttle
- clearTimeout(this._throttleTimeout)
- const diffTime = this._lastLogTime ? logObj.date - this._lastLogTime : 0
- this._lastLogTime = logObj.date
- if (diffTime < this._throttle) {
- try {
- const serializedLog = JSON.stringify([logObj.type, logObj.tag, logObj.args])
- const isSameLog = this._lastLogSerialized === serializedLog
- this._lastLogSerialized = serializedLog
- if (isSameLog) {
- this._lastLogCount++
- if (this._lastLogCount > this._throttleMin) {
- // Auto-resolve when throttle is timed out
- this._throttleTimeout = setTimeout(resolveLog, this._throttle)
- return // SPAM!
- }
- }
- } catch (_) {
- // Circular References
- }
- }
- resolveLog(true)
- }
- _log (logObj) {
- for (const reporter of this._reporters) {
- reporter.log(logObj, {
- async: false,
- stdout: this.stdout,
- stderr: this.stderr
- })
- }
- }
- _logAsync (logObj) {
- return Promise.all(
- this._reporters.map(reporter => reporter.log(logObj, {
- async: true,
- stdout: this.stdout,
- stderr: this.stderr
- }))
- )
- }
- }
- // Legacy support
- Consola.prototype.add = Consola.prototype.addReporter
- Consola.prototype.remove = Consola.prototype.removeReporter
- Consola.prototype.clear = Consola.prototype.removeReporter
- Consola.prototype.withScope = Consola.prototype.withTag
- Consola.prototype.mock = Consola.prototype.mockTypes
- Consola.prototype.pause = Consola.prototype.pauseLogs
- Consola.prototype.resume = Consola.prototype.resumeLogs
- // Export class
- export default Consola
|