UNPKG

@instana/core

Version:
231 lines (204 loc) 8.13 kB
/* * (c) Copyright IBM Corp. 2021 * (c) Copyright Instana Inc. and contributors 2020 */ 'use strict'; const constants = require('../constants'); const leftPad = require('../leftPad'); const tracingUtil = require('../tracingUtil'); const VERSION00 = '00'; const LEFT_PAD_16 = '0000000000000000'; // See https://www.w3.org/TR/trace-context-2/#trace-flags for details on the bitmasks. const SAMPLED_BITMASK = 0b1; const RANDOM_TRACE_ID_BITMASK = 0b10; function W3cTraceContext() { // whether the traceparent header is valid this.traceParentValid = false; // the trace context specification version from the traceparent header this.version = undefined; // the trace id from the traceparent header this.traceParentTraceId = undefined; // the parent id from the traceparent header this.traceParentParentId = undefined; // the sampled flag from the traceparent header this.sampled = undefined; // the random trace ID flag from the traceparent header this.randomTraceId = undefined; // whether the tracestate header is valid this.traceStateValid = false; // the non-Instana key-value pairs that come before the in key-value pair this.traceStateHead = undefined; // the trace ID from the in key-value pair from the tracestate header this.instanaTraceId = undefined; // the parent ID from the in key-value pair from the tracestate header this.instanaParentId = undefined; // the non-Instana key-value pairs that come after the in key-value pair this.traceStateTail = undefined; } /** * @param {string} instanaTraceId * @param {string} instanaParentId * @param {boolean | *} sampled * @returns {W3cTraceContext} */ W3cTraceContext.fromInstanaIds = function fromInstanaIds(instanaTraceId, instanaParentId, sampled) { const paddedTraceId = instanaTraceId.length === 16 ? LEFT_PAD_16 + instanaTraceId : instanaTraceId; sampled = typeof sampled === 'boolean' ? sampled : true; const traceContext = new W3cTraceContext(); traceContext.traceParentValid = true; traceContext.version = VERSION00; traceContext.traceParentTraceId = paddedTraceId; traceContext.traceParentParentId = instanaParentId; traceContext.sampled = sampled; // Instana tracers always generate trace IDs randomly, so we can confidently set this flag. traceContext.randomTraceId = true; traceContext.traceStateValid = true; traceContext.instanaTraceId = instanaTraceId; traceContext.instanaParentId = instanaParentId; return traceContext; }; /** * @param {string} traceId * @param {string} parentId * @returns */ W3cTraceContext.createEmptyUnsampled = function createEmptyUnsampled(traceId, parentId) { const paddedTraceId = traceId.length === 16 ? LEFT_PAD_16 + traceId : traceId; const traceContext = new W3cTraceContext(); traceContext.traceParentValid = true; traceContext.version = VERSION00; traceContext.traceParentTraceId = paddedTraceId; traceContext.traceParentParentId = parentId; traceContext.sampled = false; // We always generate a new trace IDs randomly for this case, so we can confidently set this flag. traceContext.randomTraceId = true; traceContext.traceStateValid = true; return traceContext; }; /** * @returns {string} */ W3cTraceContext.prototype.renderTraceParent = function renderTraceParent() { if (!this.traceParentValid) { return ''; } // Since we only support version 00, we must always downgrade to 00 if we have updated any value. return `00-${this.traceParentTraceId}-${this.traceParentParentId}-${this.renderFlags()}`; }; /** * @returns {string} */ W3cTraceContext.prototype.renderFlags = function renderFlags() { let flagsInt = 0; if (this.sampled) { // eslint-disable-next-line no-bitwise flagsInt |= SAMPLED_BITMASK; } if (this.randomTraceId) { // eslint-disable-next-line no-bitwise flagsInt |= RANDOM_TRACE_ID_BITMASK; } return leftPad(flagsInt.toString(16), 2); }; /** * @returns {boolean} */ W3cTraceContext.prototype.hasTraceState = function hasTraceState() { return ((this.instanaTraceId && this.instanaParentId) || this.traceStateHead || this.traceStateTail) != null; }; /** * @returns {string} */ W3cTraceContext.prototype.renderTraceState = function renderTraceState() { if (!this.traceStateValid) { return ''; } /** @type {Array.<*>} */ let allKeyValuePairs = []; const instanaKeyValuePair = this.renderInstanaTraceStateValue(); if (this.traceStateHead) { allKeyValuePairs = allKeyValuePairs.concat(this.traceStateHead); } if (instanaKeyValuePair) { allKeyValuePairs.push(instanaKeyValuePair); } if (this.traceStateTail) { allKeyValuePairs = allKeyValuePairs.concat(this.traceStateTail); } return allKeyValuePairs.join(','); }; W3cTraceContext.prototype.renderInstanaTraceStateValue = function renderInstanaTraceStateValue() { if (this.instanaTraceId && this.instanaParentId) { return `${constants.w3cInstanaEquals + this.instanaTraceId};${this.instanaParentId}`; } else { return null; } }; W3cTraceContext.prototype.resetTraceState = function resetTraceState() { this.traceStateValid = true; this.traceStateHead = null; this.instanaTraceId = null; this.instanaParentId = null; this.traceStateTail = null; }; /** * Modifies this trace context object: * - updates the foreing parent ID in traceparent to the given given value (not that we do not set the * foreign trace ID), * - sets the sampled flag in traceparent to true, * - upserts the in key-value pair in tracestate to the given trace ID and span ID, and moved to the leftmost position. * @param {string} instanaTraceId * @param {string} instanaParentId */ W3cTraceContext.prototype.updateParent = function updateParent(instanaTraceId, instanaParentId) { this.instanaTraceId = instanaTraceId; this.instanaParentId = instanaParentId; this.traceParentParentId = instanaParentId; if (this.traceStateHead && this.traceStateTail) { this.traceStateTail = this.traceStateHead.concat(this.traceStateTail); } else if (this.traceStateHead) { this.traceStateTail = this.traceStateHead; } // ^ If only this.traceStateTail has content, we do not need to update this.traceStateHead nor this.traceStateTail; // we only need to make sure all foreign key-value pairs come to the right of the in key-value pair, which they // do, if they are only in traceStateTail. // Remove everything from traceStateHead to move the in key-value pair to the leftmost position. this.traceStateHead = null; this.sampled = true; }; /** * @param {string} longTraceId */ W3cTraceContext.prototype.restartTrace = function restartTrace(longTraceId) { this.traceParentValid = true; this.version = VERSION00; this.instanaTraceId = longTraceId ? tracingUtil.generateRandomLongTraceId() : tracingUtil.generateRandomTraceId(); this.traceParentTraceId = longTraceId ? this.instanaTraceId : LEFT_PAD_16 + this.instanaTraceId; this.traceParentParentId = this.instanaParentId = tracingUtil.generateRandomSpanId(); this.sampled = true; this.traceStateValid = true; this.traceStateHead = null; this.traceStateTail = null; }; W3cTraceContext.prototype.disableSampling = function disableSampling() { if (this.sampled) { // See https://www.w3.org/TR/trace-context/#mutating-the-traceparent-field // "The parent-id field MUST be set to a new value with the sampled flag update." this.traceParentParentId = tracingUtil.generateRandomSpanId(); } this.sampled = false; }; W3cTraceContext.prototype.clone = function clone() { return Object.assign(new W3cTraceContext(), this); }; W3cTraceContext.prototype.getMostRecentForeignTraceStateMember = function getMostRecentForeignTraceStateMember() { const traceStateToInspect = this.traceStateHead ? this.traceStateHead : this.traceStateTail; if (!traceStateToInspect || traceStateToInspect.length === 0) { return undefined; } return traceStateToInspect[0]; }; W3cTraceContext.VERSION00 = VERSION00; W3cTraceContext.SAMPLED_BITMASK = SAMPLED_BITMASK; W3cTraceContext.RANDOM_TRACE_ID_BITMASK = RANDOM_TRACE_ID_BITMASK; module.exports = W3cTraceContext;