connect-logger.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. "use strict";
  2. var levels = require("./levels");
  3. var DEFAULT_FORMAT = ':remote-addr - -' +
  4. ' ":method :url HTTP/:http-version"' +
  5. ' :status :content-length ":referrer"' +
  6. ' ":user-agent"';
  7. /**
  8. * Log requests with the given `options` or a `format` string.
  9. *
  10. * Options:
  11. *
  12. * - `format` Format string, see below for tokens
  13. * - `level` A log4js levels instance. Supports also 'auto'
  14. *
  15. * Tokens:
  16. *
  17. * - `:req[header]` ex: `:req[Accept]`
  18. * - `:res[header]` ex: `:res[Content-Length]`
  19. * - `:http-version`
  20. * - `:response-time`
  21. * - `:remote-addr`
  22. * - `:date`
  23. * - `:method`
  24. * - `:url`
  25. * - `:referrer`
  26. * - `:user-agent`
  27. * - `:status`
  28. *
  29. * @param {String|Function|Object} format or options
  30. * @return {Function}
  31. * @api public
  32. */
  33. function getLogger(logger4js, options) {
  34. if ('object' == typeof options) {
  35. options = options || {};
  36. } else if (options) {
  37. options = { format: options };
  38. } else {
  39. options = {};
  40. }
  41. var thislogger = logger4js
  42. , level = levels.toLevel(options.level, levels.INFO)
  43. , fmt = options.format || DEFAULT_FORMAT
  44. , nolog = options.nolog ? createNoLogCondition(options.nolog) : null;
  45. return function (req, res, next) {
  46. // mount safety
  47. if (req._logging) return next();
  48. // nologs
  49. if (nolog && nolog.test(req.originalUrl)) return next();
  50. if (thislogger.isLevelEnabled(level) || options.level === 'auto') {
  51. var start = new Date()
  52. , statusCode
  53. , writeHead = res.writeHead
  54. , end = res.end
  55. , url = req.originalUrl;
  56. // flag as logging
  57. req._logging = true;
  58. // proxy for statusCode.
  59. res.writeHead = function(code, headers){
  60. res.writeHead = writeHead;
  61. res.writeHead(code, headers);
  62. res.__statusCode = statusCode = code;
  63. res.__headers = headers || {};
  64. //status code response level handling
  65. if(options.level === 'auto'){
  66. level = levels.INFO;
  67. if(code >= 300) level = levels.WARN;
  68. if(code >= 400) level = levels.ERROR;
  69. } else {
  70. level = levels.toLevel(options.level, levels.INFO);
  71. }
  72. };
  73. // proxy end to output a line to the provided logger.
  74. res.end = function(chunk, encoding) {
  75. res.end = end;
  76. res.end(chunk, encoding);
  77. res.responseTime = new Date() - start;
  78. if (thislogger.isLevelEnabled(level)) {
  79. if (typeof fmt === 'function') {
  80. var line = fmt(req, res, function(str){ return format(str, req, res); });
  81. if (line) thislogger.log(level, line);
  82. } else {
  83. thislogger.log(level, format(fmt, req, res));
  84. }
  85. }
  86. };
  87. }
  88. //ensure next gets always called
  89. next();
  90. };
  91. }
  92. /**
  93. * Return formatted log line.
  94. *
  95. * @param {String} str
  96. * @param {IncomingMessage} req
  97. * @param {ServerResponse} res
  98. * @return {String}
  99. * @api private
  100. */
  101. function format(str, req, res) {
  102. return str
  103. .replace(':url', req.originalUrl)
  104. .replace(':method', req.method)
  105. .replace(':status', res.__statusCode || res.statusCode)
  106. .replace(':response-time', res.responseTime)
  107. .replace(':date', new Date().toUTCString())
  108. .replace(':referrer', req.headers.referer || req.headers.referrer || '')
  109. .replace(':http-version', req.httpVersionMajor + '.' + req.httpVersionMinor)
  110. .replace(
  111. ':remote-addr',
  112. req.socket &&
  113. (req.socket.remoteAddress || (req.socket.socket && req.socket.socket.remoteAddress))
  114. )
  115. .replace(':user-agent', req.headers['user-agent'] || '')
  116. .replace(
  117. ':content-length',
  118. (res._headers && res._headers['content-length']) ||
  119. (res.__headers && res.__headers['Content-Length']) ||
  120. '-'
  121. )
  122. .replace(/:req\[([^\]]+)\]/g, function(_, field){ return req.headers[field.toLowerCase()]; })
  123. .replace(/:res\[([^\]]+)\]/g, function(_, field){
  124. return res._headers ?
  125. (res._headers[field.toLowerCase()] || res.__headers[field])
  126. : (res.__headers && res.__headers[field]);
  127. });
  128. }
  129. /**
  130. * Return RegExp Object about nolog
  131. *
  132. * @param {String} nolog
  133. * @return {RegExp}
  134. * @api private
  135. *
  136. * syntax
  137. * 1. String
  138. * 1.1 "\\.gif"
  139. * NOT LOGGING http://example.com/hoge.gif and http://example.com/hoge.gif?fuga
  140. * LOGGING http://example.com/hoge.agif
  141. * 1.2 in "\\.gif|\\.jpg$"
  142. * NOT LOGGING http://example.com/hoge.gif and
  143. * http://example.com/hoge.gif?fuga and http://example.com/hoge.jpg?fuga
  144. * LOGGING http://example.com/hoge.agif,
  145. * http://example.com/hoge.ajpg and http://example.com/hoge.jpg?hoge
  146. * 1.3 in "\\.(gif|jpe?g|png)$"
  147. * NOT LOGGING http://example.com/hoge.gif and http://example.com/hoge.jpeg
  148. * LOGGING http://example.com/hoge.gif?uid=2 and http://example.com/hoge.jpg?pid=3
  149. * 2. RegExp
  150. * 2.1 in /\.(gif|jpe?g|png)$/
  151. * SAME AS 1.3
  152. * 3. Array
  153. * 3.1 ["\\.jpg$", "\\.png", "\\.gif"]
  154. * SAME AS "\\.jpg|\\.png|\\.gif"
  155. */
  156. function createNoLogCondition(nolog) {
  157. var regexp = null;
  158. if (nolog) {
  159. if (nolog instanceof RegExp) {
  160. regexp = nolog;
  161. }
  162. if (typeof nolog === 'string') {
  163. regexp = new RegExp(nolog);
  164. }
  165. if (Array.isArray(nolog)) {
  166. var regexpsAsStrings = nolog.map(
  167. function convertToStrings(o) {
  168. return o.source ? o.source : o;
  169. }
  170. );
  171. regexp = new RegExp(regexpsAsStrings.join('|'));
  172. }
  173. }
  174. return regexp;
  175. }
  176. exports.connectLogger = getLogger;