UNPKG

9.68 kBJavaScriptView Raw
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-2015 Douglas Christopher Wilson
7 * MIT Licensed
8 */
9
10'use strict'
11
12/**
13 * Module exports.
14 * @public
15 */
16
17module.exports = morgan
18module.exports.compile = compile
19module.exports.format = format
20module.exports.token = token
21
22/**
23 * Module dependencies.
24 * @private
25 */
26
27var auth = require('basic-auth')
28var debug = require('debug')('morgan')
29var deprecate = require('depd')('morgan')
30var onFinished = require('on-finished')
31var onHeaders = require('on-headers')
32
33/**
34 * Array of CLF month names.
35 * @private
36 */
37
38var clfmonth = [
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
48var defaultBufferDuration = 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
59function 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 ? defaultBufferDuration
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 (null == line) {
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
152morgan.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
158morgan.format('common', ':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length]')
159
160/**
161 * Default format.
162 */
163
164morgan.format('default', ':remote-addr - :remote-user [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"')
165deprecate.property(morgan, 'default', 'default format: use combined format')
166
167/**
168 * Short format.
169 */
170
171morgan.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
177morgan.format('tiny', ':method :url :status :res[content-length] - :response-time ms')
178
179/**
180 * dev (colored)
181 */
182
183morgan.format('dev', function developmentFormatLine(tokens, req, res) {
184 // get the status code if response written
185 var status = res._header
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
212morgan.token('url', function getUrlToken(req) {
213 return req.originalUrl || req.url
214})
215
216/**
217 * request method
218 */
219
220morgan.token('method', function getMethodToken(req) {
221 return req.method;
222});
223
224/**
225 * response time in milliseconds
226 */
227
228morgan.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
246morgan.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
263morgan.token('status', function getStatusToken(req, res) {
264 return res._header
265 ? String(res.statusCode)
266 : undefined
267})
268
269/**
270 * normalized referrer
271 */
272
273morgan.token('referrer', function getReferrerToken(req) {
274 return req.headers['referer'] || req.headers['referrer'];
275});
276
277/**
278 * remote address
279 */
280
281morgan.token('remote-addr', getip)
282
283/**
284 * remote user
285 */
286
287morgan.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
301morgan.token('http-version', function getHttpVersionToken(req) {
302 return req.httpVersionMajor + '.' + req.httpVersionMinor
303})
304
305/**
306 * UA string
307 */
308
309morgan.token('user-agent', function getUserAgentToken(req) {
310 return req.headers['user-agent'];
311});
312
313/**
314 * request header
315 */
316
317morgan.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
330morgan.token('res', function getResponseTime(req, res, field) {
331 if (!res._header) {
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
351function 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 = clfmonth[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
373function compile(format) {
374 if (typeof format !== 'string') {
375 throw new TypeError('argument format must be a string')
376 }
377
378 var fmt = format.replace(/"/g, '\\"')
379 var js = ' return "' + fmt.replace(/:([-\w]{2,})(?:\[([^\]]+)\])?/g, function(_, name, arg) {
380 return '"\n + (tokens["' + name + '"](req, res, ' + String(JSON.stringify(arg)) + ') || "-") + "'
381 }) + '";'
382
383 return new Function('tokens, req, res', js)
384}
385
386/**
387 * Create a basic buffering stream.
388 *
389 * @param {object} stream
390 * @param {number} interval
391 * @public
392 */
393
394function createBufferStream(stream, interval) {
395 var buf = []
396 var timer = null
397
398 // flush function
399 function flush() {
400 timer = null
401 stream.write(buf.join(''))
402 buf.length = 0
403 }
404
405 // write function
406 function write(str) {
407 if (timer === null) {
408 timer = setTimeout(flush, interval)
409 }
410
411 buf.push(str)
412 }
413
414 // return a minimal "stream"
415 return { write: write }
416}
417
418/**
419 * Define a format with the given name.
420 *
421 * @param {string} name
422 * @param {string|function} fmt
423 * @public
424 */
425
426function format(name, fmt) {
427 morgan[name] = fmt
428 return this
429}
430
431/**
432 * Lookup and compile a named format function.
433 *
434 * @param {string} name
435 * @return {function}
436 * @public
437 */
438
439function getFormatFunction(name) {
440 // lookup format
441 var fmt = morgan[name] || name || morgan.default
442
443 // return compiled format
444 return typeof fmt !== 'function'
445 ? compile(fmt)
446 : fmt
447}
448
449/**
450 * Get request IP address.
451 *
452 * @private
453 * @param {IncomingMessage} req
454 * @return {string}
455 */
456
457function getip(req) {
458 return req.ip
459 || req._remoteAddress
460 || (req.connection && req.connection.remoteAddress)
461 || undefined;
462}
463
464/**
465 * Pad number to two digits.
466 *
467 * @private
468 * @param {number} num
469 * @return {string}
470 */
471
472function pad2(num) {
473 var str = String(num)
474
475 return (str.length === 1 ? '0' : '')
476 + str
477}
478
479/**
480 * Record the start time.
481 * @private
482 */
483
484function recordStartTime() {
485 this._startAt = process.hrtime()
486 this._startTime = new Date()
487}
488
489/**
490 * Define a token function with the given name,
491 * and callback fn(req, res).
492 *
493 * @param {string} name
494 * @param {function} fn
495 * @public
496 */
497
498function token(name, fn) {
499 morgan[name] = fn
500 return this
501}