packages/core/lib/segments/segment.js (269 lines of code) (raw):

var crypto = require('crypto'); var CapturedException = require('./attributes/captured_exception'); var SegmentEmitter = require('../segment_emitter'); var SegmentUtils = require('./segment_utils'); var Subsegment = require('./attributes/subsegment'); var TraceID = require('./attributes/trace_id'); var Utils = require('../utils'); 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 sub/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.'); } // Validate the Trace ID var traceId; if (rootId && typeof rootId == 'string') { traceId = TraceID.FromString(rootId); } else { traceId = new TraceID(); } var id = crypto.randomBytes(8).toString('hex'); var startTime = SegmentUtils.getCurrentTime(); this.trace_id = traceId.toString(); 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.serviceData) { this.setServiceData(SegmentUtils.serviceData); } if (SegmentUtils.pluginData) { this.addPluginData(SegmentUtils.pluginData); } if (SegmentUtils.origin) { this.origin = SegmentUtils.origin; } if (SegmentUtils.sdkData) { this.setSDKData(SegmentUtils.sdkData); } }; /** * Adds incoming request data to the http block of the segment. * @param {IncomingRequestData} data - The data of the property to add. */ Segment.prototype.addIncomingRequestData = function addIncomingRequestData(data) { this.http = 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 (typeof value !== 'boolean' && typeof value !== 'string' && !isFinite(value)) { logger.getLogger().error('Failed to add annotation key: ' + key + ' value: ' + value + ' to subsegment ' + this.name + '. Value must be of type string, number or boolean.'); return; } if (typeof key !== 'string') { logger.getLogger().error('Failed to add annotation key: ' + key + ' value: ' + value + ' to subsegment ' + this.name + '. Key must be of type string.'); return; } if (this.annotations === undefined) { this.annotations = {}; } this.annotations[key] = value; }; /** * Adds a User ID that can be queried from the X-Ray console. User ID * must be a string. * @param {string} user - The ID of the user corresponding to this segment */ Segment.prototype.setUser = function (user) { if (typeof user !== 'string') { logger.getLogger().error('Set user: ' + user + ' failed. User IDs must be of type string.'); } this.user = user; }; /** * Adds a key-value pair to the metadata.default attribute when no namespace is given. * 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. * @param {string} [namespace] - The property name to put the key/value pair under. */ Segment.prototype.addMetadata = function(key, value, namespace) { if (typeof key !== 'string') { logger.getLogger().error('Failed to add metadata key: ' + key + ' value: ' + value + ' to segment ' + this.name + '. Key must be of type string.'); return; } if (namespace && typeof namespace !== 'string') { logger.getLogger().error('Failed to add metadata key: ' + key + ' value: ' + value + ' to segment ' + this.name + '. Namespace must be of type string.'); return; } var ns = namespace || 'default'; if (!this.metadata) { this.metadata = {}; } if (!this.metadata[ns]) { this.metadata[ns] = {}; } if (ns !== '__proto__') { this.metadata[ns][key] = value !== null && value !== undefined ? value : ''; } }; /** * Adds data about the AWS X-Ray SDK onto the segment. * @param {Object} data - Object that contains the version of the SDK, and other information. */ Segment.prototype.setSDKData = function setSDKData(data) { if (!data) { logger.getLogger().error('Add SDK data: ' + data + ' failed.' + 'Must not be empty.'); return; } if (!this.aws) { this.aws = {}; } this.aws.xray = data; }; Segment.prototype.setMatchedSamplingRule = function setMatchedSamplingRule(ruleName) { if (this.aws) { this.aws = JSON.parse(JSON.stringify(this.aws)); } if (this.aws && this.aws['xray']) { this.aws.xray['rule_name'] = ruleName; } else { this.aws = {xray: {'rule_name': ruleName}}; } }; /** * Adds data about the service into the segment. * @param {Object} data - Object that contains the version of the application, and other information. */ Segment.prototype.setServiceData = function setServiceData(data) { if (!data) { logger.getLogger().error('Add service data: ' + data + ' failed.' + 'Must not be empty.'); return; } this.service = data; }; /** * Adds a service with associated version data into the segment. * @param {Object} data - The associated AWS data. */ Segment.prototype.addPluginData = function addPluginData(data) { if (this.aws === undefined) { this.aws = {}; } Object.assign(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; }; Segment.prototype.addSubsegmentWithoutSampling = function addSubsegmentWithoutSampling(subsegment) { this.addSubsegment(subsegment); subsegment.notTraced = true; }; Segment.prototype.addNewSubsegmentWithoutSampling = function addNewSubsegmentWithoutSampling(name) { const subsegment = new Subsegment(name); this.addSubsegment(subsegment); subsegment.notTraced = true; 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 (this.subsegments === undefined) { this.subsegments = []; } subsegment.segment = this; subsegment.parent = this; subsegment.notTraced = subsegment.parent.notTraced; subsegment.noOp = subsegment.parent.noOp; this.subsegments.push(subsegment); if (!subsegment.end_time) { this.incrementCounter(subsegment.counter); } }; /** * Removes the subsegment from the subsegments array, used in subsegment streaming. */ Segment.prototype.removeSubsegment = function removeSubsegment(subsegment) { if (!(subsegment instanceof Subsegment)) { throw new Error('Failed to remove subsegment:' + subsegment + ' from subsegment "' + this.name + '". Not a subsegment.'); } if (this.subsegments !== undefined) { var index = this.subsegments.indexOf(subsegment); if (index >= 0) { this.subsegments.splice(index, 1); } } }; /** * Adds error data into the segment. * @param {Error|string} err - The error to capture. * @param {boolean} [remote] - Flag for whether the exception caught was remote or not. */ Segment.prototype.addError = function addError(err, remote) { if (err == null || typeof err !== 'object' && typeof(err) !== 'string') { logger.getLogger().error('Failed to add error:' + err + ' to subsegment "' + this.name + '". Not an object or string literal.'); return; } this.addFaultFlag(); if (this.exception) { if (err === this.exception.ex) { this.cause = { id: this.exception.cause }; delete this.exception; return; } delete this.exception; } if (this.cause === undefined) { this.cause = { working_directory: process.cwd(), exceptions: [] }; } this.cause.exceptions.push(new CapturedException(err, remote)); }; /** * Adds fault flag to the subsegment. */ Segment.prototype.addFaultFlag = function addFaultFlag() { this.fault = true; }; /** * Adds error flag to the subsegment. */ Segment.prototype.addErrorFlag = function addErrorFlag() { this.error = true; }; /** * Adds throttle flag to the subsegment. */ Segment.prototype.addThrottleFlag = function addThrottleFlag() { 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 && this.subsegments && this.subsegments.length > 0) { var open = []; this.subsegments.forEach(function(child) { if (!child.streamSubsegments()) { open.push(child); } }); this.subsegments = open; } }; /** * 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--; if (this.counter <= 0 && this.isClosed()) { this.flush(); } }; /** * Closes the current segment. This automatically sets the end time. * @param {Error|string} [err] - The error to capture. * @param {boolean} [remote] - Flag for whether the exception caught was remote or not. */ Segment.prototype.close = function(err, remote) { if (!this.end_time) { this.end_time = SegmentUtils.getCurrentTime(); } if (err !== undefined) { this.addError(err, remote); } delete this.in_progress; delete this.exception; if (this.counter <= 0) { this.flush(); } }; /** * Sends the segment to the daemon. */ Segment.prototype.flush = function flush() { if (this.notTraced !== true) { delete this.exception; var thisCopy = Utils.objectWithoutProperties( this, ['counter', 'notTraced'], true ); SegmentEmitter.send(thisCopy); } }; Segment.prototype.format = function format() { var ignore = ['segment', 'parent', 'counter']; if (this.subsegments == null || this.subsegments.length === 0) { ignore.push('subsegments'); } var thisCopy = Utils.objectWithoutProperties( this, ignore, false ); return this.serialize(thisCopy); }; Segment.prototype.toString = function toString() { return this.serialize(); }; Segment.prototype.serialize = function serialize(object) { return JSON.stringify( object ?? this, SegmentUtils.getJsonStringifyReplacer() ); }; module.exports = Segment;