300 lines
6.7 KiB
JavaScript
300 lines
6.7 KiB
JavaScript
|
|
/*!
|
|
* Connect - logger
|
|
* Copyright(c) 2010 Sencha Inc.
|
|
* Copyright(c) 2011 TJ Holowaychuk
|
|
* MIT Licensed
|
|
*/
|
|
|
|
/**
|
|
* Log buffer.
|
|
*/
|
|
|
|
var buf = [];
|
|
|
|
/**
|
|
* Default log buffer duration.
|
|
*/
|
|
|
|
var defaultBufferDuration = 1000;
|
|
|
|
/**
|
|
* 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('dev')
|
|
* connect.logger(':method :url - :referrer')
|
|
* connect.logger(':req[content-type] -> :res[content-type]')
|
|
* connect.logger(function(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(''), 'ascii');
|
|
buf.length = 0;
|
|
}
|
|
}, interval);
|
|
|
|
// swap the stream
|
|
stream = {
|
|
write: function(str){
|
|
buf.push(str);
|
|
}
|
|
};
|
|
}
|
|
|
|
return function logger(req, res, next) {
|
|
req._startTime = new Date;
|
|
|
|
// mount safety
|
|
if (req._logging) return next();
|
|
|
|
// flag as logging
|
|
req._logging = true;
|
|
|
|
// immediate
|
|
if (immediate) {
|
|
var line = fmt(exports, req, res);
|
|
if (null == line) return;
|
|
stream.write(line + '\n', 'ascii');
|
|
} else {
|
|
// proxy end to output loggging
|
|
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', 'ascii');
|
|
};
|
|
}
|
|
|
|
|
|
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
|
|
, color = 32;
|
|
|
|
if (status >= 500) color = 31
|
|
else if (status >= 400) color = 33
|
|
else if (status >= 300) color = 36;
|
|
|
|
return '\033[90m' + req.method
|
|
+ ' ' + req.originalUrl + ' '
|
|
+ '\033[' + color + 'm' + res.statusCode
|
|
+ ' \033[90m'
|
|
+ (new Date - req._startTime)
|
|
+ 'ms\033[0m';
|
|
});
|
|
|
|
// request url
|
|
|
|
exports.token('url', function(req){
|
|
return req.originalUrl;
|
|
});
|
|
|
|
// 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){
|
|
return req.socket && (req.socket.remoteAddress || (req.socket.socket && req.socket.socket.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()];
|
|
});
|
|
|