All files index.js

98.67% Statements 148/150
88.64% Branches 39/44
100% Functions 8/8
98.67% Lines 148/150

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 1511x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 9x 9x 8x 8x 9x 9x 1x 9x 5x 5x 9x 9x 1x 9x 7x 7x 9x 9x 1x 9x 7x 7x 9x 9x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 10x 9x 9x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 11x 1x 1x 1x 11x 9x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 11x 11x 11x 11x 11x 11x 11x 11x 10x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 1x 11x 11x 11x     11x 1x 1x  
const { humanizeNumber } = require('./helpers')
const chalk = require('chalk')
const isStream = require('is-stream')
 
/**
 * Development style logger middleware for Mali.
 * @module @malijs/logger
 *
 * @param  {Object} options Options
 * @param  {Boolean} options.fullName To log full name from context, otherwise logs just the name.
 *                                    Default: <code>false</code>
 * @param  {Boolean | Function} options.timestamp Enables or disables the inclusion of a timestamp in the log message.
 *                                                If a function is supplied, it is passed a `Date` timestamp and it must synchronously return the string representation to be logged.
 *                                                There are predefined timestamp functions: `epochTime` (default), `epochTime`, and `isoTime`.
 * @param  {Boolean | Function} options.request Enables or disables the inclusion of request in the log message.
 *                                              By default `JSON.stringify` is used. If a function is supplied it is passed the request from the context.
 * @param  {Boolean | Function} options.response Enables or disables the inclusion of response in the log message.
 *                                               By default `JSON.stringify` is used. If a function is supplied it is passed the response from the context.
 *
 * @example
 * const logger = require('@malijs/logger')
 * app.use(logger())
 *
 * @example <caption>With timestamp</catoin>
 * const logger = require('@malijs/logger')
 * app.use(logger({timestamp: true}))
 *
 * @example <caption>With custom predefined timestamp</catoin>
 * const logger = require('@malijs/logger')
 * app.use(logger({timestamp: logger.isoTime}))
 *
 * @example <caption>With custom timestamp function</catoin>
 * const logger = require('@malijs/logger')
 * app.use(logger({ timestamp: date => `${date.toDateString()} ${date.toLocaleTimeString()}` }))
 */
function logger (options = {}) {
  if (typeof options.fullName !== 'boolean') {
    options.fullName = false
  }
 
  if (options.timestamp === true) {
    options.timestamp = epochTime
  } else if (typeof options.timestamp !== 'function') {
    options.timestamp = false
  }
 
  if (options.request === true) {
    options.request = JSON.stringify
  } else if (typeof options.request !== 'function') {
    options.request = false
  }
 
  if (options.response === true) {
    options.response = JSON.stringify
  } else if (typeof options.response !== 'function') {
    options.response = false
  }
 
  return function logger (ctx, next) {
    const start = new Date()
    const timestamp = options.timestamp ? ` ${options.timestamp(start)}` : ''
    const request = options.request ? ` ${options.request(ctx.req)}` : ''
 
    console
      .log(
        `  ${chalk.gray('-->')} ${chalk.bold('%s')}%s ${chalk.gray('%s')}`,
        timestamp,
        options.fullName ? ctx.fullName : ctx.name,
        request,
        ctx.type
      )
 
    return next().then(() => {
      if (!isStream(ctx.res) || (ctx.type !== 'response_stream' && ctx.type !== 'duplex')) {
        return log(options, ctx, start, null, null)
      }
 
      const res = ctx.res
      const onfinish = done.bind(null, 'finish')
      const onclose = done.bind(null, 'close')
      const onend = done.bind(null, 'end')
 
      res.once('finish', onfinish)
      res.once('close', onclose)
      res.once('end', onend)
 
      function done (event) {
        res.removeListener('finish', onfinish)
        res.removeListener('close', onclose)
        res.removeListener('end', onend)
        log(options, ctx, start, null, event)
      }
    }, (err) => {
      log(options, ctx, start, err)
      throw err
    })
  }
}
 
/**
 * Milliseconds since Unix epoch (Default)
 */
const epochTime = date => `${date.getTime()}`
 
/**
 * Seconds since Unix epoch
 */
const unixTime = date => `${Math.round(date.getTime() / 1000.0)}`
 
/**
 * Timestamp in ISO time.
 */
const isoTime = date => `${date.toISOString()}`
 
logger.epochTime = epochTime
logger.unixTime = unixTime
logger.isoTime = isoTime
 
function log (options, ctx, start, err, event) {
  const color = err ? 'red' : 'green'
 
  const timestamp = options.timestamp ? options.timestamp(start) : ''
 
  const response = options.response ? options.response(ctx.res) : ''
 
  const upstream = err ? chalk.red('<--')
    : event === 'close' ? chalk.yellow('-x-')
      : chalk.gray('<--')
 
  console.log('  ' + upstream +
    chalk.cyan('%s') +
    ' ' + chalk.bold('%s') +
    '%s' +
    ' ' + chalk.gray('%s') +
    ' ' + chalk[color]('%s'),
  timestamp ? ` ${timestamp}` : '',
  options.fullName ? ctx.fullName : ctx.name,
  response ? ` ${response}` : '',
  ctx.type,
  time(start))
}
 
function time (start) {
  const delta = new Date() - start
  return humanizeNumber(delta < 10000
    ? delta + 'ms'
    : Math.round(delta / 1000) + 's')
}
 
module.exports = logger