Code Monkey home page Code Monkey logo

express-keenio's Introduction

Build Status npm version

Install Keen IO analytics support into your Node.JS Express.js app in mere seconds and instantly begin capturing data.

Once installed it creates Keen.IO events from HTTP requests based on data intercepted from the calls res.json(), res.jsonp(), res.send(), res.render(), res.redirect(), res.sendfile() and res.download().

Read about why the middleware was made and the use cases it solves here.

Getting Started

Sign up to Keen IO for free here. And then install the package from the command line with:

$ npm install express-keenio

Usage

It's possible to use the middleware with specific routes decorator-style, like so:

var express = require("express"),
    keenio = require('express-keenio');

var app = express();

keenio.configure({ client: { projectId: '<test>', writeKey: '<test>'} });
keenio.on('error', console.warn); // There are 'error', 'info', 'warning', 'debug', 'track', and 'flush' events which are emitted.

app.get('/test', keenio.trackRoute('testCollection'), function (req, res) {
  // You code goes here.
});

app.post('/payment', keenio.trackRoute("payments",
                                      { query: ['userId', 'itemId', 'type', 'quantity', 'price'],
                                        reaction: ['receipt.status', 'receipt.tax'] }), function (req, res) {
  // Your code goes here.
});

app.listen(3000);

It's also possible to make the middleware handle all routes by useing it against the app:

var express = require("express"),
    bodyParser = require("body-parser"),
    keenio = require('express-keenio');

var app = express();

keenio.configure({ client: { projectId: '<test>', writeKey: '<test>' } });
app.configure(function () {
   app.use(bodyParser.json());
   app.use(keenio.handleAll());
});

app.get('/test', function (req, res) {
   // Your code goes here.
});

app.listen(3000);

Additionally, the standard Keen.IO Node.JS client is exposed on middleware.keenClient.

What will an event look like?

The middleware will create something that looks sort of like this:

{
  "identity": {
    "user": {
      "name": "Joe Bloggs",
      "email": "[email protected]",
      "age": 17
    },
    "session": {
      "id": "some-identifier"
    },
    "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36"
  },
  "intention": {
    "method": "POST",
    "path": "/pay-user/5",
    "params": {
      "userId": 5
    },
    "body": {
      "price": 5.00
    },
    "query": {},
    "referer": "http://keen.io/"
  },
  "reaction": {
    "success": true,
    "userAddress": "..."
  },
  "httpStatus": 200,
  "environment": {
    "library": "express-keenio"
  }
}

Configuration

Keen.IO Client Configuration

See KeenClient-Node#initialization.

{
  client: {
    projectId: '<test>',
    writeKey: '<test>'
  }
}

Event Property Limitation

Keen.IO has a set limit of 1000 on the number of event properties belonging to an Event Collection and after this it will begin to drop events.

Once you are reliant on analytics I STRONGLY recommend switching to explicit whitelists.

However by default this middleware provides a fallback in the form of eventually rigid schemas. Firstly, by default we accept up to 30 properties in the intention.query, 80 properties in a intention.body, and 120 properties in a reaction. Secondly, after a route receives 500 requests or exists for a week it stops accepting new event properties. Once these properties are discovered we cache them in a file given by options.defaults.eventualSchemas.cachePath (normally, './route-schemas.cache') however this feature can be switched off by giving options.defaults.eventualSchemas.cache the value false or specifying a complete explicit whitelist against a route.

Whitelist Properties

There are some default property whitelists in the form of whitelistProperties.query, whitelistProperties.body, whitelistProperties.reaction. Whitelists can also exist against each route definition or be passed into the second argument of the keenio.trackRoute() function.

Example 1:

{
  client: {
    projectId: '<test>',
    writeKey: '<test>'
  },
  whitelistProperties: {
    query: ['id', 'userId', 'name', 'type'],
    body: [],
    reaction: ['description']
  }
}

NOTE 1: An empty array means nothing is whitelisted while a missing whitelist key means no whitelist should be applied to the data.

NOTE 2: whitelistProperties.query, whitelistProperties.body and whitelistProperties.reaction can take deep property identifiers (e.g. 'deep.array[].name' or 'deep.property.value'.)

Example 2:

app.get('/test', keenio.trackRoute("testEventCollection", { query: ['id', 'userId', 'name', 'type'], body: [] }), function (req, res) {
   // Your code goes here.
});

Blacklist Properties

