4 * Copyright(c) 2010 Sencha Inc.
5 * Copyright(c) 2011 TJ Holowaychuk
16 * Default log buffer duration.
19 var defaultBufferDuration = 1000;
22 * Log requests with the given `options` or a `format` string.
26 * - `format` Format string, see below for tokens
27 * - `stream` Output stream, defaults to _stdout_
28 * - `buffer` Buffer duration, defaults to 1000ms when _true_
29 * - `immediate` Write log line on request instead of response (for response times)
33 * - `:req[header]` ex: `:req[Accept]`
34 * - `:res[header]` ex: `:res[Content-Length]`
47 * Pre-defined formats that ship with connect:
49 * - `default` ':remote-addr - - [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"'
50 * - `short` ':remote-addr - :method :url HTTP/:http-version :status :res[content-length] - :response-time ms'
51 * - `tiny` ':method :url :status :res[content-length] - :response-time ms'
52 * - `dev` concise output colored by response status for development use
56 * connect.logger() // default
57 * connect.logger('short')
58 * connect.logger('tiny')
59 * connect.logger('dev')
60 * connect.logger(':method :url - :referrer')
61 * connect.logger(':req[content-type] -> :res[content-type]')
62 * connect.logger(function(req, res){ return 'some format string' })
66 * To define a token, simply invoke `connect.logger.token()` with the
67 * name and a callback function. The value returned is then available
68 * as ":type" in this case.
70 * connect.logger.token('type', function(req, res){ return req.headers['content-type']; })
74 * All default formats are defined this way, however it's public API as well:
76 * connect.logger.format('name', 'string or function')
78 * @param {String|Function|Object} format or options
83 exports = module.exports = function logger(options) {
84 if ('object' == typeof options) {
85 options = options || {};
87 options = { format: options };
92 // output on request instead of response
93 var immediate = options.immediate;
96 var fmt = exports[options.format] || options.format || exports.default;
99 if ('function' != typeof fmt) fmt = compile(fmt);
102 var stream = options.stream || process.stdout
103 , buffer = options.buffer;
107 var realStream = stream
108 , interval = 'number' == typeof buffer
110 : defaultBufferDuration;
113 setInterval(function(){
115 realStream.write(buf.join(''), 'ascii');
122 write: function(str){
128 return function logger(req, res, next) {
129 req._startTime = new Date;
132 if (req._logging) return next();
139 var line = fmt(exports, req, res);
140 if (null == line) return;
141 stream.write(line + '\n', 'ascii');
143 // proxy end to output loggging
145 res.end = function(chunk, encoding){
147 res.end(chunk, encoding);
148 var line = fmt(exports, req, res);
149 if (null == line) return;
150 stream.write(line + '\n', 'ascii');
160 * Compile `fmt` into a function.
162 * @param {String} fmt
167 function compile(fmt) {
168 fmt = fmt.replace(/"/g, '\\"');
169 var js = ' return "' + fmt.replace(/:([-\w]{2,})(?:\[([^\]]+)\])?/g, function(_, name, arg){
170 return '"\n + (tokens["' + name + '"](req, res, "' + arg + '") || "-") + "';
172 return new Function('tokens, req, res', js);
176 * Define a token function with the given `name`,
177 * and callback `fn(req, res)`.
179 * @param {String} name
180 * @param {Function} fn
181 * @return {Object} exports for chaining
185 exports.token = function(name, fn) {
191 * Define a `fmt` with the given `name`.
193 * @param {String} name
194 * @param {String|Function} fmt
195 * @return {Object} exports for chaining
199 exports.format = function(name, str){
206 exports.format('default', ':remote-addr - - [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"');
210 exports.format('short', ':remote-addr - :method :url HTTP/:http-version :status :res[content-length] - :response-time ms');
214 exports.format('tiny', ':method :url :status :res[content-length] - :response-time ms');
218 exports.format('dev', function(tokens, req, res){
219 var status = res.statusCode
222 if (status >= 500) color = 31
223 else if (status >= 400) color = 33
224 else if (status >= 300) color = 36;
226 return '\033[90m' + req.method
227 + ' ' + req.originalUrl + ' '
228 + '\033[' + color + 'm' + res.statusCode
230 + (new Date - req._startTime)
236 exports.token('url', function(req){
237 return req.originalUrl;
242 exports.token('method', function(req){
246 // response time in milliseconds
248 exports.token('response-time', function(req){
249 return new Date - req._startTime;
254 exports.token('date', function(){
255 return new Date().toUTCString();
258 // response status code
260 exports.token('status', function(req, res){
261 return res.statusCode;
264 // normalized referrer
266 exports.token('referrer', function(req){
267 return req.headers['referer'] || req.headers['referrer'];
272 exports.token('remote-addr', function(req){
273 return req.socket && (req.socket.remoteAddress || (req.socket.socket && req.socket.socket.remoteAddress));
278 exports.token('http-version', function(req){
279 return req.httpVersionMajor + '.' + req.httpVersionMinor;
284 exports.token('user-agent', function(req){
285 return req.headers['user-agent'];
290 exports.token('req', function(req, res, field){
291 return req.headers[field.toLowerCase()];
296 exports.token('res', function(req, res, field){
297 return (res._headers || {})[field.toLowerCase()];