UNPKG

deep-framework

Version:
305 lines (238 loc) 8.59 kB
var crypto = require('crypto'); var _ = require('underscore'); var CapturedException = require('./attributes/captured_exception'); var SegmentEmitter = require('../segment_emitter'); var SegmentUtils = require('./segment_utils'); var Subsegment = require('./attributes/subsegment'); var logger = require('../logger'); /** * Represents a segment. * @constructor * @param {string} name - The name of the subsegment. * @param {string} [rootId] - The trace ID of the spawning parent, included in the 'X-Amzn-Trace-Id' header of the incoming request. If one is not supplied, it will be generated. * @param {string} [parentId] - The segment ID of the spawning parent, included in the 'X-Amzn-Trace-Id' header of the incoming request. */ function Segment(name, rootId, parentId) { this.init(name, rootId, parentId); } 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) + '-' + crypto.randomBytes(12).toString('hex'); var id = crypto.randomBytes(8).toString('hex'); var startTime = SegmentUtils.getCurrentTime(); this.trace_id = traceId; this.id = id; this.start_time = startTime; this.name = name || ''; this.in_progress = true; this.counter = 0; if (parentId) this.parent_id = parentId; if (SegmentUtils.version) this.addServiceVersion(SegmentUtils.version); if (SegmentUtils.pluginData) this.addPluginData(SegmentUtils.pluginData); if (SegmentUtils.origin) this.origin = SegmentUtils.origin; if (SegmentUtils.sdkVersion) this.addSDKVersion(SegmentUtils.sdkVersion); }; /** * Adds a property with associated data into the segment. * @param {string} name - The name of the property to add. * @param {Object} data - The data of the property to add. */ Segment.prototype.addAttribute = function addAttribute(name, data) { this[name] = data; }; /** * Adds a key-value pair that can be queryable through GetTraceSummaries. * Only acceptable types are string, float/int and boolean. * @param {string} key - The name of key to add. * @param {boolean|string|number} value - The value to add for the given key. */ Segment.prototype.addAnnotation = function addAnnotation(key, value) { if (!(_.isBoolean(value) || _.isString(value) || _.isFinite(value))) { logger.error('Add annotation key: ' + key + ' value: ' + value + ' failed.' + ' Annotations must be of type string, number or boolean.'); return; } if (_.isUndefined(this.annotations)) this.annotations = {}; this.annotations[key] = value; }; /** * 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) { if (!_.isString(version)) { logger.error('Add service version: ' + version + ' failed.' + 'Service version must be of type string.'); return; } this.service = { version: version }; }; /** * Adds a service with associated version data into the segment. * @param {Object} data - The associated AWS data. */ Segment.prototype.addPluginData = function addPluginData(data) { if (_.isUndefined(this.aws)) this.aws = {}; _.extend(this.aws, data); }; /** * Adds a new subsegment to the array of subsegments. * @param {string} name - The name of the new subsegment to append. */ Segment.prototype.addNewSubsegment = function addNewSubsegment(name) { var subsegment = new Subsegment(name); this.addSubsegment(subsegment); return subsegment; }; /** * Adds a subsegment to the array of subsegments. * @param {Subsegment} subsegment - The subsegment to append. */ Segment.prototype.addSubsegment = function addSubsegment(subsegment) { if (!(subsegment instanceof Subsegment)) throw new Error('Cannot add subsegment: ' + subsegment + '. Not a subsegment.'); if (_.isUndefined(this.subsegments)) this.subsegments = []; subsegment.segment = this; subsegment.parent = this; this.subsegments.push(subsegment); if (!subsegment.end_time) this.incrementCounter(subsegment.counter); }; /** * 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(err, type, remote) { if (!_.isObject(err) && typeof(err) !== '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 (err === this.exception.ex) { this.cause = { id: this.exception.cause }; delete this.exception; return; } delete this.exception; } if (_.isUndefined(this.cause)) { this.cause = { working_directory: process.cwd(), paths: [], exceptions: [] }; } this.cause.exceptions.push(new CapturedException(err, remote)); }; /** * Adds throttled flag into the segment. */ Segment.prototype.addThrottle = function addThrottle() { this.throttle = true; }; /** * Returns a boolean indicating whether or not the segment has been closed. * @returns {boolean} - Returns true if the subsegment is closed. */ Segment.prototype.isClosed = function isClosed() { return !this.in_progress; }; /** * Each segment holds a counter of open subsegments. This increments the counter. * @param {Number} [additional] - An additional amount to increment. Used when adding subsegment trees. */ Segment.prototype.incrementCounter = function incrementCounter(additional) { this.counter = additional ? this.counter + additional + 1 : this.counter + 1; if (this.counter > SegmentUtils.streamingThreshold) _.each(this.subsegments, function(child) { child.streamSubsegments(); }); }; /** * Each segment holds a counter of open subsegments. This decrements * the counter such that it can be called from a child and propagate up. */ Segment.prototype.decrementCounter = function decrementCounter() { this.counter--; }; /** * 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(err, type, remote) { if (!this.end_time) this.end_time = SegmentUtils.getCurrentTime(); if (!_.isUndefined(err)) this.addError(err, type, remote); delete this.in_progress; delete this.exception; delete this.counter; this.flush(); }; /** * Sends the segment to the daemon. */ Segment.prototype.flush = function flush() { if (this.notTraced !== true) { delete this.notTraced; SegmentEmitter.send(this); } else { logger.info('Segment marked as not sampled. Ignoring.'); } }; /** * Returns the formatted segment JSON string. */ Segment.prototype.toString = function toString() { return JSON.stringify(this); }; module.exports = Segment;