You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

339 lines
7.0 KiB

  1. /*!
  2. * Connect - logger
  3. * Copyright(c) 2010 Sencha Inc.
  4. * Copyright(c) 2011 TJ Holowaychuk
  5. * MIT Licensed
  6. */
  7. /**
  8. * Module dependencies.
  9. */
  10. var bytes = require('bytes');
  11. /*!
  12. * Log buffer.
  13. */
  14. var buf = [];
  15. /*!
  16. * Default log buffer duration.
  17. */
  18. var defaultBufferDuration = 1000;
  19. /**
  20. * Logger:
  21. *
  22. * Log requests with the given `options` or a `format` string.
  23. *
  24. * Options:
  25. *
  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)
  30. *
  31. * Tokens:
  32. *
  33. * - `:req[header]` ex: `:req[Accept]`
  34. * - `:res[header]` ex: `:res[Content-Length]`
  35. * - `:http-version`
  36. * - `:response-time`
  37. * - `:remote-addr`
  38. * - `:date`
  39. * - `:method`
  40. * - `:url`
  41. * - `:referrer`
  42. * - `:user-agent`
  43. * - `:status`
  44. *
  45. * Formats:
  46. *
  47. * Pre-defined formats that ship with connect:
  48. *
  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
  53. *
  54. * Examples:
  55. *
  56. * connect.logger() // default
  57. * connect.logger('short')
  58. * connect.logger('tiny')
  59. * connect.logger({ immediate: true, format: 'dev' })
  60. * connect.logger(':method :url - :referrer')
  61. * connect.logger(':req[content-type] -> :res[content-type]')
  62. * connect.logger(function(tokens, req, res){ return 'some format string' })
  63. *
  64. * Defining Tokens:
  65. *
  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.
  69. *
  70. * connect.logger.token('type', function(req, res){ return req.headers['content-type']; })
  71. *
  72. * Defining Formats:
  73. *
  74. * All default formats are defined this way, however it's public API as well:
  75. *
  76. * connect.logger.format('name', 'string or function')
  77. *
  78. * @param {String|Function|Object} format or options
  79. * @return {Function}
  80. * @api public
  81. */
  82. exports = module.exports = function logger(options) {
  83. if ('object' == typeof options) {
  84. options = options || {};
  85. } else if (options) {
  86. options = { format: options };
  87. } else {
  88. options = {};
  89. }
  90. // output on request instead of response
  91. var immediate = options.immediate;
  92. // format name
  93. var fmt = exports[options.format] || options.format || exports.default;
  94. // compile format
  95. if ('function' != typeof fmt) fmt = compile(fmt);
  96. // options
  97. var stream = options.stream || process.stdout
  98. , buffer = options.buffer;
  99. // buffering support
  100. if (buffer) {
  101. var realStream = stream
  102. , interval = 'number' == typeof buffer
  103. ? buffer
  104. : defaultBufferDuration;
  105. // flush interval
  106. setInterval(function(){
  107. if (buf.length) {
  108. realStream.write(buf.join(''));
  109. buf.length = 0;
  110. }
  111. }, interval);
  112. // swap the stream
  113. stream = {
  114. write: function(str){
  115. buf.push(str);
  116. }
  117. };
  118. }
  119. return function logger(req, res, next) {
  120. req._startTime = new Date;
  121. // immediate
  122. if (immediate) {
  123. var line = fmt(exports, req, res);
  124. if (null == line) return;
  125. stream.write(line + '\n');
  126. // proxy end to output logging
  127. } else {
  128. var end = res.end;
  129. res.end = function(chunk, encoding){
  130. res.end = end;
  131. res.end(chunk, encoding);
  132. var line = fmt(exports, req, res);
  133. if (null == line) return;
  134. stream.write(line + '\n');
  135. };
  136. }
  137. next();
  138. };
  139. };
  140. /**
  141. * Compile `fmt` into a function.
  142. *
  143. * @param {String} fmt
  144. * @return {Function}
  145. * @api private
  146. */
  147. function compile(fmt) {
  148. fmt = fmt.replace(/"/g, '\\"');
  149. var js = ' return "' + fmt.replace(/:([-\w]{2,})(?:\[([^\]]+)\])?/g, function(_, name, arg){
  150. return '"\n + (tokens["' + name + '"](req, res, "' + arg + '") || "-") + "';
  151. }) + '";'
  152. return new Function('tokens, req, res', js);
  153. };
  154. /**
  155. * Define a token function with the given `name`,
  156. * and callback `fn(req, res)`.
  157. *
  158. * @param {String} name
  159. * @param {Function} fn
  160. * @return {Object} exports for chaining
  161. * @api public
  162. */
  163. exports.token = function(name, fn) {
  164. exports[name] = fn;
  165. return this;
  166. };
  167. /**
  168. * Define a `fmt` with the given `name`.
  169. *
  170. * @param {String} name
  171. * @param {String|Function} fmt
  172. * @return {Object} exports for chaining
  173. * @api public
  174. */
  175. exports.format = function(name, str){
  176. exports[name] = str;
  177. return this;
  178. };
  179. /**
  180. * Default format.
  181. */
  182. exports.format('default', ':remote-addr - - [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"');
  183. /**
  184. * Short format.
  185. */
  186. exports.format('short', ':remote-addr - :method :url HTTP/:http-version :status :res[content-length] - :response-time ms');
  187. /**
  188. * Tiny format.
  189. */
  190. exports.format('tiny', ':method :url :status :res[content-length] - :response-time ms');
  191. /**
  192. * dev (colored)
  193. */
  194. exports.format('dev', function(tokens, req, res){
  195. var status = res.statusCode
  196. , len = parseInt(res.getHeader('Content-Length'), 10)
  197. , color = 32;
  198. if (status >= 500) color = 31
  199. else if (status >= 400) color = 33
  200. else if (status >= 300) color = 36;
  201. len = isNaN(len)
  202. ? ''
  203. : len = ' - ' + bytes(len);
  204. return '\033[90m' + req.method
  205. + ' ' + req.originalUrl + ' '
  206. + '\033[' + color + 'm' + res.statusCode
  207. + ' \033[90m'
  208. + (new Date - req._startTime)
  209. + 'ms' + len
  210. + '\033[0m';
  211. });
  212. /**
  213. * request url
  214. */
  215. exports.token('url', function(req){
  216. return req.originalUrl || req.url;
  217. });
  218. /**
  219. * request method
  220. */
  221. exports.token('method', function(req){
  222. return req.method;
  223. });
  224. /**
  225. * response time in milliseconds
  226. */
  227. exports.token('response-time', function(req){
  228. return new Date - req._startTime;
  229. });
  230. /**
  231. * UTC date
  232. */
  233. exports.token('date', function(){
  234. return new Date().toUTCString();
  235. });
  236. /**
  237. * response status code
  238. */
  239. exports.token('status', function(req, res){
  240. return res.statusCode;
  241. });
  242. /**
  243. * normalized referrer
  244. */
  245. exports.token('referrer', function(req){
  246. return req.headers['referer'] || req.headers['referrer'];
  247. });
  248. /**
  249. * remote address
  250. */
  251. exports.token('remote-addr', function(req){
  252. if (req.ip) return req.ip;
  253. var sock = req.socket;
  254. if (sock.socket) return sock.socket.remoteAddress;
  255. return sock.remoteAddress;
  256. });
  257. /**
  258. * HTTP version
  259. */
  260. exports.token('http-version', function(req){
  261. return req.httpVersionMajor + '.' + req.httpVersionMinor;
  262. });
  263. /**
  264. * UA string
  265. */
  266. exports.token('user-agent', function(req){
  267. return req.headers['user-agent'];
  268. });
  269. /**
  270. * request header
  271. */
  272. exports.token('req', function(req, res, field){
  273. return req.headers[field.toLowerCase()];
  274. });
  275. /**
  276. * response header
  277. */
  278. exports.token('res', function(req, res, field){
  279. return (res._headers || {})[field.toLowerCase()];
  280. });