deep-framework
Version:
305 lines (238 loc) • 8.59 kB
JavaScript
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;