3 * Copyright(c) 2010 Sencha Inc.
4 * Copyright(c) 2011 TJ Holowaychuk
5 * Copyright(c) 2014 Jonathan Ong
6 * Copyright(c) 2014-2017 Douglas Christopher Wilson
17 module.exports = morgan
18 module.exports.compile = compile
19 module.exports.format = format
20 module.exports.token = token
23 * Module dependencies.
27 var auth = require('basic-auth')
28 var debug = require('debug')('morgan')
29 var deprecate = require('depd')('morgan')
30 var onFinished = require('on-finished')
31 var onHeaders = require('on-headers')
34 * Array of CLF month names.
39 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
40 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
44 * Default log buffer duration.
48 var DEFAULT_BUFFER_DURATION = 1000
51 * Create a logger middleware.
54 * @param {String|Function} format
55 * @param {Object} [options]
56 * @return {Function} middleware
59 function morgan (format, options) {
61 var opts = options || {}
63 if (format && typeof format === 'object') {
65 fmt = opts.format || 'default'
67 // smart deprecation message
68 deprecate('morgan(options): use morgan(' + (typeof fmt === 'string' ? JSON.stringify(fmt) : 'format') + ', options) instead')
71 if (fmt === undefined) {
72 deprecate('undefined format: specify a format')
75 // output on request instead of response
76 var immediate = opts.immediate
78 // check if log entry should be skipped
79 var skip = opts.skip || false
82 var formatLine = typeof fmt !== 'function'
83 ? getFormatFunction(fmt)
87 var buffer = opts.buffer
88 var stream = opts.stream || process.stdout
92 deprecate('buffer option')
95 var interval = typeof buffer !== 'number'
96 ? DEFAULT_BUFFER_DURATION
100 stream = createBufferStream(stream, interval)
103 return function logger (req, res, next) {
105 req._startAt = undefined
106 req._startTime = undefined
107 req._remoteAddress = getip(req)
110 res._startAt = undefined
111 res._startTime = undefined
113 // record request start
114 recordStartTime.call(req)
116 function logRequest () {
117 if (skip !== false && skip(req, res)) {
118 debug('skip request')
122 var line = formatLine(morgan, req, res)
130 stream.write(line + '\n')
137 // record response start
138 onHeaders(res, recordStartTime)
140 // log when response finished
141 onFinished(res, logRequest)
149 * Apache combined log format.
152 morgan.format('combined', ':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"')
155 * Apache common log format.
158 morgan.format('common', ':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length]')
164 morgan.format('default', ':remote-addr - :remote-user [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"')
165 deprecate.property(morgan, 'default', 'default format: use combined format')
171 morgan.format('short', ':remote-addr :remote-user :method :url HTTP/:http-version :status :res[content-length] - :response-time ms')
177 morgan.format('tiny', ':method :url :status :res[content-length] - :response-time ms')
183 morgan.format('dev', function developmentFormatLine (tokens, req, res) {
184 // get the status code if response written
185 var status = headersSent(res)
190 var color = status >= 500 ? 31 // red
191 : status >= 400 ? 33 // yellow
192 : status >= 300 ? 36 // cyan
193 : status >= 200 ? 32 // green
196 // get colored function
197 var fn = developmentFormatLine[color]
201 fn = developmentFormatLine[color] = compile('\x1b[0m:method :url \x1b[' +
202 color + 'm:status \x1b[0m:response-time ms - :res[content-length]\x1b[0m')
205 return fn(tokens, req, res)
212 morgan.token('url', function getUrlToken (req) {
213 return req.originalUrl || req.url
220 morgan.token('method', function getMethodToken (req) {
225 * response time in milliseconds
228 morgan.token('response-time', function getResponseTimeToken (req, res, digits) {
229 if (!req._startAt || !res._startAt) {
230 // missing request and/or response start time
235 var ms = (res._startAt[0] - req._startAt[0]) * 1e3 +
236 (res._startAt[1] - req._startAt[1]) * 1e-6
238 // return truncated value
239 return ms.toFixed(digits === undefined ? 3 : digits)
246 morgan.token('date', function getDateToken (req, res, format) {
247 var date = new Date()
249 switch (format || 'web') {
253 return date.toISOString()
255 return date.toUTCString()
260 * response status code
263 morgan.token('status', function getStatusToken (req, res) {
264 return headersSent(res)
265 ? String(res.statusCode)
270 * normalized referrer
273 morgan.token('referrer', function getReferrerToken (req) {
274 return req.headers['referer'] || req.headers['referrer']
281 morgan.token('remote-addr', getip)
287 morgan.token('remote-user', function getRemoteUserToken (req) {
288 // parse basic credentials
289 var credentials = auth(req)
301 morgan.token('http-version', function getHttpVersionToken (req) {
302 return req.httpVersionMajor + '.' + req.httpVersionMinor
309 morgan.token('user-agent', function getUserAgentToken (req) {
310 return req.headers['user-agent']
317 morgan.token('req', function getRequestToken (req, res, field) {
319 var header = req.headers[field.toLowerCase()]
321 return Array.isArray(header)
330 morgan.token('res', function getResponseHeader (req, res, field) {
331 if (!headersSent(res)) {
336 var header = res.getHeader(field)
338 return Array.isArray(header)
344 * Format a Date in the common log format.
347 * @param {Date} dateTime
351 function clfdate (dateTime) {
352 var date = dateTime.getUTCDate()
353 var hour = dateTime.getUTCHours()
354 var mins = dateTime.getUTCMinutes()
355 var secs = dateTime.getUTCSeconds()
356 var year = dateTime.getUTCFullYear()
358 var month = CLF_MONTH[dateTime.getUTCMonth()]
360 return pad2(date) + '/' + month + '/' + year +
361 ':' + pad2(hour) + ':' + pad2(mins) + ':' + pad2(secs) +
366 * Compile a format string into a function.
368 * @param {string} format
373 function compile (format) {
374 if (typeof format !== 'string') {
375 throw new TypeError('argument format must be a string')
378 var fmt = String(JSON.stringify(format))
379 var js = ' "use strict"\n return ' + fmt.replace(/:([-\w]{2,})(?:\[([^\]]+)\])?/g, function (_, name, arg) {
380 var tokenArguments = 'req, res'
381 var tokenFunction = 'tokens[' + String(JSON.stringify(name)) + ']'
383 if (arg !== undefined) {
384 tokenArguments += ', ' + String(JSON.stringify(arg))
387 return '" +\n (' + tokenFunction + '(' + tokenArguments + ') || "-") + "'
390 // eslint-disable-next-line no-new-func
391 return new Function('tokens, req, res', js)
395 * Create a basic buffering stream.
397 * @param {object} stream
398 * @param {number} interval
402 function createBufferStream (stream, interval) {
409 stream.write(buf.join(''))
414 function write (str) {
415 if (timer === null) {
416 timer = setTimeout(flush, interval)
422 // return a minimal "stream"
423 return { write: write }
427 * Define a format with the given name.
429 * @param {string} name
430 * @param {string|function} fmt
434 function format (name, fmt) {
440 * Lookup and compile a named format function.
442 * @param {string} name
447 function getFormatFunction (name) {
449 var fmt = morgan[name] || name || morgan.default
451 // return compiled format
452 return typeof fmt !== 'function'
458 * Get request IP address.
461 * @param {IncomingMessage} req
465 function getip (req) {
467 req._remoteAddress ||
468 (req.connection && req.connection.remoteAddress) ||
473 * Determine if the response headers have been sent.
475 * @param {object} res
480 function headersSent (res) {
481 return typeof res.headersSent !== 'boolean'
482 ? Boolean(res._header)
487 * Pad number to two digits.
490 * @param {number} num
494 function pad2 (num) {
495 var str = String(num)
497 return (str.length === 1 ? '0' : '') + str
501 * Record the start time.
505 function recordStartTime () {
506 this._startAt = process.hrtime()
507 this._startTime = new Date()
511 * Define a token function with the given name,
512 * and callback fn(req, res).
514 * @param {string} name
515 * @param {function} fn
519 function token (name, fn) {