3 * Copyright(c) 2010 Sencha Inc.
4 * Copyright(c) 2011 TJ Holowaychuk
5 * Copyright(c) 2014 Jonathan Ong
6 * Copyright(c) 2014 Douglas Christopher Wilson
11 * Module dependencies.
15 var auth = require('basic-auth')
16 var debug = require('debug')('morgan')
17 var deprecate = require('depd')('morgan')
18 var onFinished = require('on-finished')
21 * Array of CLF month names.
26 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
27 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
31 * Default log buffer duration.
35 var defaultBufferDuration = 1000;
38 * Create a logger middleware.
41 * @param {String|Function} format
42 * @param {Object} [options]
43 * @return {Function} middleware
46 exports = module.exports = function morgan(format, options) {
47 if (typeof format === 'object') {
49 format = options.format || 'default'
51 // smart deprecation message
52 deprecate('morgan(options): use morgan(' + (typeof format === 'string' ? JSON.stringify(format) : 'format') + ', options) instead')
55 if (format === undefined) {
56 deprecate('undefined format: specify a format')
59 options = options || {}
61 // output on request instead of response
62 var immediate = options.immediate;
64 // check if log entry should be skipped
65 var skip = options.skip || function () { return false; };
68 var fmt = compile(exports[format] || format || exports.default)
71 var buffer = options.buffer
72 var stream = options.stream || process.stdout
76 deprecate('buffer option')
78 var realStream = stream
81 var interval = 'number' == typeof buffer
83 : defaultBufferDuration
86 var flush = function(){
90 realStream.write(buf.join(''));
99 timer = setTimeout(flush, interval)
107 return function logger(req, res, next) {
108 req._startAt = process.hrtime();
109 req._startTime = new Date;
110 req._remoteAddress = getip(req);
112 function logRequest(){
113 if (skip(req, res)) {
114 debug('skip request')
118 var line = fmt(exports, req, res)
126 stream.write(line + '\n')
133 onFinished(res, logRequest)
141 * Compile `format` into a function.
144 * @param {Function|String} format
148 function compile(format) {
149 if (typeof format === 'function') {
154 if (typeof format !== 'string') {
155 throw new TypeError('argument format must be a function or string')
158 var fmt = format.replace(/"/g, '\\"')
159 var js = ' return "' + fmt.replace(/:([-\w]{2,})(?:\[([^\]]+)\])?/g, function(_, name, arg){
160 return '"\n + (tokens["' + name + '"](req, res, ' + String(JSON.stringify(arg)) + ') || "-") + "';
163 return new Function('tokens, req, res', js);
167 * Define a token function with the given `name`,
168 * and callback `fn(req, res)`.
171 * @param {String} name
172 * @param {Function} fn
173 * @return {Object} exports for chaining
176 exports.token = function(name, fn) {
182 * Define a `fmt` with the given `name`.
185 * @param {String} name
186 * @param {String|Function} fmt
187 * @return {Object} exports for chaining
190 exports.format = function(name, fmt){
196 * Apache combined log format.
199 exports.format('combined', ':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"')
202 * Apache common log format.
205 exports.format('common', ':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length]')
211 exports.format('default', ':remote-addr - :remote-user [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"');
212 deprecate.property(exports, 'default', 'default format: use combined format')
218 exports.format('short', ':remote-addr :remote-user :method :url HTTP/:http-version :status :res[content-length] - :response-time ms');
224 exports.format('tiny', ':method :url :status :res[content-length] - :response-time ms');
230 exports.format('dev', function(tokens, req, res){
231 var color = 32; // green
232 var status = res.statusCode;
234 if (status >= 500) color = 31; // red
235 else if (status >= 400) color = 33; // yellow
236 else if (status >= 300) color = 36; // cyan
238 var fn = compile('\x1b[0m:method :url \x1b[' + color + 'm:status \x1b[0m:response-time ms - :res[content-length]\x1b[0m');
240 return fn(tokens, req, res);
247 exports.token('url', function(req){
248 return req.originalUrl || req.url;
255 exports.token('method', function(req){
260 * response time in milliseconds
263 exports.token('response-time', function(req, res){
264 if (!res._header || !req._startAt) return '';
265 var diff = process.hrtime(req._startAt);
266 var ms = diff[0] * 1e3 + diff[1] * 1e-6;
267 return ms.toFixed(3);
274 exports.token('date', function(req, res, format){
275 format = format || 'web'
277 var date = new Date()
283 return date.toISOString()
285 return date.toUTCString()
290 * response status code
293 exports.token('status', function(req, res){
294 return res._header ? res.statusCode : null;
298 * normalized referrer
301 exports.token('referrer', function(req){
302 return req.headers['referer'] || req.headers['referrer'];
309 exports.token('remote-addr', getip);
315 exports.token('remote-user', function (req) {
316 var creds = auth(req)
317 var user = (creds && creds.name) || '-'
325 exports.token('http-version', function(req){
326 return req.httpVersionMajor + '.' + req.httpVersionMinor;
333 exports.token('user-agent', function(req){
334 return req.headers['user-agent'];
341 exports.token('req', function(req, res, field){
342 return req.headers[field.toLowerCase()];
349 exports.token('res', function(req, res, field){
350 return (res._headers || {})[field.toLowerCase()];
354 * Format a Date in the common log format.
357 * @param {Date} dateTime
361 function clfdate(dateTime) {
362 var date = dateTime.getUTCDate()
363 var hour = dateTime.getUTCHours()
364 var mins = dateTime.getUTCMinutes()
365 var secs = dateTime.getUTCSeconds()
366 var year = dateTime.getUTCFullYear()
368 var month = clfmonth[dateTime.getUTCMonth()]
370 return pad2(date) + '/' + month + '/' + year
371 + ':' + pad2(hour) + ':' + pad2(mins) + ':' + pad2(secs)
376 * Get request IP address.
379 * @param {IncomingMessage} req
383 function getip(req) {
385 || req._remoteAddress
386 || (req.connection && req.connection.remoteAddress)
391 * Pad number to two digits.
394 * @param {number} num
399 var str = String(num)
401 return (str.length === 1 ? '0' : '')