winston-middleware 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/Readme.md +54 -7
  2. package/index.js +142 -59
  3. package/package.json +3 -4
  4. package/test/test.js +149 -8
package/Readme.md CHANGED
@@ -9,7 +9,7 @@
9
9
 
10
10
  ## Usage
11
11
 
12
- winston-middleware provides middlewares for request and error logging of your express.js application.
12
+ winston-middleware provides middlewares for request and error logging of your express.js application. It uses 'whitelists' to select properties from the request and (new in 0.2.x) response objects.
13
13
 
14
14
  ### Error Logging
15
15
 
@@ -53,9 +53,9 @@ Use `expressWinston.logger(options)` to create a middleware to log your HTTP req
53
53
  app.use(app.router); // notice how the router goes after the logger.
54
54
  ```
55
55
 
56
- ## Example
56
+ ## Examples
57
57
 
58
- ``` js
58
+ ``` js
59
59
  var express = require('express');
60
60
  var expressWinston = require('winston-middleware');
61
61
  var winston = require('winston'); // for transports.Console
@@ -127,9 +127,13 @@ Browse `/` to see a regular HTTP logging like this:
127
127
  "originalUrl": "/",
128
128
  "query": {}
129
129
  },
130
+ "res": {
131
+ "statusCode": 200
132
+ },
133
+ "responseTime" : 12,
130
134
  "level": "info",
131
135
  "message": "HTTP GET /favicon.ico"
132
- }
136
+ }
133
137
 
134
138
  Browse `/error` will show you how winston-middleware handles and logs the errors in the express pipeline like this:
135
139
 
@@ -206,13 +210,55 @@ Browse `/error` will show you how winston-middleware handles and logs the errors
206
210
  "message": "middlewareError"
207
211
  }
208
212
 
213
+ ## Whitelists
214
+ New in version 0.2.x is the ability to add whitelist elements in a route. winston-middleware adds a `_routeWhitelists` object to the `req`uest, containing `.body`, `.req` and .res` properties, to which you can set an array of 'whitelist' parameters to include in the log, specific to the route in question:
215
+
216
+ ``` js
217
+ app.post('/user/register', function(req, res, next) {
218
+ req._routeWhitelists.body = ['username', 'email', 'age']; // But not 'password' or 'confirm-password' or 'top-secret'
219
+ req._routeWhitelists.res = ['_headers'];
220
+ });
221
+ ```
222
+
223
+ Post to `/user/register` would give you something like the following:
224
+
225
+ {
226
+ "req": {
227
+ "httpVersion": "1.1",
228
+ "headers": {
229
+ "host": "localhost:3000",
230
+ "connection": "keep-alive",
231
+ "accept": "*/*",
232
+ "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_4) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
233
+ "accept-encoding": "gzip,deflate,sdch",
234
+ "accept-language": "en-US,en;q=0.8,es-419;q=0.6,es;q=0.4",
235
+ "accept-charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.3",
236
+ "cookie": "connect.sid=nGspCCSzH1qxwNTWYAoexI23.seE%2B6Whmcwd"
237
+ },
238
+ "url": "/",
239
+ "method": "GET",
240
+ "originalUrl": "/",
241
+ "query": {},
242
+ "body": {
243
+ "username": "foo",
244
+ "email": "foo@bar.com",
245
+ "age": "72"
246
+ }
247
+ },
248
+ "res": {
249
+ "statusCode": 200
250
+ },
251
+ "responseTime" : 12,
252
+ "level": "info",
253
+ "message": "HTTP GET /favicon.ico"
254
+ }
255
+
209
256
  ## Tests
210
257
 
211
258
  npm test
212
259
 
213
260
  ## Issues and Collaboration
214
261
 
215
- * Add support for filtering of __req.body__. At this moment `body` is not included in the logging because it can contain sensitive fields like 'password' or 'password_confirmation'.
216
262
  * Implement a chain of requestFilters. Currently only one requestFilter is allowed in the options.
217
263
 
