second
[josuexyz/.git] / node_modules / morgan / index.js
1 /*!
2  * morgan
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
7  * MIT Licensed
8  */
9
10 'use strict'
11
12 /**
13  * Module exports.
14  * @public
15  */
16
17 module.exports = morgan
18 module.exports.compile = compile
19 module.exports.format = format
20 module.exports.token = token
21
22 /**
23  * Module dependencies.
24  * @private
25  */
26
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')
32
33 /**
34  * Array of CLF month names.
35  * @private
36  */
37
38 var CLF_MONTH = [
39   'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
40   'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
41 ]
42
43 /**
44  * Default log buffer duration.
45  * @private
46  */
47
48 var DEFAULT_BUFFER_DURATION = 1000
49
50 /**
51  * Create a logger middleware.
52  *
53  * @public
54  * @param {String|Function} format
55  * @param {Object} [options]
56  * @return {Function} middleware
57  */
58
59 function morgan (format, options) {
60   var fmt = format
61   var opts = options || {}
62
63   if (format && typeof format === 'object') {
64     opts = format
65     fmt = opts.format || 'default'
66
67     // smart deprecation message
68     deprecate('morgan(options): use morgan(' + (typeof fmt === 'string' ? JSON.stringify(fmt) : 'format') + ', options) instead')
69   }
70
71   if (fmt === undefined) {
72     deprecate('undefined format: specify a format')
73   }
74
75   // output on request instead of response
76   var immediate = opts.immediate
77
78   // check if log entry should be skipped
79   var skip = opts.skip || false
80
81   // format function
82   var formatLine = typeof fmt !== 'function'
83     ? getFormatFunction(fmt)
84     : fmt
85
86   // stream
87   var buffer = opts.buffer
88   var stream = opts.stream || process.stdout
89
90   // buffering support
91   if (buffer) {
92     deprecate('buffer option')
93
94     // flush interval
95     var interval = typeof buffer !== 'number'
96       ? DEFAULT_BUFFER_DURATION
97       : buffer
98
99     // swap the stream
100     stream = createBufferStream(stream, interval)
101   }
102
103   return function logger (req, res, next) {
104     // request data
105     req._startAt = undefined
106     req._startTime = undefined
107     req._remoteAddress = getip(req)
108
109     // response data
110     res._startAt = undefined
111     res._startTime = undefined
112
113     // record request start
114     recordStartTime.call(req)
115
116     function logRequest () {
117       if (skip !== false && skip(req, res)) {
118         debug('skip request')
119         return
120       }
121
122       var line = formatLine(morgan, req, res)
123
124       if (line == null) {
125         debug('skip line')
126         return
127       }
128
129       debug('log request')
130       stream.write(line + '\n')
131     };
132
133     if (immediate) {
134       // immediate log
135       logRequest()
136     } else {
137       // record response start
138       onHeaders(res, recordStartTime)
139
140       // log when response finished
141       onFinished(res, logRequest)
142     }
143
144     next()
145   }
146 }
147
148 /**
149  * Apache combined log format.
150  */
151
152 morgan.format('combined', ':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"')
153
154 /**
155  * Apache common log format.
156  */
157
158 morgan.format('common', ':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length]')
159
160 /**
161  * Default format.
162  */
163
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')
166
167 /**
168  * Short format.
169  */
170
171 morgan.format('short', ':remote-addr :remote-user :method :url HTTP/:http-version :status :res[content-length] - :response-time ms')
172
173 /**
174  * Tiny format.
175  */
176
177 morgan.format('tiny', ':method :url :status :res[content-length] - :response-time ms')
178
179 /**
180  * dev (colored)
181  */
182
183 morgan.format('dev', function developmentFormatLine (tokens, req, res) {
184   // get the status code if response written
185   var status = headersSent(res)
186     ? res.statusCode
187     : undefined
188
189   // get status color
190   var color = status >= 500 ? 31 // red
191     : status >= 400 ? 33 // yellow
192       : status >= 300 ? 36 // cyan
193         : status >= 200 ? 32 // green
194           : 0 // no color
195
196   // get colored function
197   var fn = developmentFormatLine[color]
198
199   if (!fn) {
200     // compile
201     fn = developmentFormatLine[color] = compile('\x1b[0m:method :url \x1b[' +
202       color + 'm:status \x1b[0m:response-time ms - :res[content-length]\x1b[0m')
203   }
204
205   return fn(tokens, req, res)
206 })
207
208 /**
209  * request url
210  */
211
212 morgan.token('url', function getUrlToken (req) {
213   return req.originalUrl || req.url
214 })
215
216 /**
217  * request method
218  */
219
220 morgan.token('method', function getMethodToken (req) {
221   return req.method
222 })
223
224 /**
225  * response time in milliseconds
226  */
227
228 morgan.token('response-time', function getResponseTimeToken (req, res, digits) {
229   if (!req._startAt || !res._startAt) {
230     // missing request and/or response start time
231     return
232   }
233
234   // calculate diff
235   var ms = (res._startAt[0] - req._startAt[0]) * 1e3 +
236     (res._startAt[1] - req._startAt[1]) * 1e-6
237
238   // return truncated value
239   return ms.toFixed(digits === undefined ? 3 : digits)
240 })
241
242 /**
243  * current date
244  */
245
246 morgan.token('date', function getDateToken (req, res, format) {
247   var date = new Date()
248
249   switch (format || 'web') {
250     case 'clf':
251       return clfdate(date)
252     case 'iso':
253       return date.toISOString()
254     case 'web':
255       return date.toUTCString()
256   }
257 })
258
259 /**
260  * response status code
261  */
262
263 morgan.token('status', function getStatusToken (req, res) {
264   return headersSent(res)
265     ? String(res.statusCode)
266     : undefined
267 })
268
269 /**
270  * normalized referrer
271  */
272
273 morgan.token('referrer', function getReferrerToken (req) {
274   return req.headers['referer'] || req.headers['referrer']
275 })
276
277 /**
278  * remote address
279  */
280
281 morgan.token('remote-addr', getip)
282
283 /**
284  * remote user
285  */
286
287 morgan.token('remote-user', function getRemoteUserToken (req) {
288   // parse basic credentials
289   var credentials = auth(req)
290
291   // return username
292   return credentials
293     ? credentials.name
294     : undefined
295 })
296
297 /**
298  * HTTP version
299  */
300
301 morgan.token('http-version', function getHttpVersionToken (req) {
302   return req.httpVersionMajor + '.' + req.httpVersionMinor
303 })
304
305 /**
306  * UA string
307  */
308
309 morgan.token('user-agent', function getUserAgentToken (req) {
310   return req.headers['user-agent']
311 })
312
313 /**
314  * request header
315  */
316
317 morgan.token('req', function getRequestToken (req, res, field) {
318   // get header
319   var header = req.headers[field.toLowerCase()]
320
321   return Array.isArray(header)
322     ? header.join(', ')
323     : header
324 })
325
326 /**
327  * response header
328  */
329
330 morgan.token('res', function getResponseHeader (req, res, field) {
331   if (!headersSent(res)) {
332     return undefined
333   }
334
335   // get header
336   var header = res.getHeader(field)
337
338   return Array.isArray(header)
339     ? header.join(', ')
340     : header
341 })
342
343 /**
344  * Format a Date in the common log format.
345  *
346  * @private
347  * @param {Date} dateTime
348  * @return {string}
349  */
350
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()
357
358   var month = CLF_MONTH[dateTime.getUTCMonth()]
359
360   return pad2(date) + '/' + month + '/' + year +
361     ':' + pad2(hour) + ':' + pad2(mins) + ':' + pad2(secs) +
362     ' +0000'
363 }
364
365 /**
366  * Compile a format string into a function.
367  *
368  * @param {string} format
369  * @return {function}
370  * @public
371  */
372
373 function compile (format) {
374   if (typeof format !== 'string') {
375     throw new TypeError('argument format must be a string')
376   }
377
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)) + ']'
382
383     if (arg !== undefined) {
384       tokenArguments += ', ' + String(JSON.stringify(arg))
385     }
386
387     return '" +\n    (' + tokenFunction + '(' + tokenArguments + ') || "-") + "'
388   })
389
390   // eslint-disable-next-line no-new-func
391   return new Function('tokens, req, res', js)
392 }
393
394 /**
395  * Create a basic buffering stream.
396  *
397  * @param {object} stream
398  * @param {number} interval
399  * @public
400  */
401
402 function createBufferStream (stream, interval) {
403   var buf = []
404   var timer = null
405
406   // flush function
407   function flush () {
408     timer = null
409     stream.write(buf.join(''))
410     buf.length = 0
411   }
412
413   // write function
414   function write (str) {
415     if (timer === null) {
416       timer = setTimeout(flush, interval)
417     }
418
419     buf.push(str)
420   }
421
422   // return a minimal "stream"
423   return { write: write }
424 }
425
426 /**
427  * Define a format with the given name.
428  *
429  * @param {string} name
430  * @param {string|function} fmt
431  * @public
432  */
433
434 function format (name, fmt) {
435   morgan[name] = fmt
436   return this
437 }
438
439 /**
440  * Lookup and compile a named format function.
441  *
442  * @param {string} name
443  * @return {function}
444  * @public
445  */
446
447 function getFormatFunction (name) {
448   // lookup format
449   var fmt = morgan[name] || name || morgan.default
450
451   // return compiled format
452   return typeof fmt !== 'function'
453     ? compile(fmt)
454     : fmt
455 }
456
457 /**
458  * Get request IP address.
459  *
460  * @private
461  * @param {IncomingMessage} req
462  * @return {string}
463  */
464
465 function getip (req) {
466   return req.ip ||
467     req._remoteAddress ||
468     (req.connection && req.connection.remoteAddress) ||
469     undefined
470 }
471
472 /**
473  * Determine if the response headers have been sent.
474  *
475  * @param {object} res
476  * @returns {boolean}
477  * @private
478  */
479
480 function headersSent (res) {
481   return typeof res.headersSent !== 'boolean'
482     ? Boolean(res._header)
483     : res.headersSent
484 }
485
486 /**
487  * Pad number to two digits.
488  *
489  * @private
490  * @param {number} num
491  * @return {string}
492  */
493
494 function pad2 (num) {
495   var str = String(num)
496
497   return (str.length === 1 ? '0' : '') + str
498 }
499
500 /**
501  * Record the start time.
502  * @private
503  */
504
505 function recordStartTime () {
506   this._startAt = process.hrtime()
507   this._startTime = new Date()
508 }
509
510 /**
511  * Define a token function with the given name,
512  * and callback fn(req, res).
513  *
514  * @param {string} name
515  * @param {function} fn
516  * @public
517  */
518
519 function token (name, fn) {
520   morgan[name] = fn
521   return this
522 }