log.js 3.4 KB

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