218
264
  We are accepting pull-request for these features.
@@ -221,8 +267,9 @@ If you ran into any problems, please use the project [Issues section](https://gi
221
267
 
222
268
  ## Contributors
223
269
 
224
- * [Johan Hernandez](https://github.com/thepumpkin1979). [https://github.com/thepumpkin1979](https://github.com/thepumpkin1979)
225
- * [Lars Jacob]((https://github.com/jaclar)) [https://github.com/jaclar)](https://github.com/jaclar)
270
+ * [Johan Hernandez](https://github.com/thepumpkin1979) (https://github.com/thepumpkin1979)
271
+ * [Lars Jacob](https://github.com/jaclar) (https://github.com/jaclar)
272
+ * [Jonathan Lomas](https://github.com/floatingLomas) (https://github.com/floatingLomas)
226
273
 
227
274
  ## MIT License
228
275
 
package/index.js CHANGED
@@ -1,15 +1,15 @@
1
1
  // Copyright (c) 2012 Firebase.co and Contributors - http://www.firebase.co
2
- //
2
+ //
3
3
  // Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  // of this software and associated documentation files (the "Software"), to deal
5
5
  // in the Software without restriction, including without limitation the rights
6
6
  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
7
  // copies of the Software, and to permit persons to whom the Software is
8
8
  // furnished to do so, subject to the following conditions:
9
- //
9
+ //
10
10
  // The above copyright notice and this permission notice shall be included in
11
11
  // all copies or substantial portions of the Software.
12
- //
12
+ //
13
13
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
14
  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
15
  // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
@@ -18,88 +18,171 @@
18
18
  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  // THE SOFTWARE.
20
20
  //
21
-
22
21
  var winston = require('winston');
23
- var async = require('async');
24
22
  var util = require('util');
25
23
 
26
- // default list of properties in the request object that are allowed to be logged.
27
- // these properties will be safely included in the meta of the log.
28
- // 'body' is not included in this list because it can contains passwords and stuff that are sensitive for logging.
29
- // TODO: Include 'body' and get the defaultRequestFilter to filter the inner properties like 'password' or 'password_confirmation', etc. Pull requests anyone?
24
+ /**
25
+ * A default list of properties in the request object that are allowed to be logged.
26
+ * These properties will be safely included in the meta of the log.
27
+ * 'body' is not included in this list because it can contains passwords and stuff that are sensitive for logging.
28
+ * TODO: Include 'body' and get the defaultRequestFilter to filter the inner properties like 'password' or 'password_confirmation', etc. Pull requests anyone?
29
+ * @type {Array}
30
+ */
30
31
  var requestWhitelist = ['url', 'headers', 'method', 'httpVersion', 'originalUrl', 'query'];
31
32
 
32
- // default function to filter the properties of the req object.
33
- var defaultRequestFilter = function(req, propName) {
34
- return req[propName];
33
+ /**
34
+ * A default list of properties in the request body that are allowed to be logged.
35
+ * This will normally be empty here, since it should be done at the route level.
36
+ * @type {Array}
37
+ */
38
+ var bodyWhitelist = [];
39
+
40
+ /**
41
+ * A default list of properties in the response object that are allowed to be logged.
42
+ * These properties will be safely included in the meta of the log.
43
+ * @type {Array}
44
+ */
45
+ var responseWhitelist = ['statusCode'];
46
+
47
+ /**
48
+ * A default function to filter the properties of the req object.
49
+ * @param req
50
+ * @param propName
51
+ * @return {*}
52
+ */
53
+ var defaultRequestFilter = function (req, propName) {
54
+ return req[propName];
35
55
  };
36
56
 
37
- function filterRequest(originalReq, initialFilter) {
38
- var req = {};
39
- Object.keys(originalReq).forEach(function(propName) {
40
- // if the property is not in the whitelist, we return undefined so is not logged as part of the meta.
41
- if(requestWhitelist.indexOf(propName) == -1) return;
42
- var value = initialFilter(originalReq, propName);
43
- if(typeof(value) !== 'undefined') {
44
- req[propName] = value;
45
- }
46
- });
47
- return req;
57
+ /**
58
+ * A default function to filter the properties of the res object.
59
+ * @param res
60
+ * @param propName
61
+ * @return {*}
62
+ */
63
+ var defaultResponseFilter = function (req, propName) {
64
+ return req[propName];
48
65
  };
49
66
 
50
- //
67
+ function filterObject(originalObj, whiteList, initialFilter) {
68
+
69
+ var obj = {};
70
+
71
+ [].concat(whiteList).forEach(function (propName) {
72
+ var value = initialFilter(originalObj, propName);
73
+
74
+ if(typeof (value) !== 'undefined') {
75
+ obj[propName] = value;
76
+ };
77
+ });
78
+
79
+ return obj;
80
+ }
81
+
82
+ //
51
83
  // ### function errorLogger(options)
52
84
  // #### @options {Object} options to initialize the middleware.
53
85
  //
86
+
87
+
54
88
  function errorLogger(options) {
55
- if(!options) throw new Error("options are required by winston-middleware middleware");
56
- if(!options.transports || !(options.transports.length > 0)) throw new Error("transports are required by winston-middleware middleware");
57
- options.requestFilter = options.requestFilter || defaultRequestFilter;
58
- return function(err, req, res, next) {
59
- // let winston gather all the error data.
60
- var exceptionMeta = winston.exception.getAllInfo(err);
61
- exceptionMeta.req = filterRequest(req, options.requestFilter);
62
-
63
- function logOnTransport(transport, nextTransport) {
64
- return transport.logException('middlewareError', exceptionMeta, nextTransport);
65
- };
66
89
 
67
- function done() {
68
- return next(err);
90
+ ensureValidOptions(options);
91
+
92
+ options.requestFilter = options.requestFilter || defaultRequestFilter;
93
+
94
+ return function (err, req, res, next) {
95
+
96
+ // Let winston gather all the error data.
97
+ var exceptionMeta = winston.exception.getAllInfo(err);
98
+ exceptionMeta.req = filterObject(req, requestWhitelist, options.requestFilter);
99
+
100
+ // This is fire and forget, we don't want logging to hold up the request so don't wait for the callback
101
+ for(var i = 0; i < options.transports.length; i++) {
102
+ var transport = options.transports[i];
103
+ transport.logException('middlewareError', exceptionMeta, function () {
104
+ // Nothing to do here
105
+ });
106
+ }
107
+
108
+ next(err);
69
109
  };
70
- // iterate all the transports
71
- async.forEach(options.transports, logOnTransport, done);
72
- };
73
- };
110
+ }
74
111
 
75
- //
112
+ //
76
113
  // ### function logger(options)
77
114
  // #### @options {Object} options to initialize the middleware.
78
115
  //
116
+
117
+
79
118
  function logger(options) {
80
- if(!options) throw new Error("options are required by winston-middleware middleware");
81
- if(!options.transports || !(options.transports.length > 0)) throw new Error("transports are required by winston-middleware middleware");
82
- options.requestFilter = options.requestFilter || defaultRequestFilter;
83
- options.level = options.level || "info";
84
- return function(req, res, next) {
85
- var meta = {
86
- req: filterRequest(req, options.requestFilter)
87
- };
88
- var msg = util.format("HTTP %s %s", req.method, req.url);
89
119
 
90
- function logOnTransport(transport, nextTransport) {
91
- return transport.log(options.level, msg, meta, nextTransport);
92
- };
120
+ ensureValidOptions(options);
121
+
122
+ options.requestFilter = options.requestFilter || defaultRequestFilter;
123
+ options.responseFilter = options.responseFilter || defaultResponseFilter;
124
+ options.level = options.level || "info";
125
+
126
+ return function (req, res, next) {
127
+
128
+ req._startTime = (new Date);
129
+
130
+ req._routeWhitelists = {
131
+ req: [],
132
+ res: [],
133
+ body: []
134
+ };
135
+
136
+ // Manage to get information from the response too, just like Connect.logger does:
137
+ var end = res.end;
138
+ res.end = function(chunk, encoding) {
139
+ var responseTime = (new Date) - req._startTime;
140
+
141
+ res.end = end;
142
+ res.end(chunk, encoding);
93
143
 
94
- function done() {
95
- return next();
144
+ var meta = {};
145
+
146
+ var bodyWhitelist;
147
+
148
+ requestWhitelist = requestWhitelist.concat(req._routeWhitelists.req || []);
149
+ responseWhitelist = responseWhitelist.concat(req._routeWhitelists.res || []);
150
+
151
+ meta.req = filterObject(req, requestWhitelist, options.requestFilter);
152
+ meta.res = filterObject(res, responseWhitelist, options.responseFilter);
153
+
154
+ bodyWhitelist = req._routeWhitelists.body || [];
155
+
156
+ if (bodyWhitelist) {
157
+ meta.req.body = filterObject(req.body, bodyWhitelist, options.requestFilter);
158
+ };
159
+
160
+ meta.responseTime = responseTime;
161
+
162
+ var msg = util.format("HTTP %s %s", req.method, req.url);
163
+
164
+ // This is fire and forget, we don't want logging to hold up the request so don't wait for the callback
165
+ for(var i = 0; i < options.transports.length; i++) {
166
+ var transport = options.transports[i];
167
+ transport.log(options.level, msg, meta, function () {
168
+ // Nothing to do here
169
+ });
170
+ }
171
+ };
172
+
173
+ next();
96
174
  };
97
- // iterate all the transports
98
- async.forEach(options.transports, logOnTransport, done);
99
- };
175
+ }
176
+
177
+ function ensureValidOptions(options) {
178
+ if(!options) throw new Error("options are required by winston-middleware middleware");
179
+ if(!options.transports || !(options.transports.length > 0)) throw new Error("transports are required by winston-middleware middleware");
100
180
  };
101
181
 
102
182
  module.exports.errorLogger = errorLogger;
103
183
  module.exports.logger = logger;
104
184
  module.exports.requestWhitelist = requestWhitelist;
185
+ module.exports.bodyWhitelist = bodyWhitelist;
186
+ module.exports.responseWhitelist = responseWhitelist;
105
187
  module.exports.defaultRequestFilter = defaultRequestFilter;
188
+ module.exports.defaultResponseFilter = defaultResponseFilter;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "name": "winston-middleware",
4
4
  "description": "express.js middleware for flatiron/winston",
5
5
  "keywords": ["winston", "flatiron", "logging", "express", "log", "error", "handler", "middleware"],
6
- "version": "0.1.3",
6
+ "version": "0.2.0",
7
7
  "repository": {
8
8
  "url": "https://github.com/firebaseco/winston-middleware.git"
9
9
  },
@@ -12,8 +12,7 @@
12
12
  "test": "node test/test.js"
13
13
  },
14
14
  "dependencies": {
15
- "winston": "0.6.x",
16
- "async": "0.1.x"
15
+ "winston": "0.6.x"
17
16
  },
18
17
  "devDependencies": {
19
18
  "vows": "0.6.x"
@@ -23,7 +22,7 @@
23
22
  },
24
23
  "licenses" : [
25
24
  {
26
- "type" : "MIT",
25
+ "type" : "MIT",
27
26
  "url" : "https://github.com/firebaseco/winston-middleware/blob/master/LICENSE"
28
27
  }
29
28
  ]
package/test/test.js CHANGED
@@ -15,12 +15,21 @@ vows.describe("exports").addBatch({
15
15
  topic: function() {
16
16
  return expressWinston;
17
17
  },
18
- "an array with all the properties whilelist in the req object": function(exports) {
18
+ "an array with all the properties whitelisted in the req object": function(exports) {
19
19
  assert.isArray(exports.requestWhitelist);
20
20
  },
21
+ "an array with all the properties whitelisted in the res object": function(exports) {
22
+ assert.isArray(exports.responseWhitelist);
23
+ },
24
+ "an array with all the properties whitelisted in the body object": function(exports) {
25
+ assert.isArray(exports.bodyWhitelist);
26
+ },
21
27
  "and the factory should contain a default request filter function": function(exports) {
22
28
  assert.isFunction(exports.defaultRequestFilter);
23
29
  },
30
+ "and the factory should contain a default response filter function": function(exports) {
31
+ assert.isFunction(exports.defaultResponseFilter);
32
+ },
24
33
  "it should export a function for the creation of error loggers middlewares": function(exports) {
25
34
  assert.isFunction(exports.errorLogger);
26
35
  },
@@ -61,7 +70,7 @@ vows.describe("errorLogger").addBatch({
61
70
  var middleware = factory({
62
71
  transports: [
63
72
  new MockTransport({
64
-
73
+
65
74
  })
66
75
  ]
67
76
  });
@@ -86,7 +95,7 @@ vows.describe("errorLogger").addBatch({
86
95
  params: {
87
96
  id: 20
88
97
  },
89
- filteredProperty: "value that should not be logged"
98
+ nonWhitelistedProperty: "value that should not be logged"
90
99
  };
91
100
  var res = {
92
101
 
@@ -134,7 +143,7 @@ vows.describe("errorLogger").addBatch({
134
143
  assert.deepEqual(result.log.meta.req.query, {
135
144
  val: '1'
136
145
  });
137
- assert.isUndefined(result.log.meta.req.filteredProperty);
146
+ assert.isUndefined(result.log.meta.req.nonWhitelistedProperty);
138
147
  },
139
148
  "the winston-middleware middleware should not swallow the pipeline error": function(result) {
140
149
  assert.isNotNull(result.pipelineError);
@@ -143,7 +152,7 @@ vows.describe("errorLogger").addBatch({
143
152
  }
144
153
  }).export(module);
145
154
 
146
- vows.describe("logger").addBatch({
155
+ vows.describe("logger 0.1.x").addBatch({
147
156
  "when I run the middleware factory": {
148
157
  topic: function() {
149
158
  return expressWinston.logger;
@@ -174,7 +183,7 @@ vows.describe("logger").addBatch({
174
183
  var middleware = factory({
175
184
  transports: [
176
185
  new MockTransport({
177
-
186
+
178
187
  })
179
188
  ]
180
189
  });
@@ -202,14 +211,15 @@ vows.describe("logger").addBatch({
202
211
  filteredProperty: "value that should not be logged"
203
212
  };
204
213
  var res = {
205
-
214
+ end: function(chunk, encoding) {}
206
215
  };
207
216
  var test = {
208
217
  req: req,
209
218
  res: res,
210
219
  log: {}
211
220
  };
212
- var next = function() {
221
+ var next = function(_req, _res, next) {
222
+ res.end();
213
223
  return callback(null, test);
214
224
  };
215
225
 
@@ -241,3 +251,134 @@ vows.describe("logger").addBatch({
241
251
  }
242
252
  }
243
253
  }).export(module);
254
+
255
+ vows.describe("logger 0.2.x").addBatch({
256
+ "when I run the middleware factory": {
257
+ topic: function() {
258
+ return expressWinston.logger;
259
+ },
260
+ "without options": {
261
+ "an error should be raised": function(factory) {
262
+ assert.throws(function() {
263
+ factory();
264
+ }, Error);
265
+ }
266
+ },
267
+ "without any transport specified": {
268
+ "an error should be raised": function(factory) {
269
+ assert.throws(function() {
270
+ factory({});
271
+ }, Error);
272
+ }
273
+ },
274
+ "with an empty list of transports": {
275
+ "an error should be raised": function(factory) {
276
+ assert.throws(function() {
277
+ factory({transports:[]});
278
+ }, Error);
279
+ }
280
+ },
281
+ "with proper options": {
282
+ "the result should be a function with three arguments that fit req, res, next": function (factory) {
283
+ var middleware = factory({
284
+ transports: [
285
+ new MockTransport({
286
+
287
+ })
288
+ ]
289
+ });
290
+ assert.equal(middleware.length, 3);
291
+ }
292
+ }
293
+ },
294
+ "When the winston-middleware middleware is invoked in pipeline": {
295
+ topic: function() {
296
+ var factory = expressWinston.logger;
297
+ var callback = this.callback;
298
+ var req = {
299
+ url: "/hello?val=1",
300
+ headers: {
301
+ 'header-1': 'value 1'
302
+ },
303
+ method: 'GET',
304
+ query: {
305
+ val: '2'
306
+ },
307
+ originalUrl: "/hello?val=2",
308
+ params: {
309
+ id: 20
310
+ },
311
+ nonWhitelistedProperty: "value that should not be logged",
312
+ routeLevelAddedProperty: "value that should be logged",
313
+ body: {
314
+ username: 'bobby',
315
+ password: 'top-secret'
316
+ }
317
+ };
318
+ var res = {
319
+ statusCode: 200,
320
+ nonWhitelistedProperty: "value that should not be logged",
321
+ routeLevelAddedProperty: "value that should be logged",
322
+ end: function(chunk, encoding) {
323
+
324
+ }
325
+ };
326
+ var test = {
327
+ req: req,
328
+ res: res,
329
+ log: {}
330
+ };
331
+ var next = function(_req, _res, next) {
332
+ req._startTime = (new Date) - 125;
333
+
334
+ req._routeWhitelists.req = [ 'routeLevelAddedProperty' ];
335
+ req._routeWhitelists.body = [ 'username' ];
336
+
337
+ res.end();
338
+ return callback(null, test);
339
+ };
340
+
341
+ var transport = new MockTransport({});
342
+ transport.log = function(level, msg, meta, cb) {
343
+ test.transportInvoked = true;
344
+ test.log.level = level;
345
+ test.log.msg = msg;
346
+ test.log.meta = meta;
347
+ this.emit('logged');
348
+ return cb();
349
+ };
350
+ var middleware = factory({
351
+ transports: [transport]
352
+ });
353
+ middleware(req, res, next);
354
+ }
355
+ , "then the transport should be invoked": function(result){
356
+ assert.isTrue(result.transportInvoked);
357
+ }
358
+ , "the meta should contain a filtered request": function(result){
359
+ assert.isTrue(!!result.log.meta.req, "req should be defined in meta");
360
+ assert.isNotNull(result.log.meta.req);
361
+ assert.equal(result.log.meta.req.method, "GET");
362
+ assert.deepEqual(result.log.meta.req.query, { val: '2' });
363
+ assert.isUndefined(result.log.meta.req.nonWhitelistedProperty);
364
+
365
+ assert.isNotNull(result.log.meta.req.routeLevelAddedProperty);
366
+ }
367
+ , "the meta should contain a filtered request body": function(result) {
368
+ assert.deepEqual(result.log.meta.req.body, {username: 'bobby'});
369
+ assert.isUndefined(result.log.meta.req.body.password);
370
+ }
371
+ , "the meta should contain a filtered response": function(result){
372
+ assert.isTrue(!!result.log.meta.res, "res should be defined in meta");
373
+ assert.isNotNull(result.log.meta.res);
374
+ assert.equal(result.log.meta.res.statusCode, 200);
375
+ assert.isNotNull(result.log.meta.res.routeLevelAddedProperty);
376
+ }
377
+ , "the meta should contain a response time": function(result){
378
+ assert.isTrue(!!result.log.meta.responseTime, "responseTime should be defined in meta");
379
+ assert.isNotNull(result.log.meta.responseTime);
380
+ assert.isTrue(result.log.meta.responseTime > 120);
381
+ assert.isTrue(result.log.meta.responseTime < 130);
382
+ }
383
+ }
384
+ }).export(module);