@instana/core
Advanced tools
Comparing version 1.127.0 to 1.128.0
{ | ||
"name": "@instana/core", | ||
"version": "1.127.0", | ||
"version": "1.128.0", | ||
"description": "Core library for Instana's Node.js packages", | ||
@@ -136,3 +136,3 @@ "main": "src/index.js", | ||
}, | ||
"gitHead": "17186489c515d8b4df5b4142c9d4d6269496d155" | ||
"gitHead": "a08cd7c136eecdf11278b3e927da313a15ab33e8" | ||
} |
@@ -45,2 +45,13 @@ /* | ||
exports.sqsAttributeNames = { | ||
TRACE_ID: 'X_INSTANA_T', | ||
LEGACY_TRACE_ID: 'X_INSTANA_ST', | ||
SPAN_ID: 'X_INSTANA_S', | ||
LEGACY_SPAN_ID: 'X_INSTANA_SS', | ||
LEVEL: 'X_INSTANA_L', | ||
LEGACY_LEVEL: 'X_INSTANA_SL' | ||
}; | ||
exports.snsSqsInstanaHeaderPrefixRegex = /"X_INSTANA_/i; | ||
/** | ||
@@ -47,0 +58,0 @@ * Determine if <span> is an entry span (server span). |
@@ -100,2 +100,3 @@ /* | ||
exports.supportedVersion = supportedVersion; | ||
exports.util = tracingUtil; | ||
@@ -102,0 +103,0 @@ /** |
@@ -10,15 +10,6 @@ /* | ||
const cls = require('../../../../cls'); | ||
const { ENTRY, EXIT, isExitSpan } = require('../../../../constants'); | ||
const { ENTRY, EXIT, isExitSpan, snsSqsInstanaHeaderPrefixRegex, sqsAttributeNames } = require('../../../../constants'); | ||
const requireHook = require('../../../../../util/requireHook'); | ||
const tracingUtil = require('../../../../tracingUtil'); | ||
const headerNames = { | ||
TRACE_ID: 'X_INSTANA_T', | ||
LEGACY_TRACE_ID: 'X_INSTANA_ST', | ||
SPAN_ID: 'X_INSTANA_S', | ||
LEGACY_SPAN_ID: 'X_INSTANA_SS', | ||
LEVEL: 'X_INSTANA_L', | ||
LEGACY_LEVEL: 'X_INSTANA_SL' | ||
}; | ||
// Available call types to be sent into span.data.sqs.type | ||
@@ -194,3 +185,3 @@ const callTypes = { | ||
attributes[headerNames.LEVEL] = { | ||
attributes[sqsAttributeNames.LEVEL] = { | ||
DataType: 'String', | ||
@@ -206,3 +197,3 @@ StringValue: '0' | ||
attributes[headerNames.TRACE_ID] = { | ||
attributes[sqsAttributeNames.TRACE_ID] = { | ||
DataType: 'String', | ||
@@ -212,3 +203,3 @@ StringValue: span.t | ||
attributes[headerNames.SPAN_ID] = { | ||
attributes[sqsAttributeNames.SPAN_ID] = { | ||
DataType: 'String', | ||
@@ -218,3 +209,3 @@ StringValue: span.s | ||
attributes[headerNames.LEVEL] = { | ||
attributes[sqsAttributeNames.LEVEL] = { | ||
DataType: 'String', | ||
@@ -252,4 +243,2 @@ StringValue: '1' | ||
return cls.ns.runAndReturn(() => { | ||
let attributes; | ||
const span = cls.startSpan('sqs', ENTRY); | ||
@@ -293,4 +282,7 @@ span.stack = tracingUtil.getStackTrace(instrumentedSendMessage); | ||
} else if (data && data.Messages && data.Messages.length > 0) { | ||
attributes = convertAttributesFromSQS(data.Messages[0].MessageAttributes); | ||
if (readAttribCaseInsensitive(attributes, headerNames.LEVEL, headerNames.LEGACY_LEVEL) === '0') { | ||
let tracingAttributes = readTracingAttributes(data.Messages[0].MessageAttributes); | ||
if (!hasTracingAttributes(tracingAttributes)) { | ||
tracingAttributes = readTracingAttributesFromSns(data.Messages[0].Body); | ||
} | ||
if (tracingAttributes.level === '0') { | ||
cls.setTracingLevel('0'); | ||
@@ -301,4 +293,3 @@ setImmediate(() => span.cancel()); | ||
configureEntrySpan(span, data, attributes); | ||
configureEntrySpan(span, data, tracingAttributes); | ||
setImmediate(() => finishSpan(null, data, span)); | ||
@@ -328,4 +319,8 @@ return originalCallback.apply(this, arguments); | ||
} else if (data && data.Messages && data.Messages.length > 0) { | ||
attributes = convertAttributesFromSQS(data.Messages[0].MessageAttributes); | ||
if (readAttribCaseInsensitive(attributes, headerNames.LEVEL, headerNames.LEGACY_LEVEL) === '0') { | ||
let tracingAttributes = readTracingAttributes(data.Messages[0].MessageAttributes); | ||
if (!hasTracingAttributes(tracingAttributes)) { | ||
tracingAttributes = readTracingAttributesFromSns(data.Messages[0].Body); | ||
} | ||
if (tracingAttributes.level === '0') { | ||
cls.setTracingLevel('0'); | ||
@@ -336,4 +331,3 @@ setImmediate(() => span.cancel()); | ||
configureEntrySpan(span, data, attributes); | ||
configureEntrySpan(span, data, tracingAttributes); | ||
setImmediate(() => finishSpan(null, data, span)); | ||
@@ -375,50 +369,100 @@ } else { | ||
function readAttribCaseInsensitive(attributes, key1, key2) { | ||
return ( | ||
tracingUtil.readAttribCaseInsensitive(attributes, key1) || tracingUtil.readAttribCaseInsensitive(attributes, key2) | ||
); | ||
} | ||
/** | ||
* Flattens the AWS SQS MessageAttribute format into a basic object structure. | ||
* Reads all trace context relevant message attributes from an incoming message and provides them in a normalized format | ||
* for later processing. | ||
*/ | ||
function convertAttributesFromSQS(sqsAttributes) { | ||
const attributes = {}; | ||
function readTracingAttributes(sqsAttributes) { | ||
const tracingAttributes = {}; | ||
if (!sqsAttributes) { | ||
return attributes; | ||
return tracingAttributes; | ||
} | ||
const keys = Object.keys(sqsAttributes); | ||
tracingAttributes.traceId = readMessageAttributeWithFallback( | ||
sqsAttributes, | ||
sqsAttributeNames.TRACE_ID, | ||
sqsAttributeNames.LEGACY_TRACE_ID | ||
); | ||
tracingAttributes.parentId = readMessageAttributeWithFallback( | ||
sqsAttributes, | ||
sqsAttributeNames.SPAN_ID, | ||
sqsAttributeNames.LEGACY_SPAN_ID | ||
); | ||
tracingAttributes.level = readMessageAttributeWithFallback( | ||
sqsAttributes, | ||
sqsAttributeNames.LEVEL, | ||
sqsAttributeNames.LEGACY_LEVEL | ||
); | ||
for (let i = 0; i < keys.length; i++) { | ||
const key = keys[i]; | ||
return tracingAttributes; | ||
} | ||
if (sqsAttributes[key].DataType === 'String') { | ||
attributes[key] = sqsAttributes[key].StringValue; | ||
function readTracingAttributesFromSns(messageBody) { | ||
const tracingAttributes = {}; | ||
// Parsing the message body introduces a tiny overhead which we want to avoid unless we are sure that the incoming | ||
// message actually has tracing attributes. Thus some preliminary, cheaper checks are executed first. | ||
if ( | ||
typeof messageBody === 'string' && | ||
messageBody.startsWith('{') && | ||
messageBody.includes('"Type":"Notification"') && | ||
snsSqsInstanaHeaderPrefixRegex.test(messageBody) | ||
) { | ||
try { | ||
const parsedBody = JSON.parse(messageBody); | ||
if (parsedBody && parsedBody.MessageAttributes) { | ||
tracingAttributes.traceId = readMessageAttributeWithFallback( | ||
parsedBody.MessageAttributes, | ||
sqsAttributeNames.TRACE_ID, | ||
sqsAttributeNames.LEGACY_TRACE_ID | ||
); | ||
tracingAttributes.parentId = readMessageAttributeWithFallback( | ||
parsedBody.MessageAttributes, | ||
sqsAttributeNames.SPAN_ID, | ||
sqsAttributeNames.LEGACY_SPAN_ID | ||
); | ||
tracingAttributes.level = readMessageAttributeWithFallback( | ||
parsedBody.MessageAttributes, | ||
sqsAttributeNames.LEVEL, | ||
sqsAttributeNames.LEGACY_LEVEL | ||
); | ||
} | ||
} catch (e) { | ||
// The attempt to parse the message body as JSON failed, so this is not an SQS message resulting from an SNS | ||
// notification (SNS-to-SQS subscription), in which case we are not interested in the body. Ignore the error and | ||
// move on. | ||
} | ||
} | ||
return tracingAttributes; | ||
} | ||
return attributes; | ||
function readMessageAttributeWithFallback(attributes, key1, key2) { | ||
const attribute = | ||
tracingUtil.readAttribCaseInsensitive(attributes, key1) || tracingUtil.readAttribCaseInsensitive(attributes, key2); | ||
if (attribute) { | ||
// attribute.stringValue is used by SQS message attributes, attribute.Value is used by SNS-to-SQS. | ||
return attribute.StringValue || attribute.Value; | ||
} | ||
} | ||
/** | ||
* Checks whether the given tracingAttributes object has at least one attribute set, that is, if there have been Instana | ||
* message attributes present when converting message attributes into this object. | ||
*/ | ||
function hasTracingAttributes(tracingAttributes) { | ||
return tracingAttributes.traceId != null || tracingAttributes.parentId != null || tracingAttributes.level != null; | ||
} | ||
/** | ||
* Add extra info to the entry span after messages are received | ||
* @param {*} span The SQS Span | ||
* @param {*} data The data returned by the SQS API | ||
* @param {*} attributes The SQS Message Attributes | ||
* @param {*} tracingAttributes The message attributes relevant for tracing | ||
*/ | ||
function configureEntrySpan(span, data, attributes) { | ||
function configureEntrySpan(span, data, tracingAttributes) { | ||
span.data.sqs.size = data.Messages.length; | ||
span.ts = Date.now(); | ||
const spanT = readAttribCaseInsensitive(attributes, headerNames.TRACE_ID, headerNames.LEGACY_TRACE_ID); | ||
const spanP = readAttribCaseInsensitive(attributes, headerNames.SPAN_ID, headerNames.LEGACY_SPAN_ID); | ||
if (spanT) { | ||
span.t = spanT; | ||
if (tracingAttributes.traceId && tracingAttributes.parentId) { | ||
span.t = tracingAttributes.traceId; | ||
span.p = tracingAttributes.parentId; | ||
} | ||
if (spanP) { | ||
span.p = spanP; | ||
} | ||
} | ||
@@ -425,0 +469,0 @@ |
@@ -23,56 +23,61 @@ /* | ||
* The functions in this module return an object literal with the following shape: | ||
* { | ||
* traceId <string>: | ||
* - the trace ID | ||
* | ||
* @typedef {Object} TracingHeaders | ||
* @property {string} [traceId] the trace ID: | ||
* - will be used for span.t | ||
* - will be used for propagating X-INSTANA-T downstream | ||
* - will be used for the trace ID part when propagating traceparent downstream | ||
* longTraceId <string>: | ||
* @property {string} [longTraceId] | ||
* - the full trace ID, when limiting a 128 bit trace ID to 64 bit has occured | ||
* - when no limiting has been applied, this is unset | ||
* - will be used for span.lt | ||
* usedTraceParent <boolean>: | ||
* - true if and only if trace ID and parent ID have been taken from traceparent instead of X-INSTAN-T/X-INSTANA-S. | ||
* parentId <string>: | ||
* @property {boolean} usedTraceParent | ||
* - true if and only if trace ID and parent ID have been taken from the traceparent head (instead of being either | ||
* taken from X-INSTAN-T/X-INSTANA-S or having been generated). | ||
* @property {string} [parentId] | ||
* - the parent span ID | ||
* - will be used for span.p | ||
* - will be used for propagating X-INSTANA-S downstream | ||
* - before propagating traceparent another exit span will be created, whose span ID will be used for the parent ID | ||
* part in traceparent | ||
* level: <string>: | ||
* - can be null, when this is a the root entry span of a new trace | ||
* @property {string} level | ||
* - the tracing level, either '1' (tracing) or '0' (suppressing/not creating spans) | ||
* - progated downstream as the first component of X-INSTANA-L | ||
* - propagted downstream as the sampled flag in traceparent | ||
* correlationType <string>: | ||
* @property {string} [correlationType] | ||
* - the correlation type parsed from X-INSTANA-L | ||
* - will be used for span.crtp | ||
* - will not be propagated downstream | ||
* correlationId <string>: | ||
* @property {string} [correlationId] | ||
* - the correlation ID parsed from X-INSTANA-L | ||
* - will be used for span.crid | ||
* - will not be propagated downstream | ||
* synthetic <boolean>: | ||
* @property {boolean} synthetic | ||
* - true if and only if X-INSTANA-SYNTHETIC=1 was present | ||
* - will be used for span.sy | ||
* - will not be propagated downstream | ||
* w3cTraceContext <object>: | ||
* - see ./w3c_trace_context/W3cTraceContext for documentation of attributes | ||
* @property {InstanaAncestor} [instanaAncestor] | ||
* - only captured when no X-INSTANA-T/S were incoming, but traceparent plus tracestate with an "in" key-value pair | ||
* were present in the incoming request | ||
* - will be used as span.ia when present | ||
* @property {import('./w3c_trace_context/W3cTraceContext')} w3cTraceContext | ||
* - the W3C trace context information that was extracted from the incoming request headers traceparent and | ||
* tracestate or a newly created W3C trace context if those headers were not present or invalid | ||
* - will be used to initialize the internal representation of the incoming traceparent/tracestate | ||
* - will be used to manipulate that internal representation according to the W3C trace context spec when creating | ||
* instanaAncestor <object>: | ||
* - only captured when no X-INSTANA-T/S were incoming, but traceparent plus tracestate with an "in" key-value pair | ||
* child spans of the entry span | ||
* - will be used as span.ia when present | ||
* - structure/attributes: | ||
* { | ||
* t: trace ID from tracestate "in" key-value pair | ||
* p: parent ID from tracestate "in" key-value pair | ||
* } | ||
* } | ||
* new child spans and propagating W3C trace context headers downstream | ||
* - see ./w3c_trace_context/W3cTraceContext for documentation of attributes | ||
*/ | ||
/** | ||
* @typedef {Object} InstanaAncestor | ||
* @property {string} t the trace ID from tracestate "in" key-value pair, that is, the trace ID of the closest ancestor | ||
* span in the trace tree that has been created by an Instana tracer | ||
* @property {string} p the parent span ID from tracestate "in" key-value pair, that is, the span ID of the closest | ||
* ancestor in the trace tree that has been created by an Instana tracer | ||
*/ | ||
/** | ||
* Inspects the headers of an incoming HTTP request for X-INSTANA-T, X-INSTANA-S, X-INSTANA-L, as well as the W3C trace | ||
* context headers traceparent and tracestate. | ||
* @param {import('http').IncomingMessage} req | ||
* @returns {TracingHeaders} | ||
*/ | ||
@@ -91,2 +96,3 @@ exports.fromHttpRequest = function fromHttpRequest(req) { | ||
* @param {import('http').IncomingHttpHeaders} headers | ||
* @returns {TracingHeaders} | ||
*/ | ||
@@ -103,8 +109,2 @@ exports.fromHeaders = function fromHeaders(headers) { | ||
if (correlationType && correlationId) { | ||
// Ignore X-INSTANA-T/-S and force starting a new span if we received correlation info. | ||
xInstanaT = null; | ||
xInstanaS = null; | ||
} | ||
if (isSuppressed(level)) { | ||
@@ -119,2 +119,8 @@ // Ignore X-INSTANA-T/-S if X-INSTANA-L: 0 is also present. | ||
if (correlationType && correlationId) { | ||
// Ignore X-INSTANA-T/-S and force starting a new span if we received correlation info. | ||
xInstanaT = null; | ||
xInstanaS = null; | ||
} | ||
if (xInstanaT && xInstanaS && w3cTraceContext) { | ||
@@ -187,2 +193,8 @@ // X-INSTANA- headers *and* W3C trace context headers are present. We use the X-NSTANA- values for tracing and also | ||
} | ||
if (!traceId || !parentId) { | ||
// No X-INSTANA- headers, using traceparent is disabled and there was also no in key-value pair in tracestate, | ||
// thus we start a new trace. | ||
traceId = tracingUtil.generateRandomTraceId(); | ||
parentId = null; | ||
} | ||
return limitTraceId({ | ||
@@ -357,22 +369,2 @@ traceId, | ||
/** | ||
* @typedef {Object} InstanaAncestor | ||
* @property {string} t | ||
* @property {string} p | ||
*/ | ||
/** | ||
* @typedef {Object} TracingHeaders | ||
* @property {string} [traceId] | ||
* @property {string} [longTraceId] | ||
* @property {string} [parentId] | ||
* @property {boolean} usedTraceParent | ||
* @property {import('./w3c_trace_context/W3cTraceContext')} w3cTraceContext | ||
* @property {string} level | ||
* @property {string} [correlationType] | ||
* @property {string} [correlationId] | ||
* @property {boolean} synthetic | ||
* @property {InstanaAncestor} [instanaAncestor] | ||
*/ | ||
/** | ||
* @param {TracingHeaders} result | ||
@@ -379,0 +371,0 @@ * @returns {TracingHeaders} |
520922
14765