text: /*!
   * morgan
   * Copyright(c) 2010 Sencha Inc.
   * Copyright(c) 2011 TJ Holowaychuk
   * Copyright(c) 2014 Jonathan Ong
   * Copyright(c) 2014-2017 Douglas Christopher Wilson
   * MIT Licensed
   */
  
  'use strict'
  
  /**
   * Module exports.
   * @public
   */
  
  module.exports = morgan
  module.exports.compile = compile
  module.exports.format = format
  module.exports.token = token
  
  /**
   * Module dependencies.
   * @private
   */
  
  var auth = require('basic-auth')
  var debug = require('debug')('morgan')
  var deprecate = require('depd')('morgan')
  var onFinished = require('on-finished')
  var onHeaders = require('on-headers')
  
  /**
   * Array of CLF month names.
   * @private
   */
  
  var CLF_MONTH = [
    'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
    'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
  ]
  
  /**
   * Default log buffer duration.
   * @private
   */
  
  var DEFAULT_BUFFER_DURATION = 1000
  
  /**
   * Create a logger middleware.
   *
   * @public
   * @param {String|Function} format
   * @param {Object} [options]
   * @return {Function} middleware
   */
  
  function morgan (format, options) {
    var fmt = format
    var opts = options || {}
  
    if (format && typeof format === 'object') {
      opts = format
      fmt = opts.format || 'default'
  
      // smart deprecation message
      deprecate('morgan(options): use morgan(' + (typeof fmt === 'string' ? JSON.stringify(fmt) : 'format') + ', options) instead')
    }
  
    if (fmt === undefined) {
      deprecate('undefined format: specify a format')
    }
  
    // output on request instead of response
    var immediate = opts.immediate
  
    // check if log entry should be skipped
    var skip = opts.skip || false
  
    // format function
    var formatLine = typeof fmt !== 'function'
      ? getFormatFunction(fmt)
      : fmt
  
    // stream
    var buffer = opts.buffer
    var stream = opts.stream || process.stdout
  
    // buffering support
    if (buffer) {
      deprecate('buffer option')
  
      // flush interval
      var interval = typeof buffer !== 'number'
        ? DEFAULT_BUFFER_DURATION
        : buffer
  
      // swap the stream
      stream = createBufferStream(stream, interval)
    }
  
    return function logger (req, res, next) {
      // request data
      req._startAt = undefined
      req._startTime = undefined
      req._remoteAddress = getip(req)
  
      // response data
      res._startAt = undefined
      res._startTime = undefined
  
      // record request start
      recordStartTime.call(req)
  
      function logRequest () {
        if (skip !== false && skip(req, res)) {
          debug('skip request')
          return
        }
  
        var line = formatLine(morgan, req, res)
  
        if (line == null) {
          debug('skip line')
          return
        }
  
        debug('log request')
        stream.write(line + '\n')
      };
  
      if (immediate) {
        // immediate log
        logRequest()
      } else {
        // record response start
        onHeaders(res, recordStartTime)
  
        // log when response finished
        onFinished(res, logRequest)
      }
  
      next()
    }
  }
  
  /**
   * Apache combined log format.
   */
  
  morgan.format('combined', ':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"')
  
  /**
   * Apache common log format.
   */
  
  morgan.format('common', ':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length]')
  
  /**
   * Default format.
   */
  
  morgan.format('default', ':remote-addr - :remote-user [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"')
  deprecate.property(morgan, 'default', 'default format: use combined format')
  
  /**
   * Short format.
   */
  
  morgan.format('short', ':remote-addr :remote-user :method :url HTTP/:http-version :status :res[content-length] - :response-time ms')
  
  /**
   * Tiny format.
   */
  
  morgan.format('tiny', ':method :url :status :res[content-length] - :response-time ms')
  
  /**
   * JSON format.
   */
  
  morgan.format('json', function jsonFormatLine (tokens, req, res) {
    let severity = 'INFO'
    const status = tokens.status(req, res)
  
    if (status >= 500) severity = 'ERROR'
    else if (status >= 400) severity = 'WARNING'
  
    const httpRequest = {
      requestMethod: tokens.method(req, res),
      requestUrl: tokens.url(req, res),
      status,
      responseSize: tokens.res(req, res, 'content-length'),
      userAgent: tokens['user-agent'](req, res),
      remoteIp: tokens['remote-addr'](req, res),
      referer: tokens.referrer(req, res),
      protocol: `HTTP/${tokens['http-version'](req, res)}`
    }
  
    return JSON.stringify({
      severity,
      httpRequest,
      remote_user: tokens['remote-user'](req, res),
      timestamp: tokens.date(req, res, 'iso'),
      response_time: tokens['response-time'](req, res),
      total_time: tokens['total-time'](req, res)
    })
  })
  
  /**
   * dev (colored)
   */
  
  morgan.format('dev', function developmentFormatLine (tokens, req, res) {
    // get the status code if response written
    var status = headersSent(res)
      ? res.statusCode
      : undefined
  
    // get status color
    var color = status >= 500 ? 31 // red
      : status >= 400 ? 33 // yellow
        : status >= 300 ? 36 // cyan
          : status >= 200 ? 32 // green
            : 0 // no color
  
    // get colored function
    var fn = developmentFormatLine[color]
  
    if (!fn) {
      // compile
      fn = developmentFormatLine[color] = compile('\x1b[0m:method :url \x1b[' +
        color + 'm:status\x1b[0m :response-time ms - :res[content-length]\x1b[0m')
    }
  
    return fn(tokens, req, res)
  })
  
  /**
   * request url
   */
  
  morgan.token('url', function getUrlToken (req) {
    return req.originalUrl || req.url
  })
  
  /**
   * request method
   */
  
  morgan.token('method', function getMethodToken (req) {
    return req.method
  })
  
  /**
   * response time in milliseconds
   */
  
  morgan.token('response-time', function getResponseTimeToken (req, res, digits) {
    if (!req._startAt || !res._startAt) {
      // missing request and/or response start time
      return
    }
  
    // calculate diff
    var ms = (res._startAt[0] - req._startAt[0]) * 1e3 +
      (res._startAt[1] - req._startAt[1]) * 1e-6
  
    // return truncated value
    return ms.toFixed(digits === undefined ? 3 : digits)
  })
  
  /**
   * total time in milliseconds
   */
  
  morgan.token('total-time', function getTotalTimeToken (req, res, digits) {
    if (!req._startAt || !res._startAt) {
      // missing request and/or response start time
      return
    }
  
    // time elapsed from request start
    var elapsed = process.hrtime(req._startAt)
  
    // cover to milliseconds
    var ms = (elapsed[0] * 1e3) + (elapsed[1] * 1e-6)
  
    // return truncated value
    return ms.toFixed(digits === undefined ? 3 : digits)
  })
  
  /**
   * current date
   */
  
  morgan.token('date', function getDateToken (req, res, format) {
    var date = new Date()
  
    switch (format || 'web') {
      case 'clf':
        return clfdate(date)
      case 'iso':
        return date.toISOString()
      case 'web':
        return date.toUTCString()
    }
  })
  
  /**
   * response status code
   */
  
  morgan.token('status', function getStatusToken (req, res) {
    return headersSent(res)
      ? String(res.statusCode)
      : undefined
  })
  
  /**
   * normalized referrer
   */
  
  morgan.token('referrer', function getReferrerToken (req) {
    return req.headers.referer || req.headers.referrer
  })
  
  /**
   * remote address
   */
  
  morgan.token('remote-addr', getip)
  
  /**
   * remote user
   */
  
  morgan.token('remote-user', function getRemoteUserToken (req) {
    // parse basic credentials
    var credentials = auth(req)
  
    // return username
    return credentials
      ? credentials.name
      : undefined
  })
  
  /**
   * HTTP version
   */
  
  morgan.token('http-version', function getHttpVersionToken (req) {
    return req.httpVersionMajor + '.' + req.httpVersionMinor
  })
  
  /**
   * UA string
   */
  
  morgan.token('user-agent', function getUserAgentToken (req) {
    return req.headers['user-agent']
  })
  
  /**
   * request header
   */
  
  morgan.token('req', function getRequestToken (req, res, field) {
    // get header
    var header = req.headers[field.toLowerCase()]
  
    return Array.isArray(header)
      ? header.join(', ')
      : header
  })
  
  /**
   * response header
   */
  
  morgan.token('res', function getResponseHeader (req, res, field) {
    if (!headersSent(res)) {
      return undefined
    }
  
    // get header
    var header = res.getHeader(field)
  
    return Array.isArray(header)
      ? header.join(', ')
      : header
  })
  
  /**
   * Format a Date in the common log format.
   *
   * @private
   * @param {Date} dateTime
   * @return {string}
   */
  
  function clfdate (dateTime) {
    var date = dateTime.getUTCDate()
    var hour = dateTime.getUTCHours()
    var mins = dateTime.getUTCMinutes()
    var secs = dateTime.getUTCSeconds()
    var year = dateTime.getUTCFullYear()
  
    var month = CLF_MONTH[dateTime.getUTCMonth()]
  
    return pad2(date) + '/' + month + '/' + year +
      ':' + pad2(hour) + ':' + pad2(mins) + ':' + pad2(secs) +
      ' +0000'
  }
  
  /**
   * Compile a format string into a function.
   *
   * @param {string} format
   * @return {function}
   * @public
   */
  
  function compile (format) {
    if (typeof format !== 'string') {
      throw new TypeError('argument format must be a string')
    }
  
    var fmt = String(JSON.stringify(format))
    var js = '  "use strict"\n  return ' + fmt.replace(/:([-\w]{2,})(?:\[([^\]]+)\])?/g, function (_, name, arg) {
      var tokenArguments = 'req, res'
      var tokenFunction = 'tokens[' + String(JSON.stringify(name)) + ']'
  
      if (arg !== undefined) {
        tokenArguments += ', ' + String(JSON.stringify(arg))
      }
  
      return '" +\n    (' + tokenFunction + '(' + tokenArguments + ') || "-") + "'
    })
  
    // eslint-disable-next-line no-new-func
    return new Function('tokens, req, res', js)
  }
  
  /**
   * Create a basic buffering stream.
   *
   * @param {object} stream
   * @param {number} interval
   * @public
   */
  
  function createBufferStream (stream, interval) {
    var buf = []
    var timer = null
  
    // flush function
    function flush () {
      timer = null
      stream.write(buf.join(''))
      buf.length = 0
    }
  
    // write function
    function write (str) {
      if (timer === null) {
        timer = setTimeout(flush, interval)
      }
  
      buf.push(str)
    }
  
    // return a minimal "stream"
    return { write: write }
  }
  
  /**
   * Define a format with the given name.
   *
   * @param {string} name
   * @param {string|function} fmt
   * @public
   */
  
  function format (name, fmt) {
    morgan[name] = fmt
    return this
  }
  
  /**
   * Lookup and compile a named format function.
   *
   * @param {string} name
   * @return {function}
   * @public
   */
  
  function getFormatFunction (name) {
    // lookup format
    var fmt = morgan[name] || name || morgan.default
  
    // return compiled format
    return typeof fmt !== 'function'
      ? compile(fmt)
      : fmt
  }
  
  /**
   * Get request IP address.
   *
   * @private
   * @param {IncomingMessage} req
   * @return {string}
   */
  
  function getip (req) {
    return req.ip ||
      req._remoteAddress ||
      (req.connection && req.connection.remoteAddress) ||
      undefined
  }
  
  /**
   * Determine if the response headers have been sent.
   *
   * @param {object} res
   * @returns {boolean}
   * @private
   */
  
  function headersSent (res) {
    // istanbul ignore next: node.js 0.8 support
    return typeof res.headersSent !== 'boolean'
      ? Boolean(res._header)
      : res.headersSent
  }
  
  /**
   * Pad number to two digits.
   *
   * @private
   * @param {number} num
   * @return {string}
   */
  
  function pad2 (num) {
    var str = String(num)
  
    // istanbul ignore next: num is current datetime
    return (str.length === 1 ? '0' : '') + str
  }
  
  /**
   * Record the start time.
   * @private
   */
  
  function recordStartTime () {
    this._startAt = process.hrtime()
    this._startTime = new Date()
  }
  
  /**
   * Define a token function with the given name,
   * and callback fn(req, res).
   *
   * @param {string} name
   * @param {function} fn
   * @public
   */
  
  function token (name, fn) {
    morgan[name] = fn
    return this
  }
  