1'use strict' 2 3const procLog = require('proc-log') 4const { format } = require('util') 5 6// helper to emit log messages with a predefined prefix 7const logLevels = Object.keys(procLog).filter((k) => typeof procLog[k] === 'function') 8const withPrefix = (prefix) => logLevels.reduce((acc, level) => { 9 acc[level] = (...args) => procLog[level](prefix, ...args) 10 return acc 11}, {}) 12 13// very basic ansi color generator 14const COLORS = { 15 wrap: (str, colors) => { 16 const codes = colors.filter(c => typeof c === 'number') 17 return `\x1b[${codes.join(';')}m${str}\x1b[0m` 18 }, 19 inverse: 7, 20 fg: { 21 black: 30, 22 red: 31, 23 green: 32, 24 yellow: 33, 25 blue: 34, 26 magenta: 35, 27 cyan: 36, 28 white: 37 29 }, 30 bg: { 31 black: 40, 32 red: 41, 33 green: 42, 34 yellow: 43, 35 blue: 44, 36 magenta: 45, 37 cyan: 46, 38 white: 47 39 } 40} 41 42class Logger { 43 #buffer = [] 44 #paused = null 45 #level = null 46 #stream = null 47 48 // ordered from loudest to quietest 49 #levels = [{ 50 id: 'silly', 51 display: 'sill', 52 style: { inverse: true } 53 }, { 54 id: 'verbose', 55 display: 'verb', 56 style: { fg: 'cyan', bg: 'black' } 57 }, { 58 id: 'info', 59 style: { fg: 'green' } 60 }, { 61 id: 'http', 62 style: { fg: 'green', bg: 'black' } 63 }, { 64 id: 'notice', 65 style: { fg: 'cyan', bg: 'black' } 66 }, { 67 id: 'warn', 68 display: 'WARN', 69 style: { fg: 'black', bg: 'yellow' } 70 }, { 71 id: 'error', 72 display: 'ERR!', 73 style: { fg: 'red', bg: 'black' } 74 }] 75 76 constructor (stream) { 77 process.on('log', (...args) => this.#onLog(...args)) 78 this.#levels = new Map(this.#levels.map((level, index) => [level.id, { ...level, index }])) 79 this.level = 'info' 80 this.stream = stream 81 procLog.pause() 82 } 83 84 get stream () { 85 return this.#stream 86 } 87 88 set stream (stream) { 89 this.#stream = stream 90 } 91 92 get level () { 93 return this.#levels.get(this.#level) ?? null 94 } 95 96 set level (level) { 97 this.#level = this.#levels.get(level)?.id ?? null 98 } 99 100 isVisible (level) { 101 return this.level?.index <= this.#levels.get(level)?.index ?? -1 102 } 103 104 #onLog (...args) { 105 const [level] = args 106 107 if (level === 'pause') { 108 this.#paused = true 109 return 110 } 111 112 if (level === 'resume') { 113 this.#paused = false 114 this.#buffer.forEach((b) => this.#log(...b)) 115 this.#buffer.length = 0 116 return 117 } 118 119 if (this.#paused) { 120 this.#buffer.push(args) 121 return 122 } 123 124 this.#log(...args) 125 } 126 127 #color (str, { fg, bg, inverse }) { 128 if (!this.#stream?.isTTY) { 129 return str 130 } 131 132 return COLORS.wrap(str, [ 133 COLORS.fg[fg], 134 COLORS.bg[bg], 135 inverse && COLORS.inverse 136 ]) 137 } 138 139 #log (levelId, msgPrefix, ...args) { 140 if (!this.isVisible(levelId) || typeof this.#stream?.write !== 'function') { 141 return 142 } 143 144 const level = this.#levels.get(levelId) 145 146 const prefixParts = [ 147 this.#color('gyp', { fg: 'white', bg: 'black' }), 148 this.#color(level.display ?? level.id, level.style) 149 ] 150 if (msgPrefix) { 151 prefixParts.push(this.#color(msgPrefix, { fg: 'magenta' })) 152 } 153 154 const prefix = prefixParts.join(' ').trim() + ' ' 155 const lines = format(...args).split(/\r?\n/).map(l => prefix + l.trim()) 156 157 this.#stream.write(lines.join('\n') + '\n') 158 } 159} 160 161// used to suppress logs in tests 162const NULL_LOGGER = !!process.env.NODE_GYP_NULL_LOGGER 163 164module.exports = { 165 logger: new Logger(NULL_LOGGER ? null : process.stderr), 166 stdout: NULL_LOGGER ? () => {} : (...args) => console.log(...args), 167 withPrefix, 168 ...procLog 169} 170