By default we delete any 'password' properties. If you wish you can pass in a list of other properties you wish to explicitly blacklist as shown below:

{
  client: {
    projectId: '<test>',
    writeKey: '<test>'
  },
  blacklistProperties: ['passwordHash', 'apiKey', 'authToken', 'userKey']
}

NOTE: blacklistProperties takes a property name that can be found anywhere inside an object. This means that 'passwordHash' would delete properties like intention.query.passwordHash and reaction.passwordHash. It does not allow you to specify exact properties at a particular depth like whitelistProperties.query, whitelistProperties.body and whitelistProperties.reaction each allow.

Route Configuration

If you are not using the decorator-style version of the middleware, and would like either (a) more control over which event collections exist or (b) the ability to disable specific event collections you may configure the routes upfront.

You must pick either 'routes' or 'excludeRoutes' but not both.

Excluding routes from default middleware operation

{
  client: {
    projectId: '<test>',
    writeKey: '<test>'
  },
  excludeRoutes: [
    { method: 'GET', route: 'route-name' }
  ]
}

Defining route configuration for middleware operation

{
  client: {
    projectId: '<test>',
    writeKey: '<test>'
  }
  routes: [
    { method: 'GET', route: 'route-name-1', eventCollectionName: '', whitelistProperties: {} },
    { method: 'POST', route: 'route-name-2', eventCollectionName: '' }
  ]
}

Defaults

It's also possible to override some of the default values used by validators, route schemas, etc.

{
  client: {
    projectId: '<test>',
    writeKey: '<test>'
  },
  defaults: {
    addons: {
      ipToGeo: false,
      userAgentParser: false
    },
    MAX_PROPERTY_HIERARCHY_DEPTH: 10,
    MAX_STRING_LENGTH: 1000,
    MAX_PROPERTY_QUANTITY: 300,
    eventualSchemas: {
      cache: true,
      cachePath: './route-schemas.cache',
      query: {
        MAX_PROPERTIES: 30,
        NUMBER_OF_INSTANCES: 500,
        NUMBER_OF_DAYS: 7
      },
      body: {
        MAX_PROPERTIES: 80,
        NUMBER_OF_INSTANCES: 500,
        NUMBER_OF_DAYS: 7
      },
      reaction: {
        MAX_PROPERTIES: 120,
        NUMBER_OF_INSTANCES: 500,
        NUMBER_OF_DAYS: 7
      }
    }
  }
}

Data Enrichment Addons

Keen IO supports two data enrichment addons: IP-to-GEO conversion and UserAgent parsing. If you would like to activate these addons for your project, just ask! The team is available in HipChat, IRC, or at [email protected].

Middleware Overrides

While not recommended it's possible to override some of the internal behaviours of the middleware like so:

{
  client: {
    projectId: '<test>',
    writeKey: '<test>'
  },
  handlers: {
    generateIdentity: function (req) {},
    generateEventCollectionName: function (route) {},
    parseRequestBody: function (body) {},
    parseRequestObject: function (req) {},
    parseResponseBody: function (body) {}
  }
}

Support

Feel free to submit issues and pull requests.

Tests

$ npm install --dev
$ npm test

Contributors

License

BSD 2-Clause License

Copyright (c) 2014, Seb Insua All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  • Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

  • Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

express-keenio's People

Contributors

bitdeli-chef avatar dustinlarimer avatar fgribreau avatar sebinsua avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

express-keenio's Issues

[BUG] Robustness

  • Robustness: make the ways we are tied to Express.JS explicit. Gracefully degrade to Connect.JS.
    Improve the overriding of res.send.

Fails to deploy on Heroku unless Keen-js is installed at version 3.2.5

I guess it has a build step? And in that build step it's hard-coded to look for a side-installed keen-js at exactly version 3.2.5.


