1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 | var auth = require('basic-auth')
|
16 | var debug = require('debug')('morgan')
|
17 | var deprecate = require('depd')('morgan')
|
18 | var onFinished = require('on-finished')
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 | var clfmonth = [
|
26 | 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
27 | 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
|
28 | ]
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 | var defaultBufferDuration = 1000;
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 | exports = module.exports = function morgan(format, options) {
|
47 | if (typeof format === 'object') {
|
48 | options = format
|
49 | format = options.format || 'default'
|
50 |
|
51 |
|
52 | deprecate('morgan(options): use morgan(' + (typeof format === 'string' ? JSON.stringify(format) : 'format') + ', options) instead')
|
53 | }
|
54 |
|
55 | if (format === undefined) {
|
56 | deprecate('undefined format: specify a format')
|
57 | }
|
58 |
|
59 | options = options || {}
|
60 |
|
61 |
|
62 | var immediate = options.immediate;
|
63 |
|
64 |
|
65 | var skip = options.skip || function () { return false; };
|
66 |
|
67 |
|
68 | var fmt = compile(exports[format] || format || exports.default)
|
69 |
|
70 |
|
71 | var buffer = options.buffer
|
72 | var stream = options.stream || process.stdout
|
73 |
|
74 |
|
75 | if (buffer) {
|
76 | deprecate('buffer option')
|
77 |
|
78 | var realStream = stream
|
79 | var buf = []
|
80 | var timer = null
|
81 | var interval = 'number' == typeof buffer
|
82 | ? buffer
|
83 | : defaultBufferDuration
|
84 |
|
85 |
|
86 | var flush = function(){
|
87 | timer = null
|
88 |
|
89 | if (buf.length) {
|
90 | realStream.write(buf.join(''));
|
91 | buf.length = 0;
|
92 | }
|
93 | }
|
94 |
|
95 |
|
96 | stream = {
|
97 | write: function(str){
|
98 | if (timer === null) {
|
99 | timer = setTimeout(flush, interval)
|
100 | }
|
101 |
|
102 | buf.push(str);
|
103 | }
|
104 | };
|
105 | }
|
106 |
|
107 | return function logger(req, res, next) {
|
108 | req._startAt = process.hrtime();
|
109 | req._startTime = new Date;
|
110 | req._remoteAddress = getip(req);
|
111 |
|
112 | function logRequest(){
|
113 | if (skip(req, res)) {
|
114 | debug('skip request')
|
115 | return
|
116 | }
|
117 |
|
118 | var line = fmt(exports, req, res)
|
119 |
|
120 | if (null == line) {
|
121 | debug('skip line')
|
122 | return
|
123 | }
|
124 |
|
125 | debug('log request')
|
126 | stream.write(line + '\n')
|
127 | };
|
128 |
|
129 |
|
130 | if (immediate) {
|
131 | logRequest();
|
132 | } else {
|
133 | onFinished(res, logRequest)
|
134 | }
|
135 |
|
136 | next();
|
137 | };
|
138 | };
|
139 |
|
140 |
|
141 |
|
142 |
|
143 |
|
144 |
|
145 |
|
146 |
|
147 |
|
148 | function compile(format) {
|
149 | if (typeof format === 'function') {
|
150 |
|
151 | return format
|
152 | }
|
153 |
|
154 | if (typeof format !== 'string') {
|
155 | throw new TypeError('argument format must be a function or string')
|
156 | }
|
157 |
|
158 | var fmt = format.replace(/"/g, '\\"')
|
159 | var js = ' return "' + fmt.replace(/:([-\w]{2,})(?:\[([^\]]+)\])?/g, function(_, name, arg){
|
160 | return '"\n + (tokens["' + name + '"](req, res, ' + String(JSON.stringify(arg)) + ') || "-") + "';
|
161 | }) + '";'
|
162 |
|
163 | return new Function('tokens, req, res', js);
|
164 | };
|
165 |
|
166 |
|
167 |
|
168 |
|
169 |
|
170 |
|
171 |
|
172 |
|
173 |
|
174 |
|
175 |
|
176 | exports.token = function(name, fn) {
|
177 | exports[name] = fn;
|
178 | return this;
|
179 | };
|
180 |
|
181 |
|
182 |
|
183 |
|
184 |
|
185 |
|
186 |
|
187 |
|
188 |
|
189 |
|
190 | exports.format = function(name, fmt){
|
191 | exports[name] = fmt;
|
192 | return this;
|
193 | };
|
194 |
|
195 |
|
196 |
|
197 |
|
198 |
|
199 | exports.format('combined', ':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"')
|
200 |
|
201 |
|
202 |
|
203 |
|
204 |
|
205 | exports.format('common', ':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length]')
|
206 |
|
207 |
|
208 |
|
209 |
|
210 |
|
211 | exports.format('default', ':remote-addr - :remote-user [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"');
|
212 | deprecate.property(exports, 'default', 'default format: use combined format')
|
213 |
|
214 |
|
215 |
|
216 |
|
217 |
|
218 | exports.format('short', ':remote-addr :remote-user :method :url HTTP/:http-version :status :res[content-length] - :response-time ms');
|
219 |
|
220 |
|
221 |
|
222 |
|
223 |
|
224 | exports.format('tiny', ':method :url :status :res[content-length] - :response-time ms');
|
225 |
|
226 |
|
227 |
|
228 |
|
229 |
|
230 | exports.format('dev', function(tokens, req, res){
|
231 | var color = 32;
|
232 | var status = res.statusCode;
|
233 |
|
234 | if (status >= 500) color = 31;
|
235 | else if (status >= 400) color = 33;
|
236 | else if (status >= 300) color = 36;
|
237 |
|
238 | var fn = compile('\x1b[0m:method :url \x1b[' + color + 'm:status \x1b[0m:response-time ms - :res[content-length]\x1b[0m');
|
239 |
|
240 | return fn(tokens, req, res);
|
241 | });
|
242 |
|
243 |
|
244 |
|
245 |
|
246 |
|
247 | exports.token('url', function(req){
|
248 | return req.originalUrl || req.url;
|
249 | });
|
250 |
|
251 |
|
252 |
|
253 |
|
254 |
|
255 | exports.token('method', function(req){
|
256 | return req.method;
|
257 | });
|
258 |
|
259 |
|
260 |
|
261 |
|
262 |
|
263 | exports.token('response-time', function(req, res){
|
264 | if (!res._header || !req._startAt) return '';
|
265 | var diff = process.hrtime(req._startAt);
|
266 | var ms = diff[0] * 1e3 + diff[1] * 1e-6;
|
267 | return ms.toFixed(3);
|
268 | });
|
269 |
|
270 |
|
271 |
|
272 |
|
273 |
|
274 | exports.token('date', function(req, res, format){
|
275 | format = format || 'web'
|
276 |
|
277 | var date = new Date()
|
278 |
|
279 | switch (format) {
|
280 | case 'clf':
|
281 | return clfdate(date)
|
282 | case 'iso':
|
283 | return date.toISOString()
|
284 | case 'web':
|
285 | return date.toUTCString()
|
286 | }
|
287 | });
|
288 |
|
289 |
|
290 |
|
291 |
|
292 |
|
293 | exports.token('status', function(req, res){
|
294 | return res._header ? res.statusCode : null;
|
295 | });
|
296 |
|
297 |
|
298 |
|
299 |
|
300 |
|
301 | exports.token('referrer', function(req){
|
302 | return req.headers['referer'] || req.headers['referrer'];
|
303 | });
|
304 |
|
305 |
|
306 |
|
307 |
|
308 |
|
309 | exports.token('remote-addr', getip);
|
310 |
|
311 |
|
312 |
|
313 |
|
314 |
|
315 | exports.token('remote-user', function (req) {
|
316 | var creds = auth(req)
|
317 | var user = (creds && creds.name) || '-'
|
318 | return user;
|
319 | })
|
320 |
|
321 |
|
322 |
|
323 |
|
324 |
|
325 | exports.token('http-version', function(req){
|
326 | return req.httpVersionMajor + '.' + req.httpVersionMinor;
|
327 | });
|
328 |
|
329 |
|
330 |
|
331 |
|
332 |
|
333 | exports.token('user-agent', function(req){
|
334 | return req.headers['user-agent'];
|
335 | });
|
336 |
|
337 |
|
338 |
|
339 |
|
340 |
|
341 | exports.token('req', function(req, res, field){
|
342 | return req.headers[field.toLowerCase()];
|
343 | });
|
344 |
|
345 |
|
346 |
|
347 |
|
348 |
|
349 | exports.token('res', function(req, res, field){
|
350 | return (res._headers || {})[field.toLowerCase()];
|
351 | });
|
352 |
|
353 |
|
354 |
|
355 |
|
356 |
|
357 |
|
358 |
|
359 |
|
360 |
|
361 | function clfdate(dateTime) {
|
362 | var date = dateTime.getUTCDate()
|
363 | var hour = dateTime.getUTCHours()
|
364 | var mins = dateTime.getUTCMinutes()
|
365 | var secs = dateTime.getUTCSeconds()
|
366 | var year = dateTime.getUTCFullYear()
|
367 |
|
368 | var month = clfmonth[dateTime.getUTCMonth()]
|
369 |
|
370 | return pad2(date) + '/' + month + '/' + year
|
371 | + ':' + pad2(hour) + ':' + pad2(mins) + ':' + pad2(secs)
|
372 | + ' +0000'
|
373 | }
|
374 |
|
375 |
|
376 |
|
377 |
|
378 |
|
379 |
|
380 |
|
381 |
|
382 |
|
383 | function getip(req) {
|
384 | return req.ip
|
385 | || req._remoteAddress
|
386 | || (req.connection && req.connection.remoteAddress)
|
387 | || undefined;
|
388 | }
|
389 |
|
390 |
|
391 |
|
392 |
|
393 |
|
394 |
|
395 |
|
396 |
|
397 |
|
398 | function pad2(num) {
|
399 | var str = String(num)
|
400 |
|
401 | return (str.length === 1 ? '0' : '')
|
402 | + str
|
403 | }
|