/*! * 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()]; });