npm ERR! Error extracting /app/.npm/keen-js/3.2.5/package.tgz archive: ENOENT: no such file or directory, open '/app/.npm/keen-js/3.2.5/package.tgz'```


If this isn't worth fixing I can deal. I can also try to fix if you think it's worth fixing.

[FIX] Create more sanity checks

  • Number of event properties belonging to a keen event.
  • Sanity check the length of all strings.
  • Make the code more performant. Don't recurse multiple times.

[BUG] Only support json data to begin with.

  • Explicitly whitelist application/json only.
  • Then slowly test individual middleware support:
    • connect.json() <- req.body
    • connect.urlencoded() <- "Parse x-ww-form-urlencoded request bodies, providing the parsed object as req.body using qs."
    • connect.multipart() <- "Parse multipart/form-data request bodies, providing the parsed object as req.body and req.files.""
    • connect.cookieParser() before cookie.session() to set req.session

Possibly crashing when unable to reach keen.io

I'm not positive on this, but I believe when it's unable to hit keen.io it seems to throw an exception. Is this intended? And if so how should I go about catching this error to prevent it from crashing the running application?

events.js:72
        throw er; // Unhandled 'error' event
              ^
Error: getaddrinfo ENOTFOUND
    at errnoException (dns.js:37:11)
    at Object.onanswer [as oncomplete] (dns.js:124:16)

[FEATURE] Validate EventCollections

  • Validate the event collection name with these rules:
    • The name must be 64 characters or less.
    • It must contain only Ascii characters.
    • It cannot have the dollar sign character ($) in it anywhere.
    • It cannot start with an underscore (_)
    • It cannot start nor end with a period (.)
    • Cannot be a null value.
  • Use this: http://stackoverflow.com/questions/14934452/how-to-get-all-registered-routes-in-express
    And check that the event collection names aren't the same for any particular reason, e.g. auto-generated routes longer than 64 characters but not redefined. No possible: see below.
  • Can I get out all of the route paths and use these to build the available event collections before execution? No point.

ipToGeo default

Currently ipToGeo is set to true, however, every newcomer to keenio won't have geo enabled in their account, so express-keenio won't work out of the box. It may be better to set it to false by default

[FEATURE] Add in event batching.

  • Fork the keen.io client library to add in batching.
    https://github.com/keenlabs/KeenClient-Node
    • By default the library will flush:
      the very first time it gets a message
      every N messages
      if S seconds has passed since the last flush
    • If there are too many messages and the module cannot flush faster than it's receiving messages, it will stop accepting messages. It will not crash.
    • Has a message flushed yet? It should be a promise.
    • Should be able to flush manually.
    • Configurable.

Document

  • Document code with docco (https://github.com/jashkenas/docco) and place on http://pages.github.com/
  • Update README.md with these kinds of things:
    • Getting Started
      npm install express-keenio
    • var analytics = require('analytics-node');
      // initialize the username/acme-co write key ...
      analytics.init({ secret: 'YOUR_WRITE_KEY' });
      analytics.on('error', console.log); // remove after setup
    • Put License in README.md
    • Tests
      $ npm install --dev
      $ make test
    • Credits
      • url-to-self
        Expressjs should link to Expressjs. Connect to Connect. Nodejs to Nodejs.
    • No more than 1,000 properties per EventCollection so dynamic naming of properties will be harmful. They should be switched off if possible.

body with "." inside keys

express-keenio (or keenio ?) does not handle well body with dot inside keys, in fact it does not save them on keenio

Error: read ECONNRESET / Error: connect ETIMEDOUT

Recently saw this error in my logs.

Error from express-keenio not handled with .on('error'): Error: read ECONNRESET
    at errnoException (net.js:905:11)
    at TCP.onread (net.js:559:19)
Error from express-keenio not handled with .on('error'): Error: connect ETIMEDOUT
    at errnoException (net.js:905:11)
    at Object.afterConnect [as oncomplete] (net.js:896:19)

I didn't see it actually crashing my application though (I didn't see any application restart in my logs) so if it's just logging this error it's probably not really a problem?

isAcceptableStatusCode

// There is no good reason to send an event to Keen.IO if it's for an invalid response.
// The rule is: if the app wouldn't handle a request, the middleware shouldn't handle it either.
KeenioMiddleware.prototype.isAcceptableStatusCode = function (statusCode) {
  var whitelistCodes = [401, 402, 404],
      firstCharacter = String(statusCode).charAt(0);
  return firstCharacter !== '5' && firstCharacter !== '4' || whitelistCodes.indexOf(statusCode) !== -1;
};

... but what if the developers wants to track api errors with keenio ? (that's was what I was looking for). I think it should be configurable as well :).

workaround

var keenio = require('express-keenio');
keenio.constructor.prototype.isAcceptableStatusCode = function(){
  return true;
};

[FEATURE] Validate Properties

  • Properties must all follow this rule:
    • Must be less than 256 characters long.
    • A dollar sign ($) cannot be the first character.
    • There cannot be any periods (.) in the name.
    • Cannot be a null value.

friendware should be instantiated with a middleware stack.

Hi,

I wanted to use express-keenio to track traffic on routes, and keenio to track custom events. I installed and configured both packages... but when I access '/' I got an error:

Error: friendware should be instantiated with a middleware stack.
   at Object.<anonymous> (/hackathons/yoTimer/node_modules/express-keenio/node_modules/connect-friendwares/index.js:11:15)
   at KeenioMiddleware.isMiddlewareUsedByExpress (/hackathons/yoTimer/node_modules/express-keenio/lib/keenio-middleware.js:82:10)
   at keenioHandler (/hackathons/yoTimer/node_modules/express-keenio/lib/keenio-middleware.js:128:27)
   at Layer.handle [as handle_request] (/hackathons/yoTimer/node_modules/express/lib/router/layer.js:76:5)
   at next (/hackathons/yoTimer/node_modules/express/lib/router/route.js:100:13)
   at Route.dispatch (/hackathons/yoTimer/node_modules/express/lib/router/route.js:81:3)
   at Layer.handle [as handle_request] (/hackathons/yoTimer/node_modules/express/lib/router/layer.js:76:5)
   at /hackathons/yoTimer/node_modules/express/lib/router/index.js:227:24
   at Function.proto.process_params (/hackathons/yoTimer/node_modules/express/lib/router/index.js:305:12)
   at /hackathons/yoTimer/node_modules/express/lib/router/index.js:221:12

my config

var keenio = require('express-keenio');
var Keen = require('keen.io');
keenio.configure({ client: {
    projectId: config.keen.projectId,
    writeKey: config.keen.writeKey,
} });

var client = Keen.configure({
    projectId: config.keen.projectId,
    writeKey: config.keen.writeKey,
});
app.get('/', keenio.trackRoute('indexCollection'),function(req, res){
  res.render('index');  
});

thanks

Refactor

  • Organise the files + tests + code.
  • Rethink the interfaces. How many of them? How are they named? Read premise.
  • Think + code how a refactor should work - for each feature. Fast. Explicit. Secure. Customisable.

Collection name is invalid

Sorry to bring bad news again... Since updating, I'm no longer getting the issue from #36 anymore, however I'm getting these fairly random crashes now. My current setup is pretty basic, just using the defaults right now

keenio.configure({
    client: settings.keenio
});
server.use(keenio.handleAll());

Below is the random crash I'm experiencing.

Error from express-keenio not handled with .on('error'): Error: Collection name is invalid. Must be <= 64 characters, cannot contain a newline or CR.It also cannot contain non-ascii characters. You specified: 'get-post-put-head-delete-options-trace-copy-lock-mkcol-move-purge-propfind-proppatch-unlock-report-mkactivity-checkout-merge-m-search-notify-subscribe-unsubscribe-patch-search-connect-*'.
    at module.exports (/mnt/data/2/node_modules/express-keenio/node_modules/keen-js/src/core/helpers/superagent-handle-response.js:5:49)
    at handleResponse (/mnt/data/2/node_modules/express-keenio/node_modules/keen-js/src/core/lib/addEvent.js:88:5)
    at Request.callback (/mnt/data/2/node_modules/express-keenio/node_modules/keen-js/node_modules/superagent/lib/node/index.js:746:30)
    at Request.<anonymous> (/mnt/data/2/node_modules/express-keenio/node_modules/keen-js/node_modules/superagent/lib/node/index.js:135:10)
    at Request.emit (events.js:95:17)
    at Stream.<anonymous> (/mnt/data/2/node_modules/express-keenio/node_modules/keen-js/node_modules/superagent/lib/node/index.js:938:12)
    at Stream.emit (events.js:117:20)
    at Unzip.<anonymous> (/mnt/data/2/node_modules/express-keenio/node_modules/keen-js/node_modules/superagent/lib/node/utils.js:124:12)
    at Unzip.emit (events.js:117:20)
    at _stream_readable.js:944:16

[FIX] Growing beyond the maximum number of event properties.

  • Considering the idea of some rules to allow eventually rigid schemas. Either: the queries and bodies of the first N requests become the canonical allowed properties, or the first X properties, or after a set amount of time the acceptable properties list gets locked. Thinking that I can grow a schema while the Node.JS app is running and keep it in memory. I mean probably in 95% of cases I could order the properties in the 'intention.query' (and 'intention.body') by the quantity received and the first 15 would be all you would ever want.
  • Property whitelists for 'intention.body', 'intention.query' and 'reaction'.
  • Ways of overriding default configs here. Functions.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.