Created
June 30, 2013 10:27
-
-
Save zs1621/5894655 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*! | |
* Connect - logger | |
* Copyright(c) 2010 Sencha Inc. | |
* Copyright(c) 2011 TJ Holowaychuk | |
* MIT Licensed | |
*/ | |
/** | |
* Module dependencies. | |
*/ | |
var bytes = require('bytes'); | |
/*! | |
* Log buffer. | |
*/ | |
var buf = []; | |
/*! | |
* Default log buffer duration. | |
*/ | |
var defaultBufferDuration = 1000; | |
/** | |
* Logger: | |
* | |
* Log requests with the given `options` or a `format` string. | |
* | |
* Options: | |
* | |
* - `format` Format string, see below for tokens | |
* - `stream` Output stream, defaults to _stdout_ | |
* - `buffer` Buffer duration, defaults to 1000ms when _true_ | |
* - `immediate` Write log line on request instead of response (for response times) | |
* | |
* Tokens: | |
* | |
* - `:req[header]` ex: `:req[Accept]` | |
* - `:res[header]` ex: `:res[Content-Length]` | |
* - `:http-version` | |
* - `:response-time` | |
* - `:remote-addr` | |
* - `:date` | |
* - `:method` | |
* - `:url` | |
* - `:referrer` | |
* - `:user-agent` | |
* - `:status` | |
* | |
* Formats: | |
* | |
* Pre-defined formats that ship with connect: | |
* | |
* - `default` ':remote-addr - - [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"' | |
* - `short` ':remote-addr - :method :url HTTP/:http-version :status :res[content-length] - :response-time ms' | |
* - `tiny` ':method :url :status :res[content-length] - :response-time ms' | |
* - `dev` concise output colored by response status for development use | |
* | |
* Examples: | |
* | |
* connect.logger() // default | |
* connect.logger('short') | |
* connect.logger('tiny') | |
* connect.logger({ immediate: true, format: 'dev' }) | |
* connect.logger(':method :url - :referrer') | |
* connect.logger(':req[content-type] -> :res[content-type]') | |
* connect.logger(function(tokens, req, res){ return 'some format string' }) | |
* | |
* Defining Tokens: | |
* | |
* To define a token, simply invoke `connect.logger.token()` with the | |
* name and a callback function. The value returned is then available | |
* as ":type" in this case. | |
* | |
* connect.logger.token('type', function(req, res){ return req.headers['content-type']; }) | |
* | |
* Defining Formats: | |
* | |
* All default formats are defined this way, however it's public API as well: | |
* | |
* connect.logger.format('name', 'string or function') | |
* | |
* @param {String|Function|Object} format or options | |
* @return {Function} | |
* @api public | |
*/ | |
exports = module.exports = function logger(options) { | |
if ('object' == typeof options) { | |
options = options || {}; | |
} else if (options) { | |
options = { format: options }; | |
} else { | |
options = {}; | |
} | |
// output on request instead of response | |
var immediate = options.immediate; | |
// format name | |
var fmt = exports[options.format] || options.format || exports.default; | |
// compile format | |
if ('function' != typeof fmt) fmt = compile(fmt); | |
// options | |
var stream = options.stream || process.stdout | |
, buffer = options.buffer; | |
// buffering support | |
if (buffer) { | |
var realStream = stream | |
, interval = 'number' == typeof buffer | |
? buffer | |
: defaultBufferDuration; | |
// flush interval | |
setInterval(function(){ | |
if (buf.length) { | |
realStream.write(buf.join('')); | |
buf.length = 0; | |
} | |
}, interval); | |
// swap the stream | |
stream = { | |
write: function(str){ | |
buf.push(str); | |
} | |
}; | |
} | |
return function logger(req, res, next) { | |
req._startTime = new Date; | |
// immediate | |
if (immediate) { | |
var line = fmt(exports, req, res); | |
if (null == line) return; | |
stream.write(line + '\n'); | |
// proxy end to output logging | |
} else { | |
var end = res.end; | |
res.end = function(chunk, encoding){ | |
res.end = end; | |
res.end(chunk, encoding); | |
var line = fmt(exports, req, res); | |
if (null == line) return; | |
stream.write(line + '\n'); | |
}; | |
} | |
next(); | |
}; | |
}; | |
/** | |
* Compile `fmt` into a function. | |
* | |
* @param {String} fmt | |
* @return {Function} | |
* @api private | |
*/ | |
function compile(fmt) { | |
fmt = fmt.replace(/"/g, '\\"'); | |
var js = ' return "' + fmt.replace(/:([-\w]{2,})(?:\[([^\]]+)\])?/g, function(_, name, arg){ | |
return '"\n + (tokens["' + name + '"](req, res, "' + arg + '") || "-") + "'; | |
}) + '";' | |
return new Function('tokens, req, res', js); | |
}; | |
/** | |
* Define a token function with the given `name`, | |
* and callback `fn(req, res)`. | |
* | |
* @param {String} name | |
* @param {Function} fn | |
* @return {Object} exports for chaining | |
* @api public | |
*/ | |
exports.token = function(name, fn) { | |
exports[name] = fn; | |
return this; | |
}; | |
/** | |
* Define a `fmt` with the given `name`. | |
* | |
* @param {String} name | |
* @param {String|Function} fmt | |
* @return {Object} exports for chaining | |
* @api public | |
*/ | |
exports.format = function(name, str){ | |
exports[name] = str; | |
return this; | |
}; | |
/** | |
* Default format. | |
*/ | |
exports.format('default', ':remote-addr - - [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"'); | |
/** | |
* Short format. | |
*/ | |
exports.format('short', ':remote-addr - :method :url HTTP/:http-version :status :res[content-length] - :response-time ms'); | |
/** | |
* Tiny format. | |
*/ | |
exports.format('tiny', ':method :url :status :res[content-length] - :response-time ms'); | |
/** | |
* dev (colored) | |
*/ | |
exports.format('dev', function(tokens, req, res){ | |
var status = res.statusCode | |
, len = parseInt(res.getHeader('Content-Length'), 10) | |
, color = 32; | |
if (status >= 500) color = 31 | |
else if (status >= 400) color = 33 | |
else if (status >= 300) color = 36; | |
len = isNaN(len) | |
? '' | |
: len = ' - ' + bytes(len); | |
return '\033[90m' + req.method | |
+ ' ' + req.originalUrl + ' ' | |
+ '\033[' + color + 'm' + res.statusCode | |
+ ' \033[90m' | |
+ (new Date - req._startTime) | |
+ 'ms' + len | |
+ '\033[0m'; | |
}); | |
/** | |
* request url | |
*/ | |
exports.token('url', function(req){ | |
return req.originalUrl || req.url; | |
}); | |
/** | |
* request method | |
*/ | |
exports.token('method', function(req){ | |
return req.method; | |
}); | |
/** | |
* response time in milliseconds | |
*/ | |
exports.token('response-time', function(req){ | |
return new Date - req._startTime; | |
}); | |
/** | |
* UTC date | |
*/ | |
exports.token('date', function(){ | |
return new Date().toUTCString(); | |
}); | |
/** | |
* response status code | |
*/ | |
exports.token('status', function(req, res){ | |
return res.statusCode; | |
}); | |
/** | |
* normalized referrer | |
*/ | |
exports.token('referrer', function(req){ | |
return req.headers['referer'] || req.headers['referrer']; | |
}); | |
/** | |
* remote address | |
*/ | |
exports.token('remote-addr', function(req){ | |
if (req.ip) return req.ip; | |
var sock = req.socket; | |
if (sock.socket) return sock.socket.remoteAddress; | |
return sock.remoteAddress; | |
}); | |
/** | |
* HTTP version | |
*/ | |
exports.token('http-version', function(req){ | |
return req.httpVersionMajor + '.' + req.httpVersionMinor; | |
}); | |
/** | |
* UA string | |
*/ | |
exports.token('user-agent', function(req){ | |
return req.headers['user-agent']; | |
}); | |
/** | |
* request header | |
*/ | |
exports.token('req', function(req, res, field){ | |
return req.headers[field.toLowerCase()]; | |
}); | |
/** | |
* response header | |
*/ | |
exports.token('res', function(req, res, field){ | |
return (res._headers || {})[field.toLowerCase()]; | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment