aws-xray-sdk
Advanced tools
Comparing version 1.0.0-beta to 1.0.1-beta
var _ = require('underscore'); | ||
var CLSUtils = require('./utils').CLSUtils; | ||
var BeanstalkPlugin = require('./segments/plugins/beanstalk_plugin'); | ||
var EC2Plugin = require('./segments/plugins/ec2_plugin'); | ||
var ECSPlugin = require('./segments/plugins/ecs_plugin'); | ||
var MWUtils = require('./middleware/mw_utils'); | ||
var SamplingRules = require('./sampling/sampling_rules'); | ||
var Segment = require('./segments/segment'); | ||
var Subsegment = require('./segments/attributes/subsegment'); | ||
var SegmentUtils = require('./segments/segment_utils'); | ||
@@ -28,24 +24,21 @@ /** | ||
/** | ||
* Returns Express.js middleware. | ||
* Exposes the AWS EC2 plugin. | ||
* @memberof AWSXRay.plugins | ||
* @see module:EC2Plugin | ||
*/ | ||
EC2: EC2Plugin, | ||
EC2: require('./segments/plugins/ec2_plugin'), | ||
/** | ||
* Returns Express.js middleware. | ||
* Exposes the AWS ECS plugin. | ||
* @memberof AWSXRay.plugins | ||
* @see module:ECSPlugin | ||
*/ | ||
ECS: ECSPlugin, | ||
ECS: require('./segments/plugins/ecs_plugin'), | ||
/** | ||
* Returns Express.js middleware. | ||
* Exposes the AWS Elastic Beanstalk plugin. | ||
* @memberof AWSXRay.plugins | ||
* @see module:BeanstalkPlugin | ||
*/ | ||
ElasticBeanstalk: BeanstalkPlugin | ||
ElasticBeanstalk: require('./segments/plugins/beanstalk_plugin'), | ||
}, | ||
@@ -84,6 +77,6 @@ | ||
MWUtils.pluginData = pluginData; | ||
SegmentUtils.setPluginData(pluginData); | ||
if (pluginData.beanstalk) | ||
origin = 'AWS::Beanstalk::Environment'; | ||
if (pluginData.elastic_beanstalk) | ||
origin = 'AWS::ElasticBeanstalk::Environment'; | ||
else if (pluginData.ecs) | ||
@@ -95,3 +88,4 @@ origin = 'AWS::ECS::Container'; | ||
if (origin) | ||
MWUtils.origin = origin; | ||
SegmentUtils.setOrigin(origin); | ||
}; | ||
@@ -115,15 +109,2 @@ | ||
/** | ||
* Sets the segment default name. | ||
* @param {string} defaultName - The default name to use. | ||
* @memberof AWSXRay | ||
*/ | ||
setDefaultName: function (defaultName) { | ||
if(_.isUndefined(defaultName) || typeof defaultName !== 'string') | ||
throw new Error('Default segment name must be a string.'); | ||
MWUtils.setDefaultName(defaultName); | ||
}, | ||
/** | ||
* Overrides the default sampling rules file to specify at what rate to sample at for specific routes. | ||
@@ -143,2 +124,13 @@ * The base sampling rules file can be found at /lib/resources/default_sampling_rules.json | ||
/** | ||
* Configures the address and port the daemon is expected to be on. | ||
* @param {string} address - Address of the daemon the segments should be sent to. Expects 'x.x.x.x', ':yyyy' or 'x.x.x.x:yyyy' IPv4 formats. | ||
* @module SegmentEmitter | ||
* @memberof AWSXRay | ||
* @function | ||
* @see module:SegmentEmitter.setDaemonAddress | ||
*/ | ||
setDaemonAddress: require('./segment_emitter').setDaemonAddress, | ||
/** | ||
* @param {string} name - The name of the new subsegment. | ||
@@ -242,7 +234,9 @@ * @param {function} fcn - The function conext to wrap. | ||
/** | ||
* Exposes the Middleware Utils class. | ||
* @memberof AWSXRay | ||
* @type {object} | ||
* @namespace AWSXRay.express | ||
* @see module:mw_utils | ||
*/ | ||
middleware: require('./middleware/mw_utils'), | ||
express: { | ||
@@ -287,3 +281,3 @@ | ||
Segment: Segment, | ||
Segment: require('./segments/segment'), | ||
@@ -296,3 +290,3 @@ /** | ||
Subsegment: Subsegment, | ||
Subsegment: require('./segments/attributes/subsegment'), | ||
}; | ||
@@ -302,3 +296,6 @@ | ||
if (process.env.npm_package_version) | ||
MWUtils.version = process.env.npm_package_version; | ||
SegmentUtils.setServiceVersion(process.env.npm_package_version); | ||
require('pkginfo')(module); | ||
SegmentUtils.setSDKVersion(module.exports.version); | ||
CLSUtils.enableCLSMode(); | ||
@@ -305,0 +302,0 @@ MWUtils.setSampler(new SamplingRules()); |
@@ -6,3 +6,3 @@ var winston = require('winston'); | ||
if (process.env.XRAY_DEBUG_MODE) { | ||
if (process.env.AWS_XRAY_DEBUG_MODE) { | ||
logger = new (winston.Logger)({ | ||
@@ -9,0 +9,0 @@ transports: [ |
/** | ||
* Express_mw module. | ||
* Express middleware module. | ||
* | ||
* Exposes middleware functions to enable automated data capturing on a web service. To enable on a Node.js/Express application, | ||
* Exposes Express middleware functions to enable automated data capturing on a web service. To enable on a Node.js/Express application, | ||
* use 'app.use(AWSXRay.express.openSegment())' before defining your routes. After your routes, before any extra error | ||
@@ -12,4 +12,2 @@ * handling middleware, use 'app.use(AWSXRay.express.closeSegment())'. | ||
var _ = require('underscore'); | ||
var CLSUtils = require('../utils').CLSUtils; | ||
@@ -21,11 +19,7 @@ var MWUtils = require('./mw_utils'); | ||
var XRAY_HEADER = 'x-amzn-trace-id'; | ||
var NAME = process.env.XRAY_TRACING_NAME; | ||
var DEFAULT_NAME = process.env.XRAY_TRACING_DEFAULT_NAME; | ||
/** | ||
* Use 'app.use(AWSXRay.express.openSegment())' before defining your routes. | ||
* Use 'app.use(AWSXRay.express.openSegment('defaultName'))' before defining your routes. | ||
* Use AWSXRay.getSegment() to access the current segment/subsegment. | ||
* Otherwise, for manual mode, this appends the Segment object to the request object as req.segment. | ||
* @param {string} defaultName - The default name for the segment. | ||
* @alias module:express_mw.openSegment | ||
@@ -35,16 +29,15 @@ * @returns {function} | ||
module.exports.openSegment = function openSegment() { | ||
if (_.isUndefined(DEFAULT_NAME) && _.isUndefined(MWUtils.defaultName)) { | ||
throw new Error('Default segment name must be set using "process.env.XRAY_TRACING_DEFAULT_NAME"' + | ||
' or "setDefaultName()".'); | ||
} | ||
module.exports.openSegment = function openSegment(defaultName) { | ||
if (!defaultName || typeof defaultName !== 'string') | ||
throw new Error('Default segment name was not supplied. Please provide a string.'); | ||
MWUtils.setDefaultName(defaultName); | ||
return function open(req, res, next) { | ||
var amznTraceHeader = processHeaders(req); | ||
var amznTraceHeader = MWUtils.processHeaders(req); | ||
var name = MWUtils.resolveName(req.headers.host); | ||
var segment = new Segment(name, amznTraceHeader.Root, amznTraceHeader.Parent); | ||
var segment = new Segment(resolveName(req.headers.host), amznTraceHeader.Root, amznTraceHeader.Parent); | ||
resolveSampling(amznTraceHeader, segment, res); | ||
MWUtils.resolveSampling(amznTraceHeader, segment, res); | ||
segment.addAttribute('http', new Local(req)); | ||
MWUtils.populate(segment); | ||
@@ -85,3 +78,3 @@ res.on('finish', function () { | ||
return function close(err, req, res, next) { | ||
var segment = CLSUtils.isCLSMode() ? CLSUtils.getSegment() : segment = req.segment; | ||
var segment = CLSUtils.isCLSMode() ? CLSUtils.getSegment() : req.segment; | ||
@@ -99,40 +92,1 @@ if (segment && err) { | ||
}; | ||
function processHeaders(req) { | ||
var amznTraceHeader = {}; | ||
if (req && req.headers && req.headers[XRAY_HEADER]) { | ||
_.each(req.headers[XRAY_HEADER].replace(/ /g,'').split(';'), function (header) { | ||
var pair = header.split('='); | ||
this[pair[0]] = pair[1]; | ||
}, amznTraceHeader); | ||
} | ||
return amznTraceHeader; | ||
} | ||
function resolveName(hostName) { | ||
if (NAME) | ||
return NAME; | ||
var regex = new RegExp('(?:[0-9]{1,3}\.){3}[0-9]{1,3}', 'g'); | ||
var hostIp = hostName.match(regex) || _.isEmpty(hostName); | ||
return !hostIp ? hostName : (DEFAULT_NAME ? DEFAULT_NAME : MWUtils.defaultName); | ||
} | ||
function resolveSampling(amznTraceHeader, segment, res) { | ||
var isSampled; | ||
if (amznTraceHeader.Sampled === '1') | ||
isSampled = true; | ||
else if (amznTraceHeader.Sampled === '0') | ||
isSampled = false; | ||
isSampled = !_.isUndefined(isSampled) ? isSampled : MWUtils.shouldSample(); | ||
if (amznTraceHeader.Sampled === '?') | ||
res.header[XRAY_HEADER] = 'Root=' + amznTraceHeader.Root + '; Sampled=' + (isSampled ? '1' : '0'); | ||
if (!isSampled) | ||
segment.notTraced = true; | ||
} |
@@ -0,23 +1,113 @@ | ||
/** | ||
* Middleware Utils module. | ||
* | ||
* Exposes various configuration and helper methods to be used by the middleware. | ||
* @module mw_utils | ||
*/ | ||
var _ = require('underscore'); | ||
var wildcardMatch = require('../utils').wildcardMatch; | ||
//headers are case-insensitive | ||
var XRAY_HEADER = 'x-amzn-trace-id'; | ||
var overrideFlag = !!process.env.AWS_XRAY_TRACING_NAME || !!process.env.XRAY_TRACING_NAME; | ||
var utils = { | ||
setSampler: function setSampler(samp) { | ||
this.sampler = samp; | ||
defaultName: process.env.AWS_XRAY_TRACING_NAME || process.env.XRAY_TRACING_NAME, | ||
dynamicNaming: false, | ||
hostPattern: null, | ||
/** | ||
* Enables dynamic naming for segments via the middleware. Use 'AWSXRay.middleware.enableDynamicNaming()'. | ||
* @param {string} [hostPattern] - The pattern to match the host header. See the README on dynamic and fixed naming modes. | ||
* @alias module:mw_utils.enableDynamicNaming | ||
*/ | ||
enableDynamicNaming: function(hostPattern) { | ||
this.dynamicNaming = true; | ||
if (hostPattern && typeof hostPattern !== 'string') | ||
throw new Error('Host pattern must be a string.'); | ||
this.hostPattern = hostPattern || null; | ||
}, | ||
setDefaultName: function setDefaultName(name) { | ||
this.defaultName = name; | ||
/** | ||
* Splits out the 'x-amzn-trace-id' header params from the incoming request. Used by the middleware. | ||
* @param {http.IncomingMessage|https.IncomingMessage} req - The request object from the incoming call. | ||
* @returns {object} | ||
* @alias module:mw_utils.processHeaders | ||
*/ | ||
processHeaders: function processHeaders(req) { | ||
var amznTraceHeader = {}; | ||
if (req && req.headers && req.headers[XRAY_HEADER]) { | ||
_.each(req.headers[XRAY_HEADER].split(';'), function (header) { | ||
var pair = header.split('='); | ||
this[pair[0].trim()] = pair[1].trim(); | ||
}, amznTraceHeader); | ||
} | ||
return amznTraceHeader; | ||
}, | ||
shouldSample: function shouldSample() { | ||
return this.sampler.shouldSample(); | ||
/** | ||
* Resolves the name of the segment as determined by fixed or dynamic mode options. Used by the middleware. | ||
* @param {string} hostHeader - The string from the request.headers.host property. | ||
* @returns {string} | ||
* @alias module:mw_utils.resolveName | ||
*/ | ||
resolveName: function resolveName(hostHeader) { | ||
var name; | ||
if (this.dynamicNaming && hostHeader) | ||
name = this.hostPattern ? (wildcardMatch(this.hostPattern, hostHeader) ? hostHeader : this.defaultName) : hostHeader; | ||
else | ||
name = this.defaultName; | ||
return name; | ||
}, | ||
populate: function populate(segment) { | ||
if (this.version) | ||
segment.addServiceVersion(this.version); | ||
/** | ||
* Resolves the sampling decision as determined by the values given and options set. Used by the middleware. | ||
* @param {object} amznTraceHeader - The object as returned by the processHeaders function. | ||
* @param {Segment} segment - The string from the request.headers.host property. | ||
* @param {http.ServerResponse|https.ServerResponse} res - The response object from the incoming call. | ||
* @returns {boolean} | ||
* @alias module:mw_utils.resolveSampling | ||
*/ | ||
if (this.pluginData) | ||
segment.addPluginData(this.pluginData); | ||
resolveSampling: function resolveSampling(amznTraceHeader, segment, res) { | ||
var isSampled; | ||
if (this.origin) | ||
segment.origin = this.origin; | ||
if (amznTraceHeader.Sampled === '1') | ||
isSampled = true; | ||
else if (amznTraceHeader.Sampled === '0') | ||
isSampled = false; | ||
else | ||
isSampled = this.sampler.shouldSample(); | ||
if (amznTraceHeader.Sampled === '?') | ||
res.header[XRAY_HEADER] = 'Root=' + amznTraceHeader.Root + '; Sampled=' + (isSampled ? '1' : '0'); | ||
if (!isSampled) | ||
segment.notTraced = true; | ||
}, | ||
/** | ||
* Sets the default name of created segments. Used with the middleware. | ||
* Can be overridden by the AWS_XRAY_TRACING_NAME (or XRAY_TRACING_NAME) environment variable. | ||
* @param {string} name - The default name for segments created in the middleware. | ||
* @alias module:mw_utils.setDefaultName | ||
*/ | ||
setDefaultName: function setDefaultName(name) { | ||
if (!overrideFlag) | ||
this.defaultName = name; | ||
}, | ||
setSampler: function setSampler(samp) { | ||
this.sampler = samp; | ||
} | ||
@@ -24,0 +114,0 @@ }; |
@@ -12,6 +12,8 @@ /** | ||
var CLSUtils = require('../utils').CLSUtils; | ||
var Utils = require('../utils'); | ||
var Utils = require('../utils'); | ||
var logger = require('../logger'); | ||
var minVersion = '2.7.15'; | ||
/** | ||
@@ -28,4 +30,4 @@ * Configures the AWS SDK to automatically capture information for the segment. | ||
var captureAWS = function captureAWS(awssdk) { | ||
if (!semver.gte(awssdk.VERSION, '2.7.2')) | ||
throw new Error ('AWS SDK version 2.7.2 or greater required.'); | ||
if (!semver.gte(awssdk.VERSION, minVersion)) | ||
throw new Error ('AWS SDK version ' + minVersion + ' or greater required.'); | ||
@@ -58,6 +60,6 @@ for (var prop in awssdk) { | ||
function captureAWSRequest(req) { | ||
var parent = CLSUtils.isCLSMode() ? CLSUtils.getSegment() : req.params.Segment; | ||
var parent = CLSUtils.isCLSMode() ? CLSUtils.getSegment() : (req.params ? req.params.Segment : null); | ||
if (_.isUndefined(parent)) { | ||
logger.info('Call ' + this.config.signatureVersion + ' requires a Segment in params for tracing.'); | ||
if (!parent) { | ||
logger.info('Call ' + this.serviceIdentifier + '.' + req.operation + ' requires a segment object for tracing.'); | ||
return req; | ||
@@ -83,13 +85,14 @@ } | ||
if (res.retryCount > 0) | ||
parent.addThrottle(); | ||
if (!_.isEmpty(e)) { | ||
e = { message: e.message, name: e.code, stack: stack }; | ||
subsegment.addAttribute('remote', true); | ||
if (res.httpResponse && res.httpResponse.statusCode === 429) | ||
subsegment.addThrottle(); | ||
if (res.httpResponse && res.httpResponse.statusCode) { | ||
if (res.httpResponse.statusCode === 429) | ||
subsegment.addThrottle(); | ||
subsegment.close(e, Utils.getCauseTypeFromHttpStatus(res.httpResponse.statusCode)); | ||
subsegment.close(e, Utils.getCauseTypeFromHttpStatus(res.httpResponse.statusCode), true); | ||
} else { | ||
subsegment.close(e); | ||
} | ||
} else { | ||
@@ -96,0 +99,0 @@ subsegment.close(); |
@@ -34,5 +34,6 @@ /** | ||
var parent = options.Segment ? options.Segment : CLSUtils.getSegment(); | ||
var hostname = options.hostname || options.host || 'Unknown host'; | ||
if (_.isUndefined(parent)) { | ||
logger.info('Options for request [ host:' + options.host + ', method:' + options.method + ', path:' + | ||
logger.info('Options for request [ host:' + hostname + ', method:' + options.method + ', path:' + | ||
options.path + '] requires a Segment param for manual mode.'); | ||
@@ -45,3 +46,3 @@ return module.__request(options, callback); | ||
var subsegment = parent.addNewSubsegment(options.host || 'Unkown Host'); | ||
var subsegment = parent.addNewSubsegment(hostname); | ||
subsegment.addRemote(); | ||
@@ -71,8 +72,5 @@ | ||
if (subsegment.http) { | ||
if (subsegment.http.response.status === 429) | ||
subsegment.addThrottle(); | ||
subsegment.close(e, getCauseType(subsegment.http.response.status)); | ||
subsegment.close(e, getCauseType(subsegment.http.response.status), true); | ||
} else { | ||
subsegment.addRemoteData(req, undefined, traced); | ||
subsegment.addRemoteData(req, null, traced); | ||
subsegment.close(e); | ||
@@ -79,0 +77,0 @@ } |
@@ -67,6 +67,6 @@ /** | ||
args.sql = argsObj[0]; | ||
args.values = typeof argsObj[1] !== 'function' ? argsObj[1] : undefined; | ||
args.callback = !args.values ? argsObj[1] : (typeof argsObj[2] === 'function' ? argsObj[2] : undefined); | ||
args.values = typeof argsObj[1] !== 'function' ? argsObj[1] : null; | ||
args.callback = !args.values ? argsObj[1] : (typeof argsObj[2] === 'function' ? argsObj[2] : null); | ||
args.segment = (argsObj[argsObj.length-1].constructor && (argsObj[argsObj.length-1].constructor.name === 'Segment' || | ||
argsObj[argsObj.length-1].constructor.name === 'Subsegment')) ? argsObj[argsObj.length-1] : undefined; | ||
argsObj[argsObj.length-1].constructor.name === 'Subsegment')) ? argsObj[argsObj.length-1] : null; | ||
} | ||
@@ -141,3 +141,3 @@ | ||
function createSqlData(config, query) { | ||
var queryType = query.values ? PREPARED : undefined; | ||
var queryType = query.values ? PREPARED : null; | ||
@@ -144,0 +144,0 @@ var data = new SqlData(DATABASE_VERS, DRIVER_VERS, config.user, |
@@ -6,2 +6,3 @@ var _ = require('underscore'); | ||
var Sampler = require('./sampler'); | ||
var Utils = require('../utils'); | ||
@@ -18,3 +19,3 @@ var rulesLocation = path.join(__dirname, '..', 'resources', 'default_sampling_rules.json'); | ||
function SamplingRules (location) { | ||
function SamplingRules(location) { | ||
this.init(location); | ||
@@ -34,4 +35,4 @@ } | ||
if (ruleEntry.id === DEFAULT || (serviceName.match(rule.service_name) | ||
&& httpMethod.match(rule.http_method) && urlPath.match(rule.url_path))) { | ||
if (ruleEntry.id === DEFAULT || (Utils.wildcardMatch(rule.service_name, serviceName) | ||
&& Utils.wildcardMatch(rule.http_method, httpMethod) && Utils.wildcardMatch(rule.url_path, urlPath))) { | ||
@@ -49,3 +50,3 @@ matchedRule = rule; | ||
function addRulesConfig (location) { | ||
function addRulesConfig(location) { | ||
var doc = loadRules(location); | ||
@@ -58,3 +59,3 @@ var rules = []; | ||
if (!_.isNaN(parseInt(id))) { | ||
var rule = parseIntoRegex(rawRule); | ||
var rule = parseIntoRuleParams(rawRule); | ||
rule.sampler = new Sampler(rawRule.fixed_target, rawRule.rate); | ||
@@ -79,3 +80,3 @@ | ||
function parseIntoRegex (rule) { | ||
function parseIntoRuleParams(rule) { | ||
var params = _.omit(rule, 'fixed_target'); | ||
@@ -85,15 +86,6 @@ params = _.omit(params, 'rate'); | ||
_.each(params, function(matcher, attribute) { | ||
var regex, regexString; | ||
if (attribute != 'service_name' && attribute != 'http_method' && attribute != 'url_path') | ||
throw new Error('Unkown rule attribute "' + attribute + '".'); | ||
throw new Error('Unknown rule attribute "' + attribute + '".'); | ||
if (matcher == '*') { | ||
regex = new RegExp('.*', 'g'); | ||
} else { | ||
regexString = matcher.replace(/\//g, '\\/').replace(/\./g, '\\.').replace(/\*/, '.*'); | ||
regex = new RegExp(regexString, 'g'); | ||
} | ||
params[attribute] = regex; | ||
params[attribute] = matcher; | ||
}, this); | ||
@@ -104,3 +96,3 @@ | ||
function loadRules (location) { | ||
function loadRules(location) { | ||
var doc = JSON.parse(fs.readFileSync(location, 'utf8')); | ||
@@ -107,0 +99,0 @@ |
@@ -5,9 +5,17 @@ var dgram = require('dgram'); | ||
var CLIENT = dgram.createSocket('udp4'); | ||
var DEFAULT_ADDRESS = '127.0.0.1'; | ||
var DEFAULT_PORT = 2000; | ||
var PROTOCOL_HEADER = '{"format": "json", "version": 1}'; | ||
var PROTOCOL_DELIMITER = '\n'; | ||
var port = 2000; | ||
var address = '127.0.0.1'; | ||
/** | ||
* Segment emitter module. | ||
* @module SegmentEmitter | ||
*/ | ||
var SegmentEmitter = { | ||
daemonAddress: DEFAULT_ADDRESS, | ||
daemonPort: DEFAULT_PORT, | ||
/** | ||
@@ -35,5 +43,3 @@ * Returns the formatted segment JSON string. | ||
var client = dgram.createSocket('udp4'); | ||
client.send(message, 0, message.length, port, address, function(err) { | ||
CLIENT.send(message, 0, message.length, this.daemonPort, this.daemonAddress, function(err) { | ||
if (err) | ||
@@ -43,13 +49,34 @@ logger.error('Error occured sending segment: ', err); | ||
logger.info('UDP message sent: ' + segment); | ||
client.close(); | ||
}); | ||
}, | ||
setDaemonAddressAndPort: function setDaemonAddressAndPort(hostAddr, portNumber) { | ||
address = hostAddr; | ||
port = portNumber; | ||
/** | ||
* Configures the address and/or port the daemon is expected to be on. | ||
* @param {string} address - Address of the daemon the segments should be sent to. Should be formatted as an IPv4 address. | ||
* @module SegmentEmitter | ||
* @function setDaemonAddress | ||
*/ | ||
setDaemonAddress: function setDaemonAddress(address) { | ||
if (!process.env.AWS_XRAY_DAEMON_ADDRESS) { | ||
processAddress(address); | ||
} | ||
} | ||
}; | ||
var processAddress = function processAddress(rawAddress) { | ||
var splitAddress = rawAddress.split(':'); | ||
if (splitAddress[0] && splitAddress[0].indexOf('.') !== -1) | ||
SegmentEmitter.daemonAddress = splitAddress[0]; | ||
if (splitAddress[1]) | ||
SegmentEmitter.daemonPort = parseInt(splitAddress[1]); | ||
logger.info('Configured daemon address to ' + SegmentEmitter.daemonAddress + ':' + SegmentEmitter.daemonPort); | ||
}; | ||
if (process.env.AWS_XRAY_DAEMON_ADDRESS) | ||
processAddress(process.env.AWS_XRAY_DAEMON_ADDRESS); | ||
module.exports = SegmentEmitter; |
@@ -6,3 +6,3 @@ var _ = require('underscore'); | ||
* @constructor | ||
* @param {http.IncomingMessage|https.IncomingMessage} req - The response object from the HTTP/HTTPS call. | ||
* @param {http.IncomingMessage|https.IncomingMessage} req - The request object from the HTTP/HTTPS call. | ||
*/ | ||
@@ -9,0 +9,0 @@ |
var _ = require('underscore'); | ||
var crypto = require('crypto'); | ||
var LocalException = require('./local_exception'); | ||
var CapturedException = require('./captured_exception'); | ||
var Remote = require('./remote'); | ||
var SegmentUtils = require('../segment_utils'); | ||
@@ -20,5 +21,8 @@ var logger = require('../../logger'); | ||
Subsegment.prototype.init = function init(name) { | ||
if (typeof name != 'string') | ||
throw new Error('Subsegment name must be of type string.'); | ||
this.id = crypto.randomBytes(8).toString('hex'); | ||
this.name = name; | ||
this.start_time = new Date().getTime()/1000; | ||
this.start_time = SegmentUtils.getCurrentTime(); | ||
}; | ||
@@ -114,3 +118,3 @@ | ||
* @param {string} key - The name of the key to add. | ||
* @param {boolean|string|number} value - The value of the associated key. | ||
* @param {object|null} value - The value of the associated key. | ||
*/ | ||
@@ -142,10 +146,11 @@ | ||
* on each subsegment to the originating subsegment. | ||
* @param {Error} e - The error to add. | ||
* @param {'fault'|'error'} type - The type of error, user fault or service error. | ||
* @param {Error|String} err - The error to capture. | ||
* @param {'fault'|'error'} [type] - The type of error caught. Valid values are 'fault' and 'error'. | ||
* @param {boolean} [remote] - Flag for whether the exception caught was remote or not. | ||
*/ | ||
Subsegment.prototype.addError = function(e, type) { | ||
if (!e) { | ||
throw new Error('Failed to add error:' + e + ' to subsegment "' + this.name + | ||
'". Not an error.'); | ||
Subsegment.prototype.addError = function(err, type, remote) { | ||
if (!_.isObject(err) || !(typeof(s) !== 'string')) { | ||
throw new Error('Failed to add error:' + err + ' to subsegment "' + this.name + | ||
'". Not an object or string literal.'); | ||
} | ||
@@ -159,4 +164,4 @@ | ||
if (this.segment && this.segment.exception) { | ||
if (e === this.segment.exception.ex) { | ||
this.addFault(); | ||
if (err === this.segment.exception.ex) { | ||
this.fault = true; | ||
this.cause = { id: this.segment.exception.cause }; | ||
@@ -171,3 +176,3 @@ return; | ||
this.segment.exception = { | ||
ex: e, | ||
ex: err, | ||
cause: this.id | ||
@@ -187,3 +192,3 @@ }; | ||
this.cause.exceptions.unshift(new LocalException(e)); | ||
this.cause.exceptions.unshift(new CapturedException(err, remote)); | ||
}; | ||
@@ -215,19 +220,13 @@ | ||
/** | ||
* Adds fault flag into the subsegment. | ||
*/ | ||
Subsegment.prototype.addFault = function addFault() { | ||
this.fault = true; | ||
}; | ||
/** | ||
* Closes the current subsegment. This automatically captures any exceptions and sets the end time. | ||
* @param {Error} e - The exception thrown while the activity was in progress. | ||
* @param {Error|String} [err] - The error to capture. | ||
* @param {'fault'|'error'} [type] - The type of error caught. Valid values are 'fault' and 'error'. | ||
* @param {boolean} [remote] - Flag for whether the exception caught was remote or not. | ||
*/ | ||
Subsegment.prototype.close = function close(e, type) { | ||
this.end_time = new Date().getTime()/1000; | ||
Subsegment.prototype.close = function close(err, type, remote) { | ||
this.end_time = SegmentUtils.getCurrentTime(); | ||
if (e) | ||
this.addError(e, type); | ||
if (err) | ||
this.addError(err, type, remote); | ||
@@ -234,0 +233,0 @@ if (this.segment) { |
@@ -10,3 +10,2 @@ var fs = require('fs'); | ||
* @param {function} callback - The callback for the plugin loader. | ||
* @exports BeanstalkPlugin | ||
*/ | ||
@@ -13,0 +12,0 @@ |
@@ -6,3 +6,2 @@ var Plugin = require('./plugin'); | ||
* @param {function} callback - The callback for the plugin loader. | ||
* @exports EC2Plugin | ||
*/ | ||
@@ -9,0 +8,0 @@ |
@@ -6,7 +6,6 @@ var os = require('os'); | ||
* @param {function} callback - The callback for the plugin loader. | ||
* @exports ECSPlugin | ||
*/ | ||
var ECSPlugin = function ECSPlugin(callback) { | ||
var metadata = { ecs: { conainter: os.hostname() }}; | ||
var metadata = { ecs: { container: os.hostname() }}; | ||
@@ -13,0 +12,0 @@ callback(metadata); |
@@ -5,4 +5,5 @@ var crypto = require('crypto'); | ||
var SegmentEmitter = require('../segment_emitter'); | ||
var SegmentUtils = require('./segment_utils'); | ||
var Subsegment = require('./attributes/subsegment'); | ||
var LocalException = require('./attributes/local_exception'); | ||
var CapturedException = require('./attributes/captured_exception'); | ||
@@ -19,3 +20,3 @@ var logger = require('../logger'); | ||
function Segment (name, rootId, parentId) { | ||
function Segment(name, rootId, parentId) { | ||
this.init(name, rootId, parentId); | ||
@@ -25,2 +26,5 @@ } | ||
Segment.prototype.init = function init(name, rootId, parentId) { | ||
if (typeof name != 'string') | ||
throw new Error('Segment name must be of type string.'); | ||
var traceId = rootId || '1-' + Math.round(new Date().getTime() / 1000).toString(16) + '-' + | ||
@@ -30,3 +34,3 @@ crypto.randomBytes(12).toString('hex'); | ||
var id = crypto.randomBytes(8).toString('hex'); | ||
var startTime = new Date().getTime()/1000; | ||
var startTime = SegmentUtils.getCurrentTime(); | ||
@@ -44,6 +48,8 @@ _.extend(this, { | ||
this.parent_id = parentId; | ||
SegmentUtils.populate(this); | ||
}; | ||
/** | ||
* Adds a property with associated data into the subsegment. | ||
* Adds a property with associated data into the segment. | ||
* @param {string} name - The name of the property to add. | ||
@@ -78,6 +84,48 @@ * @param {Object} data - The data of the property to add. | ||
/** | ||
* Adds a service with associated version data into the subsegment. | ||
* Adds a key-value pair to the metadata.default attribute. | ||
* Metadata is not queryable, but is recorded. | ||
* @param {string} key - The name of the key to add. | ||
* @param {object|null} value - The value of the associated key. | ||
*/ | ||
Segment.prototype.addMetadata = function(key, value) { | ||
if (!_.isString(key)) { | ||
throw new Error('Failed to add annotation key: ' + key + ' value: ' + value + ' to subsegment ' + | ||
this.name + '. Key must be of type string.'); | ||
} | ||
if (!(this.metadata)) { | ||
this.metadata = { default: {} }; | ||
} else if (!(this.metadata.default)) { | ||
this.metadata.default = {}; | ||
} | ||
this.metadata.default[key] = value; | ||
}; | ||
/** | ||
* Adds a service with associated version data into the segment. | ||
* @param {string} version - The version of the application. | ||
*/ | ||
Segment.prototype.addSDKVersion = function addSDKVersion(version) { | ||
if (!_.isString(version)) { | ||
logger.error('Add SDK version: ' + version + ' failed.' + | ||
'SDK version must be of type string.'); | ||
return; | ||
} | ||
var versionObj = { xray: { sdk: { version: version }}}; | ||
if (this.aws) | ||
_.extend(this.aws, versionObj); | ||
else | ||
this.aws = versionObj; | ||
}; | ||
/** | ||
* Adds a service with associated version data into the segment. | ||
* @param {string} version - The version of the application. | ||
*/ | ||
Segment.prototype.addServiceVersion = function addServiceVersion(version) { | ||
@@ -94,3 +142,3 @@ if (!_.isString(version)) { | ||
/** | ||
* Adds a service with associated version data into the subsegment. | ||
* Adds a service with associated version data into the segment. | ||
* @param {Object} data - The associated AWS data. | ||
@@ -123,2 +171,5 @@ */ | ||
Segment.prototype.addSubsegment = function addSubsegment(subsegment) { | ||
if (!(subsegment instanceof Subsegment)) | ||
throw new Error('Cannot add subsegment: ' + subsegment.toString() + '. Not a subsegment.'); | ||
if (_.isUndefined(this.subsegments)) | ||
@@ -135,11 +186,21 @@ this.subsegments = []; | ||
/** | ||
* Adds error data into the subsegment. | ||
* @param {Error} e - The error. | ||
* Adds error data into the segment. | ||
* @param {Error|String} err - The error to capture. | ||
* @param {'fault'|'error'} [type] - The type of error caught. Valid values are 'fault' and 'error'. | ||
* @param {boolean} [remote] - Flag for whether the exception caught was remote or not. | ||
*/ | ||
Segment.prototype.addError = function addError(e) { | ||
this.addFault(); | ||
Segment.prototype.addError = function addError(err, type, remote) { | ||
if (!_.isObject(err) || !(typeof(s) !== 'string')) { | ||
throw new Error('Failed to add error:' + err + ' to subsegment "' + this.name + | ||
'". Not an object or string literal.'); | ||
} | ||
if (type != 'fault' && type != 'error') | ||
type = 'fault'; | ||
this[type] = true; | ||
if (this.exception) { | ||
if (e === this.exception.ex) { | ||
if (err === this.exception.ex) { | ||
this.cause = { id: this.exception.cause }; | ||
@@ -160,3 +221,3 @@ delete this.exception; | ||
this.cause.exceptions.push(new LocalException(e)); | ||
this.cause.exceptions.push(new CapturedException(err, remote)); | ||
}; | ||
@@ -173,10 +234,2 @@ | ||
/** | ||
* Adds fault flag into the segment. | ||
*/ | ||
Segment.prototype.addFault = function addFault() { | ||
this.fault = true; | ||
}; | ||
/** | ||
* Each segment holds a counter of open subsegments. This decrements | ||
@@ -195,10 +248,13 @@ * the counter and flushes the segment if the counter hit zero. | ||
* Closes the current segment. This automatically sets the end time. | ||
* @param {Error|String} [err] - The error to capture. | ||
* @param {'fault'|'error'} [type] - The type of error caught. Valid values are 'fault' and 'error'. | ||
* @param {boolean} [remote] - Flag for whether the exception caught was remote or not. | ||
*/ | ||
Segment.prototype.close = function (e) { | ||
if(!this.end_time) | ||
this.end_time = new Date().getTime()/1000; | ||
Segment.prototype.close = function(err, type, remote) { | ||
if (!this.end_time) | ||
this.end_time = SegmentUtils.getCurrentTime(); | ||
if(!_.isUndefined(e)) | ||
this.addError(e); | ||
if (!_.isUndefined(err)) | ||
this.addError(err, type, remote); | ||
@@ -205,0 +261,0 @@ delete this.in_progress; |
105
lib/utils.js
@@ -22,3 +22,3 @@ /** | ||
getNamespace: function () { | ||
getNamespace: function getNamespace() { | ||
return cls.getNamespace(NAMESPACE); | ||
@@ -34,3 +34,3 @@ }, | ||
getSegment: function () { | ||
getSegment: function getSegment() { | ||
if (cls_mode) | ||
@@ -40,3 +40,3 @@ return cls.getNamespace(NAMESPACE).get(SEGMENT); | ||
setSegment: function (segment) { | ||
setSegment: function setSegment(segment) { | ||
if (cls_mode) | ||
@@ -46,3 +46,3 @@ return cls.getNamespace(NAMESPACE).set(SEGMENT, segment); | ||
isCLSMode: function () { | ||
isCLSMode: function isCLSMode() { | ||
return cls_mode; | ||
@@ -58,3 +58,3 @@ }, | ||
enableCLSMode: function () { | ||
enableCLSMode: function enableCLSMode() { | ||
cls_mode = true; | ||
@@ -71,3 +71,3 @@ cls.createNamespace(NAMESPACE); | ||
enableManualMode: function () { | ||
enableManualMode: function enableManualMode() { | ||
cls_mode = false; | ||
@@ -80,3 +80,3 @@ | ||
getCauseTypeFromHttpStatus: function (status) { | ||
getCauseTypeFromHttpStatus: function getCauseTypeFromHttpStatus(status) { | ||
var stat = status.toString(); | ||
@@ -87,2 +87,93 @@ if (!_.isNull(stat.match(/^[4][0-9]{2}$/))) | ||
return 'fault'; | ||
}, | ||
/** | ||
* Performs a case-insensitive wildcard match against two strings. This method works with pseduo-regex chars; specifically ? and * are supported. | ||
* An asterisk (*) represents any combination of characters | ||
* A question mark (?) represents any single character | ||
* | ||
* @param pattern - the regex-like pattern to be compared against. | ||
* @param text - the string to compare against the pattern. | ||
* @returns boolean | ||
*/ | ||
wildcardMatch: function wildcardMatch(pattern, text) { | ||
if (_.isUndefined(pattern) || _.isUndefined(text)) | ||
return false; | ||
if (pattern.length === 1 && pattern.charAt(0) === '*') | ||
return true; | ||
var patternLength = pattern.length; | ||
var textLength = text.length; | ||
var indexOfGlob = pattern.indexOf('*'); | ||
pattern = pattern.toLowerCase(); | ||
text = text.toLowerCase(); | ||
// Infix globs are relatively rare, and the below search is expensive especially when | ||
// Balsa is used a lot. Check for infix globs and, in their absence, do the simple thing | ||
if (indexOfGlob === -1 || indexOfGlob === (patternLength - 1)) { | ||
var match = function simpleWildcardMatch() { | ||
var j = 0; | ||
for(var i = 0; i < patternLength; i++) { | ||
var patternChar = pattern.charAt(i); | ||
if(patternChar === '*') { | ||
// Presumption for this method is that globs only occur at end | ||
return true; | ||
} else if (patternChar === '?') { | ||
if(j === textLength) | ||
return false; // No character to match | ||
j++; | ||
} else { | ||
if (j >= textLength || patternChar != text.charAt(j)) | ||
return false; | ||
j++; | ||
} | ||
} | ||
// Ate up all the pattern and didn't end at a glob, so a match will have consumed all | ||
// the text | ||
return j === textLength; | ||
}; | ||
return match(); | ||
} | ||
/* | ||
* The matchArray[i] is used to record if there is a match between the first i chars in = | ||
* text and the first j chars in pattern. | ||
* So will return matchArray[textLength+1] in the end | ||
* Loop from the beginning of the pattern | ||
* case not '*': if text[i]==pattern[j] or pattern[j] is '?', and matchArray[i] is true, | ||
* set matchArray[i+1] to true, otherwise false | ||
* case '*': since '*' can match any globing, as long as there is a true in matchArray before i | ||
* all the matchArray[i+1], matchArray[i+2],...,matchArray[textLength] could be true | ||
*/ | ||
var matchArray = []; | ||
matchArray[0] = true; | ||
for (var j = 0; j < patternLength; j++) { | ||
var i; | ||
var patternChar = pattern.charAt(j); | ||
if (patternChar != '*') { | ||
for(i = textLength - 1; i >= 0; i--) | ||
matchArray[i+1] = !!matchArray[i] && (patternChar === '?' || (patternChar === text.charAt(i))); | ||
} else { | ||
i = 0; | ||
while (i <= textLength && !matchArray[i]) | ||
i++; | ||
for(i; i <= textLength; i++) | ||
matchArray[i] = true; | ||
} | ||
matchArray[0] = (matchArray[0] && patternChar === '*'); | ||
} | ||
return matchArray[textLength]; | ||
} | ||
@@ -89,0 +180,0 @@ }; |
{ | ||
"name": "aws-xray-sdk", | ||
"version": "1.0.0-beta", | ||
"version": "1.0.1-beta", | ||
"description": "AWS X-Ray SDK for Javascript", | ||
@@ -30,5 +30,5 @@ "author": "Amazon Web Services", | ||
"grunt-jsdoc": "^2.1.0", | ||
"microtime": "^2.1.2", | ||
"mocha": "^3.0.2", | ||
"nock": "^8.0.0", | ||
"node-mocks-http": "^1.5.3", | ||
"sinon": "^1.17.5", | ||
@@ -35,0 +35,0 @@ "sinon-chai": "^2.8.0" |
@@ -15,3 +15,3 @@ ## Setup development environment | ||
AWS SDK v2.7.2 or greater. | ||
AWS SDK v2.7.15 or greater. | ||
@@ -40,6 +40,6 @@ ## AWS X-Ray | ||
AWSXRay.capture - Takes a function that takes a single subsegment argument. This will create a new nested subsegment and expose it. The segment | ||
will close automatically when the function completes executing. This will not corretly time functions with asynchronous calls, instead use | ||
will close automatically when the function completes executing. This will not correctly time functions with asynchronous calls, instead use | ||
captureAsync. | ||
AWSXRay.captureAsync - Takes a function that takes a single subsegment argument. This will create a new nested subsegment and expose it. The segment | ||
AWSXRay.captureAsync - Takes a function that takes a single subsegment argument. This will create a new nested subsegment and expose it. The segment | ||
must be closed manually using subsegment.close() or subsegment.close(error) when the asynchronous function completes. | ||
@@ -55,6 +55,11 @@ | ||
XRAY_DEBUG_MODE Enables logging to console output (otherwise outputs to AWSXRay.log). | ||
XRAY_TRACING_NAME *For setting the default segment name. Internal use only. Ex: An app running on AWS Elastic Beanstalk will have this set automatically. | ||
XRAY_TRACING_DEFAULT_NAME *For setting the default segment name. This does not override XRAY_TRACING_NAME, but does override setting it via code. | ||
**Environment variables always override values set in code.** | ||
AWS_XRAY_DEBUG_MODE Enables logging to console output (otherwise outputs to AWSXRay.log). | ||
AWS_XRAY_TRACING_NAME For overriding the default segment name to be used with the middleware. See dynamic and fixed naming modes. | ||
XRAY_TRACING_NAME For overriding the default segment name to be used with the middleware. See dynamic and fixed naming modes. | ||
**This will be deprecated in favor of AWS_XRAY_TRACING_NAME for the GA release. | ||
AWS_XRAY_DAEMON_ADDRESS For setting the daemon address and port. Expects 'x.x.x.x', ':yyyy' or 'x.x.x.x:yyyy' IPv4 formats. | ||
POSTGRES_DATABASE_VERSION Sets additional data for the sql subsegment. | ||
@@ -66,5 +71,32 @@ POSTGRES_DRIVER_VERSION Sets additional data for the sql subsegment. | ||
Segment names default to XRAY_TRACING_NAME if set, otherwise, req.headers 'host' field will be used. If this field is missing or an IP (in the case of an application load balancer), | ||
then XRAY_TRACING_DEFAULT_NAME will be used if available. If neither of these environment variables is set, it will check the value set in code. If it is not set in code, an error will be thrown. | ||
### daemon configuration | ||
By default, the SDK expects the daemon to be at 127.0.0.1 (localhost) on port 2000. You can override the address, port, or both. | ||
This can be changed via the environment variables listed above, or through code. The same format is applicable for both. | ||
AWSXRay.setDaemonAddress('186.34.0.23:8082'); | ||
AWSXRay.setDaemonAddress(':8082'); | ||
AWSXRay.setDaemonAddress('186.34.0.23'); | ||
### middleware - dynamic and fixed naming modes | ||
The SDK requires a default segment name to be set when using the middleware. If it is not set, an error will be | ||
thrown. This value can be overridden via the AWS_XRAY_TRACING_NAME (or XRAY_TRACING_NAME) environment variable. | ||
app.use(AWSXRay.express.openSegment('defaultName')); | ||
The SDK defaults to a fixed naming mode. This means that each time a new segment is created for an incoming request, | ||
the name of that segment is set to the default name. | ||
In dynamic mode, the segment name can vary between the host header of the request or the default name. | ||
AWSXRay.middleware.enableDynamicNaming(<pattern>); | ||
If no pattern is provided, the host header is used as the segment name. If no host header is present, the default is used. | ||
This is equivalent to using the pattern '*'. | ||
If a pattern is provided, in the form of a string with wild cards (ex: '*.*.us-east-1.elasticbeanstalk.com') then the host header of the | ||
request will be checked against it. If the host header is present and matches this pattern, it is used as the segment name. Otherwise, | ||
the default name is used. | ||
### version capturing | ||
@@ -81,5 +113,4 @@ | ||
var AWSXRay = require('aws-xray-sdk'); | ||
var AWSXRay.setDefaultName('myDefaultSegment'); //required if XRAY_TRACING_DEFAULT_NAME is not set | ||
app.use(AWSXRay.express.openSegment()); //required at the start of your routes | ||
app.use(AWSXRay.express.openSegment('defaultName')); //required at the start of your routes | ||
@@ -138,3 +169,3 @@ app.get('/', function (req, res) { | ||
app.use(AWSXRay.express.openSegment()); | ||
app.use(AWSXRay.express.openSegment('defaultName')); | ||
@@ -151,3 +182,3 @@ app.get('/', function (req, res) { | ||
app.use(AWSXRay.express.openSegment()); | ||
app.use(AWSXRay.express.openSegment('defaultName')); | ||
@@ -222,2 +253,3 @@ app.get('/', function (req, res) { | ||
var AWSXRay = require('aws-xray-sdk'); | ||
var pg = AWSXRay.capturePostgres(require('pg')); | ||
@@ -233,3 +265,3 @@ | ||
client.query({name: 'moop', text: 'SELECT $1::text as name'}, ['brianc'], function (err, result) { | ||
//automatically captures query and error (if any) | ||
//automatically captures query information and error (if any) | ||
}); | ||
@@ -246,3 +278,3 @@ }); | ||
var query = client.query('SELECT * FROM mytable', function(err, result) { | ||
//automatically captures query and error (if any) | ||
//automatically captures query information and error (if any) | ||
}); | ||
@@ -254,2 +286,3 @@ }); | ||
var AWSXRay = require('aws-xray-sdk'); | ||
var mysql = AWSXRay.captureMySQL(require('mysql')); | ||
@@ -264,3 +297,3 @@ | ||
connection.query('SELECT * FROM cats', function(err, rows) { | ||
//automatically captures query and error (if any) | ||
//automatically captures query information and error (if any) | ||
}); | ||
@@ -270,7 +303,7 @@ | ||
var pool = mysql.createPool(config); | ||
var pool = mysql.createPool(config); | ||
var segment = req.segment; | ||
pool.query('SELECT * FROM cats', function(err, rows, fields) { | ||
//automatically captures query and error (if any) | ||
//automatically captures query information and error (if any) | ||
} | ||
@@ -290,3 +323,3 @@ | ||
app.use(AWSXRay.express.openSegment()); | ||
app.use(AWSXRay.express.openSegment('defaultName')); | ||
@@ -318,4 +351,4 @@ app.get('/', function (req, res) { | ||
//the whole response has been recieved, so we just print it out here | ||
//another chunk of data has been recieved, so append it to `str` | ||
//the whole response has been received, so we just print it out here | ||
//another chunk of data has been received, so append it to `str` | ||
response.on('data', function (chunk) { | ||
@@ -381,4 +414,4 @@ str += chunk; | ||
client.query({name: 'moop', text: 'SELECT $1::text as name'}, ['brianc'], function (err, result) { | ||
//automatically captures query and error (if any) | ||
client.query({name: 'moop', text: 'SELECT $1::text as name'}, ['mcmuls'], function (err, result) { | ||
//automatically captures query information and error (if any) | ||
}); | ||
@@ -395,3 +428,3 @@ }); | ||
var query = client.query('SELECT * FROM mytable', function(err, result) { | ||
//automatically captures query and error (if any) | ||
//automatically captures query information and error (if any) | ||
}, segment)); | ||
@@ -412,3 +445,3 @@ }; | ||
connection.query('SELECT * FROM cats', function(err, rows) { | ||
//automatically captures query and error (if any) | ||
//automatically captures query information and error (if any) | ||
}); | ||
@@ -418,6 +451,6 @@ | ||
var pool = mysql.createPool(config); | ||
var pool = mysql.createPool(config); | ||
pool.query('SELECT * FROM cats', function(err, rows, fields) { | ||
//automatically captures query and error (if any) | ||
//automatically captures query information and error (if any) | ||
}, segment); |
@@ -1,5 +0,2 @@ | ||
var assert = require('chai').assert; | ||
var chai = require('chai'); | ||
var cls = require('continuation-local-storage'); | ||
var httpMocks = require('node-mocks-http'); | ||
var sinon = require('sinon'); | ||
@@ -9,8 +6,4 @@ var sinonChai = require('sinon-chai'); | ||
var SamplingRules = require('../../lib/sampling/sampling_rules'); | ||
var Segment = require('../../lib/segments/segment'); | ||
var Subsegment = require('../../lib/segments/attributes/subsegment'); | ||
var SegmentUtils = require('../../lib/segments/segment_utils'); | ||
var EventEmitter = require('events'); | ||
var util = require('util'); | ||
chai.should(); | ||
@@ -21,26 +14,34 @@ chai.use(sinonChai); | ||
var AWSXRay; | ||
var segmentSandbox; | ||
var traceId = '1-57fbe041-2c7ad569f5d6ff149137be86'; | ||
describe('on load', function() { | ||
var sandbox, setSDKVersionStub, setServiceVersionStub; | ||
before(function() { | ||
segmentSandbox = sinon.sandbox.create(); | ||
segmentSandbox.stub(Segment.prototype, 'flush'); | ||
}); | ||
beforeEach(function() { | ||
sandbox = sinon.sandbox.create(); | ||
beforeEach(function() { | ||
AWSXRay = require('../../lib/index'); | ||
}); | ||
setSDKVersionStub = sandbox.stub(SegmentUtils, 'setSDKVersion'); | ||
setServiceVersionStub = sandbox.stub(SegmentUtils, 'setServiceVersion'); | ||
}); | ||
after(function() { | ||
segmentSandbox.restore(); | ||
afterEach(function() { | ||
sandbox.restore(); | ||
}); | ||
it('should set the SegmentUtils version and SDK version', function() { | ||
AWSXRay = require('../../lib/index'); | ||
setSDKVersionStub.should.have.been.calledWithExactly(sinon.match.string); | ||
setServiceVersionStub.should.have.been.calledWithExactly(sinon.match.string); | ||
}); | ||
}); | ||
describe('#config', function() { | ||
var sandbox, stubPlugins, stubCallback; | ||
var sandbox, pluginsStub, setOriginStub, setPluginDataStub; | ||
beforeEach(function() { | ||
MWutils = require('../../lib/middleware/mw_utils'); | ||
sandbox = sinon.sandbox.create(); | ||
stubPlugins = sandbox.stub(); | ||
pluginsStub = sandbox.stub(); | ||
setPluginDataStub = sandbox.stub(SegmentUtils, 'setPluginData'); | ||
setOriginStub = sandbox.stub(SegmentUtils, 'setOrigin'); | ||
}); | ||
@@ -52,8 +53,9 @@ | ||
it('should load the given plugins', function(done) { | ||
stubPlugins.yields({ client: 'data' }); | ||
AWSXRay.config([stubPlugins]); | ||
it('should load the given plugins and set the data on SegmentUtils', function(done) { | ||
var data = { client: 'data' }; | ||
pluginsStub.yields(data); | ||
AWSXRay.config([pluginsStub]); | ||
setTimeout(function() { | ||
assert.property(MWutils.pluginData, 'client'); | ||
setPluginDataStub.should.have.been.calledWithExactly(data); | ||
done(); | ||
@@ -63,8 +65,8 @@ }, 50); | ||
it('should set origin to beanstalk if beanstalk plugin was loaded', function(done) { | ||
stubPlugins.yields({ beanstalk: 'data' }); | ||
AWSXRay.config([stubPlugins]); | ||
it('should set SegmentUtils origin to beanstalk if beanstalk plugin was loaded', function(done) { | ||
pluginsStub.yields({ elastic_beanstalk: 'data' }); | ||
AWSXRay.config([pluginsStub]); | ||
setTimeout(function() { | ||
assert.propertyVal(MWutils, 'origin', 'AWS::Beanstalk::Environment'); | ||
setOriginStub.should.have.been.calledWithExactly('AWS::ElasticBeanstalk::Environment'); | ||
done(); | ||
@@ -76,3 +78,3 @@ }, 50); | ||
describe('#setSamplingRules', function() { | ||
var samplingRulesStub; | ||
var samplingRulesStub, sandbox; | ||
@@ -79,0 +81,0 @@ beforeEach(function() { |
var assert = require('chai').assert; | ||
var chai = require('chai'); | ||
var cls = require('continuation-local-storage'); | ||
var httpMocks = require('node-mocks-http'); | ||
var sinon = require('sinon'); | ||
@@ -10,3 +8,2 @@ var sinonChai = require('sinon-chai'); | ||
var Segment = require('../../lib/segments/segment'); | ||
var Subsegment = require('../../lib/segments/attributes/subsegment'); | ||
@@ -13,0 +10,0 @@ var capture = require('../../lib/capture').capture; |
var assert = require('chai').assert; | ||
var httpMocks = require('node-mocks-http'); | ||
var sinon = require('sinon'); | ||
var sinonChai = require('sinon-chai'); | ||
@@ -10,29 +8,20 @@ var CLSUtils = require('../../../lib/utils').CLSUtils; | ||
var Segment = require('../../../lib/segments/segment'); | ||
var Local = require('../../../lib/segments/attributes/local'); | ||
var Utils = require('../test_utils'); | ||
//headers are case-insensitive | ||
var XRAY_HEADER = 'x-amzn-trace-id'; | ||
describe('Express middleware', function() { | ||
var traceId = '1-f9194208-156a11d5704'; | ||
var defaultName = 'defaultName'; | ||
var hostName = 'expressMiddlewareTest'; | ||
var envVarName = 'xrayNameTest'; | ||
var envVarDefaultName = 'xrayNameDefaultTest'; | ||
var parentId = '2c7ad569f5d6ff149137be86'; | ||
var traceId = '1-f9194208-2c7ad569f5d6ff149137be86'; | ||
function getUncachedOpen(customName) { | ||
var path = '../../../lib/middleware/express_mw'; | ||
delete require.cache[require.resolve(path)]; | ||
return require(path).openSegment(customName); | ||
} | ||
describe('#openSegment', function() { | ||
var openSegment = Middleware.openSegment; | ||
it('should throw an error if no default name is set', function() { | ||
assert.throws(openSegment()); | ||
it('should throw an error if no default name is supplied', function() { | ||
assert.throws(openSegment); | ||
}); | ||
it('should return a middleware function', function() { | ||
MWUtils.defaultName = 'defaultName'; | ||
assert.isFunction(openSegment()); | ||
assert.isFunction(openSegment(defaultName)); | ||
}); | ||
@@ -43,4 +32,3 @@ }); | ||
var req, res, sandbox; | ||
MWUtils.defaultName = 'defaultName'; | ||
var open = Middleware.openSegment(); | ||
var open = Middleware.openSegment(defaultName); | ||
@@ -75,97 +63,16 @@ beforeEach(function() { | ||
describe('when creating a segment', function() { | ||
var populateStub, sandbox; | ||
describe('when handling a request', function() { | ||
var newAttributeSpy, newSegmentSpy, onEventStub, processHeadersStub, resolveNameStub, sandbox; | ||
beforeEach(function() { | ||
sandbox = sinon.sandbox.create(); | ||
populateStub = sandbox.stub(MWUtils, 'populate'); | ||
}); | ||
afterEach(function() { | ||
sandbox.restore(); | ||
}); | ||
it('should call MWUtils.populate to add global attributes', function() { | ||
open(req, res); | ||
var segment = req.segment; | ||
populateStub.should.have.been.calledOnce; | ||
}); | ||
}); | ||
describe('when creating a segment with a name', function() { | ||
var newSegmentSpy, sandbox; | ||
beforeEach(function() { | ||
sandbox = sinon.sandbox.create(); | ||
sandbox.stub(MWUtils, 'shouldSample').returns(true); | ||
newSegmentSpy = sandbox.spy(Segment.prototype, 'init'); | ||
newAttributeSpy = sandbox.spy(Segment.prototype, 'addAttribute'); | ||
req.headers = { host: hostName }; | ||
}); | ||
onEventStub = sandbox.stub(res, 'on'); | ||
afterEach(function() { | ||
sandbox.restore(); | ||
delete process.env.XRAY_TRACING_NAME; | ||
delete process.env.XRAY_TRACING_DEFAULT_NAME; | ||
}); | ||
processHeadersStub = sandbox.stub(MWUtils, 'processHeaders').returns({ Root: traceId, Parent: parentId, Sampled: '0' }); | ||
resolveNameStub = sandbox.stub(MWUtils, 'resolveName').returns(defaultName); | ||
after(function() { | ||
open = getUncachedOpen(); | ||
}); | ||
it('should create a new segment, use process.env.XRAY_TRACING_NAME for the name if set', function() { | ||
req.headers.host = 'host.here'; | ||
process.env.XRAY_TRACING_NAME = envVarName; | ||
var open = getUncachedOpen(); | ||
open(req, res); | ||
newSegmentSpy.should.have.been.calledWithExactly(envVarName, sinon.match.any, sinon.match.any); | ||
}); | ||
it('should create a new segment, if process.env.XRAY_TRACING_NAME is not set, use req.headers.host for the name', function() { | ||
var open = getUncachedOpen(); | ||
open(req, res); | ||
newSegmentSpy.should.have.been.calledWithExactly(hostName, sinon.match.any, sinon.match.any); | ||
}); | ||
it('should create a new segment, if host is not an IP and XRAY_TRACING_DEFAULT_NAME is set, use the host for the name', function() { | ||
req.headers.host = hostName; | ||
process.env.XRAY_TRACING_DEFAULT_NAME = envVarDefaultName; | ||
var open = getUncachedOpen(); | ||
open(req, res); | ||
newSegmentSpy.should.have.been.calledWithExactly(hostName, sinon.match.any, sinon.match.any); | ||
}); | ||
it('should create a new segment, if host is an IP and process.env.XRAY_TRACING_NAME is not set, use process.env.XRAY_TRACING_DEFAULT_NAME for the name', function() { | ||
req.headers.host = '::ffff:46.19.37.108'; | ||
process.env.XRAY_TRACING_DEFAULT_NAME = envVarDefaultName; | ||
var open = getUncachedOpen(); | ||
open(req, res); | ||
newSegmentSpy.should.have.been.calledWithExactly(envVarDefaultName, sinon.match.any, sinon.match.any); | ||
}); | ||
it('should create a new segment, if host is an IP and process.env.XRAY_TRACING_NAME and XRAY_TRACING_DEFAULT_NAME are not set, use the default name set via code', function() { | ||
req.headers.host = '::ffff:46.19.37.108'; | ||
var open = getUncachedOpen(); | ||
open(req, res); | ||
newSegmentSpy.should.have.been.calledWithExactly(MWUtils.defaultName, sinon.match.any, sinon.match.any); | ||
}); | ||
}); | ||
describe('when splitting the XRay Headers', function() { | ||
var sandbox, shouldSampleStub; | ||
beforeEach(function() { | ||
sandbox = sinon.sandbox.create(); | ||
req.headers = { host: hostName }; | ||
shouldSampleStub = sandbox.stub(MWUtils, 'shouldSample').returns(true); | ||
}); | ||
@@ -176,83 +83,45 @@ | ||
delete process.env.XRAY_TRACING_NAME; | ||
delete process.env.XRAY_TRACING_DEFAULT_NAME; | ||
}); | ||
it('should create a new segment, with segment that has a new trace ID', function() { | ||
it('should call MWUtils.processHeaders to split the headers, if any', function() { | ||
open(req, res); | ||
var segment = req.segment; | ||
assert.property(segment, 'trace_id'); | ||
processHeadersStub.should.have.been.calledOnce; | ||
processHeadersStub.should.have.been.calledWithExactly(req); | ||
}); | ||
it('should create a new segment, with a segment that has the trace ID from the request header', function() { | ||
req.headers[XRAY_HEADER] = 'Root=' + traceId; | ||
it('should call MWUtils.resolveName to find the name of the segment', function() { | ||
open(req, res); | ||
var segment = req.segment; | ||
assert.equal(segment.trace_id, traceId); | ||
resolveNameStub.should.have.been.calledOnce; | ||
resolveNameStub.should.have.been.calledWithExactly(req.headers.host); | ||
}); | ||
it('should create a new segment, with a segment that has the parent ID from the request header', function() { | ||
var parentId = '74051af127d2bcba'; | ||
req.headers[XRAY_HEADER] = 'Root=' + traceId + '; Parent=' + parentId; | ||
it('should create a new segment', function() { | ||
open(req, res); | ||
var segment = req.segment; | ||
assert.equal(segment.parent_id, parentId); | ||
newSegmentSpy.should.have.been.calledOnce; | ||
newSegmentSpy.should.have.been.calledWithExactly(defaultName, traceId, parentId); | ||
}); | ||
it('should mark segment as not traced if the sampled header is set to "0"', function() { | ||
req.headers[XRAY_HEADER] = 'Root=' + traceId + '; Sampled=0'; | ||
it('should add a new http property on the segment', function() { | ||
open(req, res); | ||
var segment = req.segment; | ||
assert.equal(segment.notTraced, true); | ||
newAttributeSpy.should.have.been.calledOnce; | ||
newAttributeSpy.should.have.been.calledWithExactly('http', sinon.match.instanceOf(Local)); | ||
}); | ||
it('should do a sampling rules check if no "Sampled" header is set', function() { | ||
req.headers[XRAY_HEADER] = 'Root=' + traceId; | ||
it('should add a finish event to the response', function() { | ||
open(req, res); | ||
shouldSampleStub.should.have.been.calledOnce; | ||
onEventStub.should.have.been.calledOnce; | ||
onEventStub.should.have.been.calledWithExactly('finish', sinon.match.typeOf('function')); | ||
}); | ||
it('should set the response header with sampling result if header is "?"', function() { | ||
req.headers[XRAY_HEADER] = 'Root=' + traceId + '; Sampled=?'; | ||
open(req, res); | ||
var expected = new RegExp('^Root=' + traceId + '; Sampled=1$'); | ||
assert.match(res.header[XRAY_HEADER], expected); | ||
}); | ||
it('should mark segment as not traced if the sampling rules check returns false', function() { | ||
shouldSampleStub.returns(false); | ||
req.headers[XRAY_HEADER] = 'Root=' + traceId; | ||
open(req, res); | ||
var segment = req.segment; | ||
assert.equal(segment.notTraced, true); | ||
}); | ||
}); | ||
describe('when the request completes', function() { | ||
var newSegmentSpy, sandbox; | ||
beforeEach(function() { | ||
sandbox = sinon.sandbox.create(); | ||
sandbox.stub(MWUtils, 'shouldSample').returns(true); | ||
newSegmentSpy = sandbox.spy(Segment.prototype, 'init'); | ||
req.headers = { host: hostName }; | ||
}); | ||
afterEach(function() { | ||
sandbox.restore(); | ||
}); | ||
after(function() { | ||
open = getUncachedOpen(); | ||
}); | ||
it('should add the error flag the segment on 4xx', function() { | ||
@@ -285,3 +154,2 @@ open(req, res); | ||
it('should add nothing on anything else', function() { | ||
@@ -288,0 +156,0 @@ open(req, res); |
@@ -13,5 +13,6 @@ var assert = require('chai').assert; | ||
var Segment = require('../../../lib/segments/segment'); | ||
var Subsegment = require('../../../lib/segments/attributes/subsegment'); | ||
var Utils = require('../../../lib/utils'); | ||
var logger = require('../../../lib/logger'); | ||
chai.should(); | ||
@@ -27,3 +28,3 @@ chai.use(sinonChai); | ||
var awssdk = { | ||
VERSION: '2.7.2', | ||
VERSION: '2.7.15', | ||
s3: { | ||
@@ -54,3 +55,3 @@ prototype: { | ||
awssdk.VERSION = '1.2.5'; | ||
assert.throws(function() { awsPatcher.captureAWS(awssdk) }, Error); | ||
assert.throws(function() { awsPatcher.captureAWS(awssdk); }, Error); | ||
}); | ||
@@ -83,3 +84,3 @@ }); | ||
describe('#captureAWSRequest', function() { | ||
var awsClient, awsRequest, getStub, MyEmitter, segment, sub; | ||
var awsClient, awsRequest, getStub, MyEmitter, sandbox, segment, sub; | ||
@@ -89,3 +90,3 @@ before(function() { | ||
EventEmitter.call(this); | ||
} | ||
}; | ||
@@ -136,2 +137,14 @@ awsClient = { customizeRequests: function (captureAWSRequest) { | ||
it('should log an info statement and exit if parent is not found on the CLS context or on the call params', function(done) { | ||
sandbox.stub(CLSUtils, 'isCLSMode').returns(false); | ||
var logStub = sandbox.stub(logger, 'info'); | ||
awsClient.call(awsRequest); | ||
setTimeout(function() { | ||
logStub.should.have.been.calledOnce; | ||
done(); | ||
}, 50); | ||
}); | ||
it('should call getSegment if in CLS mode', function(done) { | ||
@@ -171,3 +184,3 @@ sandbox.stub(CLSUtils, 'isCLSMode').returns(true); | ||
httpResponse: { statusCode: 200 }, | ||
} | ||
}; | ||
@@ -195,3 +208,3 @@ awsClient.call(awsRequest); | ||
httpResponse: { statusCode: 429 }, | ||
} | ||
}; | ||
@@ -208,3 +221,3 @@ awsClient.call(awsRequest); | ||
it('should capture an error on the response', function(done) { | ||
it('should capture an error on the response and mark exception as remote', function(done) { | ||
var closeStub = sandbox.stub(sub, 'close').returns(); | ||
@@ -229,3 +242,3 @@ var getCauseStub = sandbox.stub(Utils, 'getCauseTypeFromHttpStatus').returns(); | ||
getCauseStub.should.have.been.calledWithExactly(awsRequest.response.httpResponse.statusCode); | ||
closeStub.should.have.been.calledWithExactly(sinon.match({ message: error.message, name: error.code}), sinon.match.any); | ||
closeStub.should.have.been.calledWithExactly(sinon.match({ message: error.message, name: error.code}), sinon.match.any, true); | ||
done(); | ||
@@ -232,0 +245,0 @@ }, 50); |
var assert = require('chai').assert; | ||
var sinon = require('sinon'); | ||
var fs = require('fs'); | ||
var path = require('path'); | ||
@@ -6,0 +5,0 @@ var CallCapturer = require('../../../lib/patchers/call_capturer'); |
var assert = require('chai').assert; | ||
var EventEmitter = require('events'); | ||
var chai = require('chai'); | ||
var sinon = require('sinon'); | ||
var sinonChai = require('sinon-chai'); | ||
var util = require('util'); | ||
@@ -11,3 +9,2 @@ var captureHTTPs = require('../../../lib/patchers/http_p').captureHTTPs; | ||
var Segment = require('../../../lib/segments/segment'); | ||
var Subsegment = require('../../../lib/segments/attributes/subsegment'); | ||
var TestEmitter = require('../test_utils').TestEmitter; | ||
@@ -23,3 +20,3 @@ | ||
connection: { | ||
remoteAddress: 'localhost' | ||
remoteAddress: 'myhost' | ||
} | ||
@@ -40,2 +37,15 @@ }; | ||
var buildFakeResponse = function() { | ||
var response = {}; | ||
response.emitter = new TestEmitter(); | ||
response.on = function(event, fcn) { | ||
this.emitter.on(event, fcn); | ||
return this; | ||
}; | ||
return response; | ||
}; | ||
describe('HTTP/S patcher', function() { | ||
@@ -52,11 +62,8 @@ describe('#captureHTTPs', function() { | ||
describe('#captureHTTPsRequest', function() { | ||
var httpClient, httpOptions, sandbox, segment, subsegment; | ||
var addRemoteDataStub, closeStub, httpClient, httpOptions, newSubsegmentStub, sandbox, segment, subsegment; | ||
var traceId = '1-57fbe041-2c7ad569f5d6ff149137be86'; | ||
before(function() { | ||
httpClient = { request: function() {} }; | ||
captureHTTPs(httpClient); | ||
httpOptions = { | ||
host: 'localhost', | ||
host: 'myhost', | ||
path: '/' | ||
@@ -71,6 +78,8 @@ }; | ||
sandbox.stub(segment, 'addNewSubsegment').returns(subsegment); | ||
newSubsegmentStub = sandbox.stub(segment, 'addNewSubsegment').returns(subsegment); | ||
sandbox.stub(CLSUtils, 'isCLSMode').returns(true); | ||
sandbox.stub(CLSUtils, 'getSegment').returns(segment); | ||
addRemoteDataStub = sandbox.stub(subsegment, 'addRemoteData').returns(); | ||
closeStub = sandbox.stub(subsegment, 'close').returns(); | ||
}); | ||
@@ -82,13 +91,19 @@ | ||
describe('on success', function () { | ||
var fakeRequest, requestStub, sandbox, setHeaderStub; | ||
describe('on invocation', function () { | ||
var fakeRequest, fakeResponse, requestSpy, sandbox, setHeaderStub; | ||
beforeEach(function() { | ||
httpClient = { request: function() {} }; | ||
sandbox = sinon.sandbox.create(); | ||
fakeRequest = buildFakeRequest(); | ||
fakeResponse = buildFakeResponse(); | ||
httpClient = { request: function(options, callback) { | ||
callback(fakeResponse); | ||
return fakeRequest; | ||
}}; | ||
setHeaderStub = sandbox.stub(fakeRequest, 'setHeader'); | ||
requestStub = sandbox.stub(httpClient, 'request').returns(fakeRequest); | ||
requestSpy = sandbox.spy(httpClient, 'request'); | ||
captureHTTPs(httpClient); | ||
@@ -101,29 +116,58 @@ }); | ||
it('should inject the tracing headers', function(done) { | ||
it('should inject the tracing headers', function() { | ||
httpClient.request(httpOptions); | ||
setTimeout(function() { | ||
var expected = new RegExp('^Root=' + traceId + '; Parent=([a-f0-9]{16}); Sampled=1$'); | ||
setHeaderStub.should.have.been.calledWith('X-Amzn-Trace-Id', sinon.match(expected)); | ||
done(); | ||
}, 50); | ||
var expected = new RegExp('^Root=' + traceId + '; Parent=([a-f0-9]{16}); Sampled=1$'); | ||
setHeaderStub.should.have.been.calledWith('X-Amzn-Trace-Id', sinon.match(expected)); | ||
}); | ||
it('should create a new subsegment with name as hostname', function() { | ||
var options = {hostname: 'hostname', path: '/'}; | ||
httpClient.request(options); | ||
newSubsegmentStub.should.have.been.calledWith(options.hostname); | ||
}); | ||
it('should create a new subsegment with name as host when hostname is missing', function() { | ||
httpClient.request(httpOptions); | ||
newSubsegmentStub.should.have.been.calledWith(httpOptions.host); | ||
}); | ||
it('should create a new subsegment with name as "Unknown host" when host and hostname is missing', function() { | ||
httpClient.request({path: '/'}); | ||
newSubsegmentStub.should.have.been.calledWith('Unknown host'); | ||
}); | ||
it('should call the base method', function() { | ||
httpClient.request({'Segment': segment}); | ||
assert(requestStub.called); | ||
assert(requestSpy.called); | ||
}); | ||
it('should attach an event handler to the "end" event', function() { | ||
httpClient.request(httpOptions); | ||
assert.isFunction(fakeResponse.emitter._events.end); | ||
}); | ||
it('should return the request object', function() { | ||
var request = httpClient.request(httpOptions); | ||
assert.equal(request, fakeRequest); | ||
}); | ||
}); | ||
describe('on error', function () { | ||
var httpClient, fakeRequest, sandbox; | ||
describe('on the "end" event', function () { | ||
var fakeRequest, fakeResponse, sandbox; | ||
beforeEach(function() { | ||
httpClient = { request: function() {} }; | ||
captureHTTPs(httpClient); | ||
sandbox = sinon.sandbox.create(); | ||
fakeRequest = buildFakeRequest(); | ||
fakeResponse = buildFakeResponse(); | ||
httpClient = { request: function(options, callback) { | ||
fakeResponse.req = fakeRequest; | ||
callback(fakeResponse); | ||
return fakeRequest; | ||
}}; | ||
sandbox.stub(fakeRequest, 'setHeader'); | ||
captureHTTPs(httpClient); | ||
}); | ||
@@ -135,45 +179,58 @@ | ||
it('should capture the error', function(done) { | ||
sandbox.stub(httpClient, '__request').returns(fakeRequest); | ||
it('when the "end" event fires, it should capture the remote data and close the subsegment', function(done) { | ||
httpClient.request(httpOptions); | ||
fakeResponse.emitter.emit('end'); | ||
var addRemoteDataStub = sandbox.stub(subsegment, 'addRemoteData').returns(); | ||
var closeStub = sandbox.stub(subsegment, 'close').returns(); | ||
var req = httpClient.request(httpOptions); | ||
var error = {}; | ||
req._events = { error: { length: 2 }}; | ||
fakeRequest.emitter.emit('error', error); | ||
setTimeout(function() { | ||
addRemoteDataStub.should.have.been.calledWith(req); | ||
closeStub.should.have.been.calledWith(error); | ||
addRemoteDataStub.should.have.been.calledWithExactly(fakeRequest, fakeResponse, sinon.match.any); | ||
closeStub.should.have.been.calledWithExactly(); | ||
done(); | ||
}, 50); | ||
}); | ||
}); | ||
it('should flag status "429" as a throttling error', function(done) { | ||
sandbox.stub(httpClient, '__request').returns(fakeRequest); | ||
describe('on error', function () { | ||
var error, httpClient, fakeRequest, req, sandbox; | ||
sandbox.stub(subsegment, 'addRemoteData').returns(); | ||
sandbox.stub(subsegment, 'close').returns(); | ||
beforeEach(function() { | ||
sandbox = sinon.sandbox.create(); | ||
subsegment.http = { response: { status: 429 }}; | ||
httpClient = { request: function() {} }; | ||
captureHTTPs(httpClient); | ||
var throttleStub = sandbox.stub(subsegment, 'addThrottle').returns(); | ||
fakeRequest = buildFakeRequest(); | ||
var req = httpClient.request(httpOptions); | ||
var error = {}; | ||
sandbox.stub(fakeRequest, 'setHeader'); | ||
sandbox.stub(httpClient, '__request').returns(fakeRequest); | ||
error = {}; | ||
req = httpClient.request(httpOptions); | ||
req._events = { error: { length: 2 }}; | ||
}); | ||
afterEach(function() { | ||
sandbox.restore(); | ||
}); | ||
it('should capture the request error', function(done) { | ||
fakeRequest.emitter.emit('error', error); | ||
setTimeout(function() { | ||
throttleStub.should.have.been.calledOnce; | ||
addRemoteDataStub.should.have.been.calledWith(req); | ||
closeStub.should.have.been.calledWithExactly(error); | ||
done(); | ||
}, 50); | ||
}); | ||
it('should capture the response error', function(done) { | ||
subsegment.http = { response: { status: 500 }}; | ||
fakeRequest.emitter.emit('error', error); | ||
setTimeout(function() { | ||
closeStub.should.have.been.calledWithExactly(error, sinon.match.any, true); | ||
done(); | ||
}, 50); | ||
}); | ||
}); | ||
}); | ||
}); |
var assert = require('chai').assert; | ||
var httpMocks = require('node-mocks-http'); | ||
var sinon = require('sinon'); | ||
var sinonChai = require('sinon-chai'); | ||
@@ -13,3 +11,2 @@ var captureMySQL = require('../../../lib/patchers/mysql_p').captureMySQL; | ||
describe('captureMySQL', function() { | ||
var traceId = '1-f9194208-156a11d5704'; | ||
var err = new Error('An error has been encountered.'); | ||
@@ -37,3 +34,3 @@ | ||
describe('for basic connections', function() { | ||
var conn, connectionObj, mysql, query, queryObj, sandbox, segment, stubAddNew, stubBaseQuery, stubCLSMode, subsegment; | ||
var conn, connectionObj, mysql, query, queryObj, sandbox, segment, stubAddNew, stubBaseQuery, subsegment; | ||
@@ -49,6 +46,5 @@ before(function() { | ||
query: function() {} | ||
} | ||
}; | ||
var mysql = { createConnection: function() { return conn; } }; | ||
mysql = { createConnection: function() { return conn; } }; | ||
mysql = captureMySQL(mysql); | ||
@@ -71,3 +67,3 @@ connectionObj = mysql.createConnection(); | ||
stubAddNew = sandbox.stub(segment, 'addNewSubsegment').returns(subsegment); | ||
stubCLSMode = sandbox.stub(CLSUtils, 'isCLSMode').returns(true); | ||
sandbox.stub(CLSUtils, 'isCLSMode').returns(true); | ||
query = connectionObj.query; | ||
@@ -101,3 +97,3 @@ }); | ||
var stubClose = sandbox.stub(subsegment, 'close'); | ||
var session = { run: function(fcn) { fcn() }}; | ||
var session = { run: function(fcn) { fcn(); }}; | ||
var stubRun = sandbox.stub(session, 'run'); | ||
@@ -108,3 +104,3 @@ | ||
stubBaseQuery.should.have.been.calledWith(sinon.match.string, undefined, sinon.match.func); | ||
stubBaseQuery.should.have.been.calledWith(sinon.match.string, null, sinon.match.func); | ||
assert.equal(stubBaseQuery.args[0][2].name, 'clsContext'); | ||
@@ -114,3 +110,3 @@ stubBaseQuery.args[0][2].call(queryObj); | ||
setTimeout(function() { | ||
stubClose.should.always.have.been.calledWithExactly(undefined); | ||
stubClose.should.always.have.been.calledWith(); | ||
stubRun.should.have.been.calledOnce; | ||
@@ -117,0 +113,0 @@ done(); |
var assert = require('chai').assert; | ||
var httpMocks = require('node-mocks-http'); | ||
var sinon = require('sinon'); | ||
var sinonChai = require('sinon-chai'); | ||
@@ -13,3 +11,2 @@ var capturePostgres = require('../../../lib/patchers/postgres_p').capturePostgres; | ||
describe('capturePostgres', function() { | ||
var traceId = '1-f9194208-156a11d5704'; | ||
var err = new Error('An error has been encountered.'); | ||
@@ -26,3 +23,3 @@ | ||
describe('#captureQuery', function() { | ||
var postgres, query, queryObj, sandbox, segment, stubAddNew, stubCLSMode, subsegment; | ||
var postgres, query, queryObj, sandbox, segment, stubAddNew, subsegment; | ||
@@ -45,3 +42,3 @@ before(function() { | ||
postgres = postgres.Client.prototype; | ||
}) | ||
}); | ||
@@ -60,3 +57,3 @@ beforeEach(function() { | ||
stubAddNew = sandbox.stub(segment, 'addNewSubsegment').returns(subsegment); | ||
stubCLSMode = sandbox.stub(CLSUtils, 'isCLSMode').returns(true); | ||
sandbox.stub(CLSUtils, 'isCLSMode').returns(true); | ||
}); | ||
@@ -88,3 +85,3 @@ | ||
var stubClose = sandbox.stub(subsegment, 'close'); | ||
var session = { run: function(fcn) { fcn() }}; | ||
var session = { run: function(fcn) { fcn(); }}; | ||
var stubRun = sandbox.stub(session, 'run'); | ||
@@ -101,3 +98,3 @@ | ||
setTimeout(function() { | ||
stubClose.should.always.have.been.calledWithExactly(undefined); | ||
stubClose.should.always.have.been.calledWith(undefined); | ||
stubRun.should.have.been.calledOnce; | ||
@@ -104,0 +101,0 @@ done(); |
@@ -5,3 +5,2 @@ var _ = require('underscore'); | ||
var sinon = require('sinon'); | ||
var sinonChai = require('sinon-chai'); | ||
@@ -20,9 +19,9 @@ var Sampler = require('../../../lib/sampling/sampler'); | ||
it('should throw an exception if fixed target is a float or a negative number', function() { | ||
expect(function() { new Sampler(123.45, 0.5) }).to.throw(Error, 'Rule param "fixed_target" must be a non-negative integer.'); | ||
expect(function() { new Sampler(-123, 0.5) }).to.throw(Error, 'Rule param "fixed_target" must be a non-negative integer.'); | ||
expect(function() { new Sampler(123.45, 0.5); }).to.throw(Error, 'Rule param "fixed_target" must be a non-negative integer.'); | ||
expect(function() { new Sampler(-123, 0.5); }).to.throw(Error, 'Rule param "fixed_target" must be a non-negative integer.'); | ||
}); | ||
it('should throw an exception if rate is not a number between 0 and 1', function() { | ||
expect(function() { new Sampler(5, 123) }).to.throw(Error, 'Rule param "rate" must be a number between 0 and 1 inclusive.'); | ||
expect(function() { new Sampler(5, -0.5) }).to.throw(Error, 'Rule param "rate" must be a number between 0 and 1 inclusive.'); | ||
expect(function() { new Sampler(5, 123); }).to.throw(Error, 'Rule param "rate" must be a number between 0 and 1 inclusive.'); | ||
expect(function() { new Sampler(5, -0.5); }).to.throw(Error, 'Rule param "rate" must be a number between 0 and 1 inclusive.'); | ||
}); | ||
@@ -29,0 +28,0 @@ }); |
@@ -7,2 +7,3 @@ var assert = require('chai').assert; | ||
var Sampler = require('../../../lib/sampling/sampler'); | ||
var Utils = require('../../../lib/utils'); | ||
@@ -49,17 +50,17 @@ describe('SamplingRules', function() { | ||
var yamlDocRegex = { rules: { | ||
1: { | ||
service_name: '*', | ||
http_method: '*', | ||
url_path: '*', | ||
var yamlDoc2 = { rules: { | ||
99: { | ||
http_method: 'GET', | ||
service_name: '*.foo.com', | ||
url_path: '/signin/*', | ||
fixed_target: 10, | ||
rate: 0.05 | ||
}, | ||
2: { | ||
20: { | ||
http_method: 'POST', | ||
service_name: '*.moop.com', | ||
http_method: 'GET', | ||
url_path: '/home/*', | ||
url_path: '/login/*', | ||
fixed_target: 10, | ||
rate: 0.05 | ||
} | ||
}, | ||
}}; | ||
@@ -80,12 +81,21 @@ | ||
it('should parse the matchers into regex', function() { | ||
it('should parse the matchers rules', function() { | ||
sandbox.stub(fs, 'readFileSync').returns(); | ||
sandbox.stub(JSON, 'parse').returns(yamlDoc); | ||
sandbox.stub(JSON, 'parse').returns(yamlDoc2); | ||
var samplingRules = new SamplingRules('/path/here'); | ||
var rule = samplingRules.rules[1].rule; | ||
var rule1 = samplingRules.rules[0].rule; | ||
var rule2 = samplingRules.rules[1].rule; | ||
assert('hello.moop.com'.match(rule.service_name)); | ||
assert('GET'.match(rule.http_method)); | ||
assert('/home/moop/hello'.match(rule.url_path)); | ||
assert.equal(samplingRules.rules[0].id, '20'); | ||
assert.equal(rule1.service_name, yamlDoc2.rules['20'].service_name); | ||
assert.equal(rule1.http_method, yamlDoc2.rules['20'].http_method); | ||
assert.equal(rule1.url_path, yamlDoc2.rules['20'].url_path); | ||
assert.instanceOf(rule1.sampler, Sampler); | ||
assert.equal(samplingRules.rules[1].id, '99'); | ||
assert.equal(rule2.service_name, yamlDoc2.rules['99'].service_name); | ||
assert.equal(rule2.http_method, yamlDoc2.rules['99'].http_method); | ||
assert.equal(rule2.url_path, yamlDoc2.rules['99'].url_path); | ||
assert.instanceOf(rule2.sampler, Sampler); | ||
}); | ||
@@ -110,3 +120,27 @@ }); | ||
}); | ||
it('should match the customer rule and call Utils.wildcardMatch on each attribute', function() { | ||
sandbox.stub(fs, 'readFileSync'); | ||
sandbox.stub(JSON, 'parse').returns({ rules: {}}); | ||
var matchStub = sandbox.stub(Utils, 'wildcardMatch').returns(true); | ||
var samplingRules = new SamplingRules(); | ||
samplingRules.rules = [ { | ||
id: '20', | ||
rule: { | ||
http_method: 'POST', | ||
service_name: '*.moop.com', | ||
url_path: '/login/*', | ||
sampler: new Sampler(10, 0.05) | ||
} | ||
}]; | ||
assert.isTrue(samplingRules.shouldSample('hello.moop.com', 'POST', '/login/moop/hello')); | ||
matchStub.should.have.been.calledThrice; | ||
matchStub.should.have.been.calledWithExactly('/login/*', '/login/moop/hello'); | ||
matchStub.should.have.been.calledWithExactly('POST', 'POST'); | ||
matchStub.should.have.been.calledWithExactly('*.moop.com', 'hello.moop.com'); | ||
}); | ||
}); | ||
}); |
@@ -0,1 +1,2 @@ | ||
var assert = require('chai').assert; | ||
var expect = require('chai').expect; | ||
@@ -9,10 +10,23 @@ var chai = require('chai'); | ||
var dgram = require('dgram'); | ||
var SegmentEmitter = require('../../lib/segment_emitter'); | ||
var Segment = require('../../lib/segments/segment'); | ||
describe('SegmentEmitter', function() { | ||
var getStub, sandbox; | ||
var client, sandbox, SegmentEmitter; | ||
var DEFAULT_DAEMON_ADDRESS = '127.0.0.1'; | ||
var DEFAULT_DAEMON_PORT = 2000; | ||
var ADDRESS_PROPERTY_NAME = 'daemonAddress'; | ||
var PORT_PROPERTY_NAME = 'daemonPort'; | ||
function getUncachedEmitter() { | ||
var path = '../../lib/segment_emitter'; | ||
delete require.cache[require.resolve(path)]; | ||
return require(path); | ||
} | ||
beforeEach(function() { | ||
sandbox = sinon.sandbox.create(); | ||
delete process.env.AWS_XRAY_DAEMON_ADDRESS; | ||
SegmentEmitter = getUncachedEmitter(); | ||
}); | ||
@@ -24,16 +38,73 @@ | ||
describe('init', function() { | ||
it('should load the default address and port', function() { | ||
assert.equal(SegmentEmitter[ADDRESS_PROPERTY_NAME], DEFAULT_DAEMON_ADDRESS); | ||
assert.equal(SegmentEmitter[PORT_PROPERTY_NAME], DEFAULT_DAEMON_PORT); | ||
}); | ||
it('should load the environment variables address and port if set', function() { | ||
process.env.AWS_XRAY_DAEMON_ADDRESS = '192.168.0.23:8081'; | ||
SegmentEmitter = getUncachedEmitter(); | ||
assert.equal(SegmentEmitter[ADDRESS_PROPERTY_NAME], '192.168.0.23'); | ||
assert.equal(SegmentEmitter[PORT_PROPERTY_NAME], 8081); | ||
}); | ||
}); | ||
describe('#send', function() { | ||
it('should send the segment using the dgram client', function() { | ||
var client = dgram.createSocket('udp4'); | ||
client = dgram.createSocket('udp4'); | ||
sandbox.stub(client, 'send'); | ||
sandbox.stub(dgram, 'createSocket').returns(client); | ||
SegmentEmitter = getUncachedEmitter(); | ||
var segmentMock = sandbox.mock(Segment); | ||
SegmentEmitter.send(segmentMock); | ||
expect(client.send).to.have.been.calledOnce; | ||
expect(client.send).to.have.been.calledWithExactly(sinon.match.any, 0, sinon.match.number, | ||
SegmentEmitter[PORT_PROPERTY_NAME], SegmentEmitter[ADDRESS_PROPERTY_NAME], sinon.match.func); | ||
}); | ||
}); | ||
describe('#setDaemonAddress', function() { | ||
it('should sent the address', function() { | ||
SegmentEmitter.setDaemonAddress('192.168.0.23'); | ||
assert.equal(SegmentEmitter[ADDRESS_PROPERTY_NAME], '192.168.0.23'); | ||
assert.equal(SegmentEmitter[PORT_PROPERTY_NAME], 2000); | ||
}); | ||
it('should sent the port', function() { | ||
SegmentEmitter.setDaemonAddress(':8081'); | ||
assert.equal(SegmentEmitter[ADDRESS_PROPERTY_NAME], '127.0.0.1'); | ||
assert.equal(SegmentEmitter[PORT_PROPERTY_NAME], 8081); | ||
}); | ||
it('should sent the address and port', function() { | ||
SegmentEmitter.setDaemonAddress('192.168.0.23:8081'); | ||
assert.equal(SegmentEmitter[ADDRESS_PROPERTY_NAME], '192.168.0.23'); | ||
assert.equal(SegmentEmitter[PORT_PROPERTY_NAME], 8081); | ||
}); | ||
it('should support IPv6 notation', function() { | ||
SegmentEmitter.setDaemonAddress('192.168.0.23'); | ||
assert.equal(SegmentEmitter[ADDRESS_PROPERTY_NAME], '192.168.0.23'); | ||
assert.equal(SegmentEmitter[PORT_PROPERTY_NAME], 2000); | ||
}); | ||
it('should not override the environment variables', function() { | ||
process.env.AWS_XRAY_DAEMON_ADDRESS = '184.88.8.173:8081'; | ||
SegmentEmitter = getUncachedEmitter(); | ||
SegmentEmitter.setDaemonAddress('204.45.25.168:4553'); | ||
assert.equal(SegmentEmitter[ADDRESS_PROPERTY_NAME], '184.88.8.173'); | ||
assert.equal(SegmentEmitter[PORT_PROPERTY_NAME], 8081); | ||
}); | ||
}); | ||
}); |
@@ -16,5 +16,7 @@ var assert = require('chai').assert; | ||
retryCount: 3 | ||
} | ||
}; | ||
describe('#init', function() { | ||
var sandbox; | ||
beforeEach(function() { | ||
@@ -21,0 +23,0 @@ sandbox = sinon.sandbox.create(); |
var assert = require('chai').assert; | ||
var chai = require('chai'); | ||
var sinon = require('sinon'); | ||
var sinonChai = require('sinon-chai'); | ||
var CapturedException = require('../../../../lib/segments/attributes/captured_exception'); | ||
var Subsegment = require('../../../../lib/segments/attributes/subsegment'); | ||
chai.should(); | ||
chai.use(sinonChai); | ||
describe('Subsegment', function() { | ||
@@ -20,3 +27,3 @@ describe('#init', function() { | ||
subsegment = new Subsegment('test'); | ||
key = 'key' | ||
key = 'key'; | ||
value = [1, 2, 3]; | ||
@@ -45,20 +52,23 @@ }); | ||
it('should throw an error if trying to add a non-subsegment', function() { | ||
assert.throws( function() { subsegment.addSubsegment({ key: 'x' }) }, Error); | ||
assert.throws( function() { subsegment.addSubsegment({ key: 'x' }); }, Error); | ||
}); | ||
}); | ||
describe('#addError', function() { | ||
var e; | ||
var err, exceptionStub, sandbox, subsegment; | ||
beforeEach(function() { | ||
sandbox = sinon.sandbox.create(); | ||
exceptionStub = sandbox.stub(CapturedException.prototype, 'init'); | ||
subsegment = new Subsegment('test'); | ||
e = new Error('Test error'); | ||
e.stack = ('Test error\n at /path/to/file.js:200:15\n ' + | ||
'at myTestFunction /path/to/another/file.js:20:30\n ' + | ||
'at myTest [as _myTests] (test.js:10:5)'); | ||
err = new Error('Test error'); | ||
}); | ||
it('should set fault to true', function() { | ||
subsegment.addError(e); | ||
afterEach(function() { | ||
sandbox.restore(); | ||
}); | ||
it('should set fault to true by default', function() { | ||
subsegment.addError(err); | ||
assert.equal(subsegment.fault, true); | ||
@@ -68,3 +78,3 @@ }); | ||
it('should add the cause property with working directory and paths data', function() { | ||
subsegment.addError(e); | ||
subsegment.addError(err); | ||
assert.property(subsegment.cause, 'working_directory'); | ||
@@ -74,27 +84,7 @@ assert.property(subsegment.cause, 'paths'); | ||
it('should add the cause property with exception data', function() { | ||
var exceptions = [{ | ||
message: 'Test error', | ||
type: 'Error', | ||
stack: [{ | ||
path: '/path/to/file.js', | ||
line: 200, | ||
label: 'anonymous' | ||
}, | ||
{ | ||
path: '/path/to/another/file.js', | ||
line: 20, | ||
label: 'myTestFunction' | ||
}, | ||
{ | ||
path: 'test.js', | ||
line: 10, | ||
label: 'myTest [as _myTests]' | ||
}] | ||
}]; | ||
subsegment.addError(e); | ||
assert.deepEqual(subsegment.cause.exceptions, exceptions); | ||
it('should add a new captured exception', function() { | ||
subsegment.addError(err, null, true); | ||
exceptionStub.should.have.been.calledWithExactly(err, true); | ||
}); | ||
}); | ||
}); |
@@ -14,5 +14,5 @@ var expect = require('chai').expect; | ||
var data = { | ||
deployment_id: "deployment_id", | ||
version_label: "version_label", | ||
environment_name: "my_env" | ||
deployment_id: 'deployment_id', | ||
version_label: 'version_label', | ||
environment_name: 'my_env' | ||
}; | ||
@@ -19,0 +19,0 @@ |
@@ -12,5 +12,2 @@ var expect = require('chai').expect; | ||
describe('EC2Plugin', function() { | ||
var METADATA_HOST = 'http://169.254.169.254'; | ||
var METADATA_PATH = '/latest/dynamic/instance-identity/document'; | ||
var data = { | ||
@@ -17,0 +14,0 @@ availabilityZone: 'us-east-1d', |
var expect = require('chai').expect; | ||
var chai = require('chai'); | ||
var nock = require('nock'); | ||
var sinon = require('sinon'); | ||
@@ -24,3 +23,3 @@ var sinonChai = require('sinon-chai'); | ||
ECSPlugin(function (data) { | ||
expect(data.ecs).not.to.be.empty; | ||
expect(data.ecs.container).not.to.be.empty; | ||
done(); | ||
@@ -27,0 +26,0 @@ }); |
@@ -14,3 +14,3 @@ var expect = require('chai').expect; | ||
path: '/index' | ||
} | ||
}; | ||
@@ -17,0 +17,0 @@ var data = { data: 1234 }; |
var EventEmitter = require('events'); | ||
var util = require('util'); | ||
var TestEmitter = function() { | ||
var TestUtils = {}; | ||
TestUtils.TestEmitter = function TestEmitter() { | ||
EventEmitter.call(this); | ||
} | ||
}; | ||
util.inherits(TestEmitter, EventEmitter); | ||
util.inherits(TestUtils.TestEmitter, EventEmitter); | ||
var onEvent = function(event, fcn) { | ||
TestUtils.onEvent = function onEvent(event, fcn) { | ||
this.emitter.on(event, fcn.bind(this)); | ||
@@ -15,3 +17,11 @@ return this; | ||
module.exports.TestEmitter = TestEmitter; | ||
module.exports.onEvent = onEvent; | ||
TestUtils.randomString = function randomString(length) { | ||
var text = ''; | ||
var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.#-_$%^&@!'; | ||
for(var i = 0; i < length; i++) | ||
text += possible.charAt(Math.floor(Math.random() * possible.length)); | ||
return text; | ||
}; | ||
module.exports = TestUtils; |
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 3 instances in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances in 1 package
193913
62
4586
438
30