diff --git a/.ebextensions/options.config b/.ebextensions/options.config index ba195a9..576c436 100644 --- a/.ebextensions/options.config +++ b/.ebextensions/options.config @@ -10,12 +10,3 @@ option_settings: ProxyServer: nginx aws:elasticbeanstalk:container:nodejs:staticfiles: /static: /static - aws:autoscaling:asg: - Cooldown: "120" - aws:autoscaling:trigger: - Unit: "Percent" - Period: "1" - BreachDuration: "2" - UpperThreshold: "75" - LowerThreshold: "30" - MeasureName: "CPUUtilization" diff --git a/.ebextensions/xray.config b/.ebextensions/xray.config new file mode 100644 index 0000000..15ad90d --- /dev/null +++ b/.ebextensions/xray.config @@ -0,0 +1,11 @@ +option_settings: + aws:elasticbeanstalk:xray: + XRayEnabled: true + +files: + "/opt/elasticbeanstalk/tasks/taillogs.d/xray-daemon.conf" : + mode: "000644" + owner: root + group: root + content: | + /var/log/xray/xray.log \ No newline at end of file diff --git a/.gitignore b/.gitignore index 864660f..a996717 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,11 @@ .DS_Store +node_modules +package-lock.json # Elastic Beanstalk CLI Files .elasticbeanstalk/* + +# Elastic Beanstalk Files +.elasticbeanstalk/* +!.elasticbeanstalk/*.cfg.yml +!.elasticbeanstalk/*.global.yml diff --git a/README.md b/README.md index 6dde926..823ef44 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# AWS Elastic Beanstalk Express Sample App +# AWS Elastic Beanstalk Express Sample App Instrumented for use with AWS X-Ray This sample application uses the [Express](https://expressjs.com/) framework and [Bootstrap](http://getbootstrap.com/) to build a simple, scalable customer signup form that is deployed to [AWS Elastic Beanstalk](http://aws.amazon.com/elasticbeanstalk/). The application stores data in [Amazon DynamoDB](http://aws.amazon.com/dynamodb/) and publishes notifications to the [Amazon Simple Notification Service (SNS)](http://aws.amazon.com/sns/) when a customer fills out the form. ## Features diff --git a/app.js b/app.js index 38018fa..08a7b0b 100644 --- a/app.js +++ b/app.js @@ -23,29 +23,43 @@ if (cluster.isMaster) { // Code to run if we're in a worker process } else { - var AWS = require('aws-sdk'); + // Include the AWS X-Ray Node.js SDK and set configuration + var XRay = require('aws-xray-sdk'); + var AWS = XRay.captureAWS(require('aws-sdk')); + var http = XRay.captureHTTPs(require('http')); var express = require('express'); var bodyParser = require('body-parser'); + var queryString = require('querystring'); AWS.config.region = process.env.REGION - var sns = new AWS.SNS(); - var ddb = new AWS.DynamoDB(); + XRay.config([XRay.plugins.EC2Plugin, XRay.plugins.ElasticBeanstalkPlugin]); + XRay.middleware.setSamplingRules('sampling-rules.json'); + XRay.middleware.enableDynamicNaming(); - var ddbTable = process.env.STARTUP_SIGNUP_TABLE; - var snsTopic = process.env.NEW_SIGNUP_TOPIC; var app = express(); + var sns = new AWS.SNS(); + var ddb = new AWS.DynamoDB(); + var ddbTable = process.env.STARTUP_SIGNUP_TABLE; + var snsTopic = process.env.NEW_SIGNUP_TOPIC; + var apiCNAME = process.env.API_CNAME || 'localhost'; app.set('view engine', 'ejs'); app.set('views', __dirname + '/views'); app.use(bodyParser.urlencoded({extended:false})); + app.use(XRay.express.openSegment('myfrontend')); app.get('/', function(req, res) { - res.render('index', { - static_path: 'static', - theme: process.env.THEME || 'flatly', - flask_debug: process.env.FLASK_DEBUG || 'false' + XRay.captureAsyncFunc('Page Render', function(seg) { + res.render('index', { + static_path: 'static', + theme: process.env.THEME || 'flatly', + flask_debug: process.env.FLASK_DEBUG || 'false' + }); + seg.close(); }); + + res.status(200).end(); }); app.post('/signup', function(req, res) { @@ -56,20 +70,22 @@ if (cluster.isMaster) { 'theme': {'S': req.body.theme} }; + var seg = XRay.getSegment(); + seg.addAnnotation('email', req.body.email); + seg.addAnnotation('theme', req.body.theme); + seg.addAnnotation('previewAccess', req.body.previewAccess); + ddb.putItem({ 'TableName': ddbTable, 'Item': item, 'Expected': { email: { Exists: false } } }, function(err, data) { if (err) { - var returnStatus = 500; - if (err.code === 'ConditionalCheckFailedException') { - returnStatus = 409; + res.status(409).end("User already exists"); + } else { + res.status(500).end("DDB Error"); } - - res.status(returnStatus).end(); - console.log('DDB Error: ' + err); } else { sns.publish({ 'Message': 'Name: ' + req.body.name + "\r\nEmail: " + req.body.email @@ -79,16 +95,58 @@ if (cluster.isMaster) { 'TopicArn': snsTopic }, function(err, data) { if (err) { - res.status(500).end(); - console.log('SNS Error: ' + err); + res.status(500).end("SNS Error"); } else { - res.status(201).end(); + res.status(201).end("Success"); } }); } }); }); + app.post('/remoteSignup', function(req, res) { + var seg = XRay.getSegment(); + seg.addAnnotation('theme', req.body.theme); + seg.addAnnotation('previewAccess', req.body.previewAccess); + + var reqData = queryString.stringify(req.body); + + var options = { + host: apiCNAME, + port: '80', + path: '/signup', + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Length': Buffer.byteLength(reqData) + } + }; + + // Set up the request + var remoteReq = http.request(options, function(remoteRes) { + var body = ''; + remoteRes.setEncoding('utf8'); + + remoteRes.on('data', function(chunk) { + body += chunk; + }); + + remoteRes.on('end', function() { + res.status(remoteRes.statusCode).send(body); + }); + }); + + remoteReq.on('error', function(err) { + res.status(500).end("Remote error"); + }); + + // post the data + remoteReq.write(reqData); + remoteReq.end(); + }); + + app.use(XRay.express.closeSegment()); + var port = process.env.PORT || 3000; var server = app.listen(port, function () { diff --git a/iam_policy.json b/iam_policy.json index d6d9322..c74e433 100644 --- a/iam_policy.json +++ b/iam_policy.json @@ -3,13 +3,25 @@ "Statement": [ { "Effect": "Allow", - "Action": [ "dynamodb:PutItem" ], + "Action": [ + "dynamodb:PutItem" + ], "Resource": [ "*" ] }, { "Effect": "Allow", - "Action": [ "sns:Publish" ], + "Action": [ + "sns:Publish" + ], "Resource": [ "*" ] + }, + { + "Effect":"Allow", + "Action": [ + "xray:PutTraceSegments", + "xray:PutTelemetryRecords" + ], + "Resource":[ "*" ] } ] -} \ No newline at end of file +} diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json deleted file mode 100644 index e648c9c..0000000 --- a/npm-shrinkwrap.json +++ /dev/null @@ -1,379 +0,0 @@ -{ - "name": "Elastic-Beanstalk-Sample-App", - "version": "0.0.1", - "dependencies": { - "aws-sdk": { - "version": "2.1.39", - "from": "aws-sdk@latest", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1.39.tgz", - "dependencies": { - "sax": { - "version": "0.5.3", - "from": "sax@0.5.3", - "resolved": "https://registry.npmjs.org/sax/-/sax-0.5.3.tgz" - }, - "xml2js": { - "version": "0.2.8", - "from": "xml2js@0.2.8", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.2.8.tgz" - }, - "xmlbuilder": { - "version": "0.4.2", - "from": "xmlbuilder@0.4.2", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-0.4.2.tgz" - } - } - }, - "body-parser": { - "version": "1.13.2", - "from": "body-parser@latest", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.13.2.tgz", - "dependencies": { - "bytes": { - "version": "2.1.0", - "from": "bytes@2.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.1.0.tgz" - }, - "content-type": { - "version": "1.0.1", - "from": "content-type@~1.0.1", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.1.tgz" - }, - "debug": { - "version": "2.2.0", - "from": "debug@~2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "dependencies": { - "ms": { - "version": "0.7.1", - "from": "ms@0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz" - } - } - }, - "depd": { - "version": "1.0.1", - "from": "depd@~1.0.1", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.0.1.tgz" - }, - "http-errors": { - "version": "1.3.1", - "from": "http-errors@~1.3.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz", - "dependencies": { - "inherits": { - "version": "2.0.1", - "from": "inherits@~2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" - }, - "statuses": { - "version": "1.2.1", - "from": "statuses@1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.2.1.tgz" - } - } - }, - "iconv-lite": { - "version": "0.4.11", - "from": "iconv-lite@0.4.11", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.11.tgz" - }, - "on-finished": { - "version": "2.3.0", - "from": "on-finished@~2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "dependencies": { - "ee-first": { - "version": "1.1.1", - "from": "ee-first@1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" - } - } - }, - "qs": { - "version": "4.0.0", - "from": "qs@4.0.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-4.0.0.tgz" - }, - "raw-body": { - "version": "2.1.2", - "from": "raw-body@~2.1.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.2.tgz", - "dependencies": { - "unpipe": { - "version": "1.0.0", - "from": "unpipe@1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" - } - } - }, - "type-is": { - "version": "1.6.5", - "from": "type-is@~1.6.4", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.5.tgz", - "dependencies": { - "media-typer": { - "version": "0.3.0", - "from": "media-typer@0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" - }, - "mime-types": { - "version": "2.1.3", - "from": "mime-types@~2.1.3", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.3.tgz", - "dependencies": { - "mime-db": { - "version": "1.15.0", - "from": "mime-db@~1.15.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.15.0.tgz" - } - } - } - } - } - } - }, - "ejs": { - "version": "2.3.3", - "from": "ejs@latest", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.3.3.tgz" - }, - "express": { - "version": "4.13.1", - "from": "express@latest", - "resolved": "https://registry.npmjs.org/express/-/express-4.13.1.tgz", - "dependencies": { - "accepts": { - "version": "1.2.11", - "from": "accepts@~1.2.10", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.2.11.tgz", - "dependencies": { - "mime-types": { - "version": "2.1.3", - "from": "mime-types@~2.1.3", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.3.tgz", - "dependencies": { - "mime-db": { - "version": "1.15.0", - "from": "mime-db@~1.15.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.15.0.tgz" - } - } - }, - "negotiator": { - "version": "0.5.3", - "from": "negotiator@0.5.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.5.3.tgz" - } - } - }, - "array-flatten": { - "version": "1.1.0", - "from": "array-flatten@1.1.0", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.0.tgz" - }, - "content-disposition": { - "version": "0.5.0", - "from": "content-disposition@0.5.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.0.tgz" - }, - "content-type": { - "version": "1.0.1", - "from": "content-type@~1.0.1", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.1.tgz" - }, - "cookie": { - "version": "0.1.3", - "from": "cookie@0.1.3", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.3.tgz" - }, - "cookie-signature": { - "version": "1.0.6", - "from": "cookie-signature@1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz" - }, - "debug": { - "version": "2.2.0", - "from": "debug@~2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "dependencies": { - "ms": { - "version": "0.7.1", - "from": "ms@0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz" - } - } - }, - "depd": { - "version": "1.0.1", - "from": "depd@~1.0.1", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.0.1.tgz" - }, - "escape-html": { - "version": "1.0.2", - "from": "escape-html@1.0.2", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.2.tgz" - }, - "etag": { - "version": "1.7.0", - "from": "etag@~1.7.0", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.7.0.tgz" - }, - "finalhandler": { - "version": "0.4.0", - "from": "finalhandler@0.4.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.4.0.tgz", - "dependencies": { - "unpipe": { - "version": "1.0.0", - "from": "unpipe@~1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" - } - } - }, - "fresh": { - "version": "0.3.0", - "from": "fresh@0.3.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.3.0.tgz" - }, - "merge-descriptors": { - "version": "1.0.0", - "from": "merge-descriptors@1.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.0.tgz" - }, - "methods": { - "version": "1.1.1", - "from": "methods@~1.1.1", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.1.tgz" - }, - "on-finished": { - "version": "2.3.0", - "from": "on-finished@~2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "dependencies": { - "ee-first": { - "version": "1.1.1", - "from": "ee-first@1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" - } - } - }, - "parseurl": { - "version": "1.3.0", - "from": "parseurl@~1.3.0", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.0.tgz" - }, - "path-to-regexp": { - "version": "0.1.6", - "from": "path-to-regexp@0.1.6", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.6.tgz" - }, - "proxy-addr": { - "version": "1.0.8", - "from": "proxy-addr@~1.0.8", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.0.8.tgz", - "dependencies": { - "forwarded": { - "version": "0.1.0", - "from": "forwarded@~0.1.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz" - }, - "ipaddr.js": { - "version": "1.0.1", - "from": "ipaddr.js@1.0.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.0.1.tgz" - } - } - }, - "qs": { - "version": "4.0.0", - "from": "qs@4.0.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-4.0.0.tgz" - }, - "range-parser": { - "version": "1.0.2", - "from": "range-parser@~1.0.2", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.0.2.tgz" - }, - "send": { - "version": "0.13.0", - "from": "send@0.13.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.13.0.tgz", - "dependencies": { - "destroy": { - "version": "1.0.3", - "from": "destroy@1.0.3", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.3.tgz" - }, - "http-errors": { - "version": "1.3.1", - "from": "http-errors@~1.3.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz", - "dependencies": { - "inherits": { - "version": "2.0.1", - "from": "inherits@~2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" - } - } - }, - "mime": { - "version": "1.3.4", - "from": "mime@1.3.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz" - }, - "ms": { - "version": "0.7.1", - "from": "ms@0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz" - }, - "statuses": { - "version": "1.2.1", - "from": "statuses@~1.2.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.2.1.tgz" - } - } - }, - "serve-static": { - "version": "1.10.0", - "from": "serve-static@~1.10.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.10.0.tgz" - }, - "type-is": { - "version": "1.6.5", - "from": "type-is@~1.6.4", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.5.tgz", - "dependencies": { - "media-typer": { - "version": "0.3.0", - "from": "media-typer@0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" - }, - "mime-types": { - "version": "2.1.3", - "from": "mime-types@~2.1.3", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.3.tgz", - "dependencies": { - "mime-db": { - "version": "1.15.0", - "from": "mime-db@~1.15.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.15.0.tgz" - } - } - } - } - }, - "vary": { - "version": "1.0.1", - "from": "vary@~1.0.0", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.0.1.tgz" - }, - "utils-merge": { - "version": "1.0.0", - "from": "utils-merge@1.0.0", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz" - } - } - } - } -} diff --git a/package.json b/package.json index 57d32fd..780d139 100644 --- a/package.json +++ b/package.json @@ -3,10 +3,12 @@ "version": "0.0.1", "private": true, "dependencies": { - "ejs": "latest", - "aws-sdk": "latest", - "express": "latest", - "body-parser": "latest" + "ejs": "2.5.5", + "aws-sdk": "2.682.0", + "express": "4.14.0", + "body-parser": "1.16.0", + "querystring": "0.2.0", + "aws-xray-sdk": "3.0.1" }, "scripts": { "start": "node app.js" diff --git a/sampling-rules.json b/sampling-rules.json new file mode 100644 index 0000000..b7f57fd --- /dev/null +++ b/sampling-rules.json @@ -0,0 +1,7 @@ +{ + "version": 1, + "default": { + "fixed_target": 1, + "rate": 1.0 + } +} \ No newline at end of file diff --git a/views/index.ejs b/views/index.ejs index e871601..807cbe6 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -6,18 +6,16 @@ -
+ Aww yeah, you've successfully launched the AWS X-Ray sample application. Use the start/stop buttons below to control the generation of signup requests. The application will generate up to 10 signup requests per minute with a duplicate signup each minute. Alternatively, you can use the form below to manually generate signup requests. Once you've generated a few requests, go to the AWS X-Ray Console to view the service map and traces. +
+Status: click 'Start' to begin
+We're pretty thrilled to unveil our latest creation. Sign up below to be notified when we officially launch!
- ++ We're pretty thrilled to unveil our latest creation. Sign up below to be notified when we officially launch! +
++ Sign up today +