Inital Commit
[oweals/finalsclub.git] / node_modules / connect / lib / middleware / logger.js
1
2 /*!
3  * Connect - logger
4  * Copyright(c) 2010 Sencha Inc.
5  * Copyright(c) 2011 TJ Holowaychuk
6  * MIT Licensed
7  */
8
9 /**
10  * Log buffer.
11  */
12
13 var buf = [];
14
15 /**
16  * Default log buffer duration.
17  */
18
19 var defaultBufferDuration = 1000;
20
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('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' })
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
83 exports = module.exports = function logger(options) {
84   if ('object' == typeof options) {
85     options = options || {};
86   } else if (options) {
87     options = { format: options };
88   } else {
89     options = {};
90   }
91
92   // output on request instead of response
93   var immediate = options.immediate;
94
95   // format name
96   var fmt = exports[options.format] || options.format || exports.default;
97
98   // compile format
99   if ('function' != typeof fmt) fmt = compile(fmt);
100
101   // options
102   var stream = options.stream || process.stdout
103     , buffer = options.buffer;
104
105   // buffering support
106   if (buffer) {
107     var realStream = stream
108       , interval = 'number' == typeof buffer
109         ? buffer
110         : defaultBufferDuration;
111
112     // flush interval
113     setInterval(function(){
114       if (buf.length) {
115         realStream.write(buf.join(''), 'ascii');
116         buf.length = 0;
117       }
118     }, interval); 
119
120     // swap the stream
121     stream = {
122       write: function(str){
123         buf.push(str);
124       }
125     };
126   }
127
128   return function logger(req, res, next) {
129     req._startTime = new Date;
130
131     // mount safety
132     if (req._logging) return next();
133
134     // flag as logging
135     req._logging = true;
136
137     // immediate
138     if (immediate) {
139       var line = fmt(exports, req, res);
140       if (null == line) return;
141       stream.write(line + '\n', 'ascii');
142     } else {
143       // proxy end to output loggging
144       var end = res.end;
145       res.end = function(chunk, encoding){
146         res.end = end;
147         res.end(chunk, encoding);
148         var line = fmt(exports, req, res);
149         if (null == line) return;
150         stream.write(line + '\n', 'ascii');
151       };
152     }
153
154
155     next();
156   };
157 };
158
159 /**
160  * Compile `fmt` into a function.
161  *
162  * @param {String} fmt
163  * @return {Function}
164  * @api private
165  */
166
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 + '") || "-") + "';
171   }) + '";'
172   return new Function('tokens, req, res', js);
173 };
174
175 /**
176  * Define a token function with the given `name`,
177  * and callback `fn(req, res)`.
178  *
179  * @param {String} name
180  * @param {Function} fn
181  * @return {Object} exports for chaining
182  * @api public
183  */
184
185 exports.token = function(name, fn) {
186   exports[name] = fn;
187   return this;
188 };
189
190 /**
191  * Define a `fmt` with the given `name`.
192  *
193  * @param {String} name
194  * @param {String|Function} fmt
195  * @return {Object} exports for chaining
196  * @api public
197  */
198
199 exports.format = function(name, str){
200   exports[name] = str;
201   return this;
202 };
203
204 // default format
205
206 exports.format('default', ':remote-addr - - [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"');
207
208 // short format
209
210 exports.format('short', ':remote-addr - :method :url HTTP/:http-version :status :res[content-length] - :response-time ms');
211
212 // tiny format
213
214 exports.format('tiny', ':method :url :status :res[content-length] - :response-time ms');
215
216 // dev (colored)
217
218 exports.format('dev', function(tokens, req, res){
219   var status = res.statusCode
220     , color = 32;
221
222   if (status >= 500) color = 31
223   else if (status >= 400) color = 33
224   else if (status >= 300) color = 36;
225
226   return '\033[90m' + req.method
227     + ' ' + req.originalUrl + ' '
228     + '\033[' + color + 'm' + res.statusCode
229     + ' \033[90m'
230     + (new Date - req._startTime)
231     + 'ms\033[0m';
232 });
233
234 // request url
235
236 exports.token('url', function(req){
237   return req.originalUrl;
238 });
239
240 // request method
241
242 exports.token('method', function(req){
243   return req.method;
244 });
245
246 // response time in milliseconds
247
248 exports.token('response-time', function(req){
249   return new Date - req._startTime;
250 });
251
252 // UTC date
253
254 exports.token('date', function(){
255   return new Date().toUTCString();
256 });
257
258 // response status code
259
260 exports.token('status', function(req, res){
261   return res.statusCode;
262 });
263
264 // normalized referrer
265
266 exports.token('referrer', function(req){
267   return req.headers['referer'] || req.headers['referrer'];
268 });
269
270 // remote address
271
272 exports.token('remote-addr', function(req){
273   return req.socket && (req.socket.remoteAddress || (req.socket.socket && req.socket.socket.remoteAddress));
274 });
275
276 // HTTP version
277
278 exports.token('http-version', function(req){
279   return req.httpVersionMajor + '.' + req.httpVersionMinor;
280 });
281
282 // UA string
283
284 exports.token('user-agent', function(req){
285   return req.headers['user-agent'];
286 });
287
288 // request header
289
290 exports.token('req', function(req, res, field){
291   return req.headers[field.toLowerCase()];
292 });
293
294 // response header
295
296 exports.token('res', function(req, res, field){
297   return (res._headers || {})[field.toLowerCase()];
298 });
299