123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522 |
- /*!
- * 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')
- /**
- * 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)
- })
- /**
- * 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) {
- 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)
- 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
- }
|