index.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. /**
  2. * Module dependencies.
  3. */
  4. 'use strict'
  5. const Counter = require('passthrough-counter')
  6. const humanize = require('humanize-number')
  7. const bytes = require('bytes')
  8. const chalk = require('chalk')
  9. const util = require('util')
  10. /**
  11. * Expose logger.
  12. */
  13. module.exports = dev
  14. /**
  15. * Color map.
  16. */
  17. const colorCodes = {
  18. 7: 'magenta',
  19. 5: 'red',
  20. 4: 'yellow',
  21. 3: 'cyan',
  22. 2: 'green',
  23. 1: 'green',
  24. 0: 'yellow'
  25. }
  26. /**
  27. * Development logger.
  28. */
  29. function dev (opts) {
  30. // print to console helper.
  31. const print = (function () {
  32. let transporter
  33. if (typeof opts === 'function') {
  34. transporter = opts
  35. } else if (opts && opts.transporter) {
  36. transporter = opts.transporter
  37. }
  38. return function printFunc (...args) {
  39. const str = util.format(...args)
  40. if (transporter) {
  41. transporter(str, args)
  42. } else {
  43. console.log(...args)
  44. }
  45. }
  46. }())
  47. return async function logger (ctx, next) {
  48. // request
  49. const start = ctx[Symbol.for('request-received.startTime')] ? ctx[Symbol.for('request-received.startTime')].getTime() : Date.now()
  50. print(' ' + chalk.gray('<--') +
  51. ' ' + chalk.bold('%s') +
  52. ' ' + chalk.gray('%s'),
  53. ctx.method,
  54. ctx.originalUrl)
  55. try {
  56. await next()
  57. } catch (err) {
  58. // log uncaught downstream errors
  59. log(print, ctx, start, null, err)
  60. throw err
  61. }
  62. // calculate the length of a streaming response
  63. // by intercepting the stream with a counter.
  64. // only necessary if a content-length header is currently not set.
  65. const length = ctx.response.length
  66. const body = ctx.body
  67. let counter
  68. if (length == null && body && body.readable) {
  69. ctx.body = body
  70. .pipe(counter = Counter())
  71. .on('error', ctx.onerror)
  72. }
  73. // log when the response is finished or closed,
  74. // whichever happens first.
  75. const res = ctx.res
  76. const onfinish = done.bind(null, 'finish')
  77. const onclose = done.bind(null, 'close')
  78. res.once('finish', onfinish)
  79. res.once('close', onclose)
  80. function done (event) {
  81. res.removeListener('finish', onfinish)
  82. res.removeListener('close', onclose)
  83. log(print, ctx, start, counter ? counter.length : length, null, event)
  84. }
  85. }
  86. }
  87. /**
  88. * Log helper.
  89. */
  90. function log (print, ctx, start, len, err, event) {
  91. // get the status code of the response
  92. const status = err
  93. ? (err.isBoom ? err.output.statusCode : err.status || 500)
  94. : (ctx.status || 404)
  95. // set the color of the status code;
  96. const s = status / 100 | 0
  97. // eslint-disable-next-line
  98. const color = colorCodes.hasOwnProperty(s) ? colorCodes[s] : colorCodes[0]
  99. // get the human readable response length
  100. let length
  101. if (~[204, 205, 304].indexOf(status)) {
  102. length = ''
  103. } else if (len == null) {
  104. length = '-'
  105. } else {
  106. length = bytes(len).toLowerCase()
  107. }
  108. const upstream = err ? chalk.red('xxx')
  109. : event === 'close' ? chalk.yellow('-x-')
  110. : chalk.gray('-->')
  111. print(' ' + upstream +
  112. ' ' + chalk.bold('%s') +
  113. ' ' + chalk.gray('%s') +
  114. ' ' + chalk[color]('%s') +
  115. ' ' + chalk.gray('%s') +
  116. ' ' + chalk.gray('%s'),
  117. ctx.method,
  118. ctx.originalUrl,
  119. status,
  120. time(start),
  121. length)
  122. }
  123. /**
  124. * Show the response time in a human readable format.
  125. * In milliseconds if less than 10 seconds,
  126. * in seconds otherwise.
  127. */
  128. function time (start) {
  129. const delta = Date.now() - start
  130. return humanize(delta < 10000
  131. ? delta + 'ms'
  132. : Math.round(delta / 1000) + 's')
  133. }