@opentelemetry/propagator-aws-xray
Version:
OpenTelemetry AWS Xray propagator provides context propagation for systems that are using AWS X-Ray format.
167 lines • 7.08 kB
JavaScript
;
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.AWSXRayPropagator = exports.AWSXRAY_TRACE_ID_HEADER = void 0;
const api_1 = require("@opentelemetry/api");
exports.AWSXRAY_TRACE_ID_HEADER = 'x-amzn-trace-id';
const TRACE_HEADER_DELIMITER = ';';
const KV_DELIMITER = '=';
const TRACE_ID_KEY = 'Root';
const TRACE_ID_LENGTH = 35;
const TRACE_ID_VERSION = '1';
const TRACE_ID_DELIMITER = '-';
const TRACE_ID_DELIMITER_INDEX_1 = 1;
const TRACE_ID_DELIMITER_INDEX_2 = 10;
const TRACE_ID_FIRST_PART_LENGTH = 8;
const PARENT_ID_KEY = 'Parent';
const SAMPLED_FLAG_KEY = 'Sampled';
const IS_SAMPLED = '1';
const NOT_SAMPLED = '0';
/**
* Implementation of the AWS X-Ray Trace Header propagation protocol. See <a href=
* https://https://docs.aws.amazon.com/xray/latest/devguide/xray-concepts.html#xray-concepts-tracingheader>AWS
* Tracing header spec</a>
*
* An example AWS Xray Tracing Header is shown below:
* X-Amzn-Trace-Id: Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1
*/
class AWSXRayPropagator {
inject(context, carrier, setter) {
const spanContext = api_1.trace.getSpan(context)?.spanContext();
if (!spanContext || !(0, api_1.isSpanContextValid)(spanContext))
return;
const otTraceId = spanContext.traceId;
const timestamp = otTraceId.substring(0, TRACE_ID_FIRST_PART_LENGTH);
const randomNumber = otTraceId.substring(TRACE_ID_FIRST_PART_LENGTH);
const parentId = spanContext.spanId;
const samplingFlag = (api_1.TraceFlags.SAMPLED & spanContext.traceFlags) === api_1.TraceFlags.SAMPLED
? IS_SAMPLED
: NOT_SAMPLED;
// TODO: Add OT trace state to the X-Ray trace header
const traceHeader = `Root=1-${timestamp}-${randomNumber};Parent=${parentId};Sampled=${samplingFlag}`;
setter.set(carrier, exports.AWSXRAY_TRACE_ID_HEADER, traceHeader);
}
extract(context, carrier, getter) {
const spanContext = this.getSpanContextFromHeader(carrier, getter);
if (!(0, api_1.isSpanContextValid)(spanContext))
return context;
// If a previous propagator already set the trace state, ensure it's propagated
const existingSpan = api_1.trace.getSpan(context);
const existingTraceState = existingSpan?.spanContext()?.traceState;
if (existingTraceState) {
spanContext.traceState = existingTraceState;
}
return api_1.trace.setSpan(context, api_1.trace.wrapSpanContext(spanContext));
}
fields() {
return [exports.AWSXRAY_TRACE_ID_HEADER];
}
getSpanContextFromHeader(carrier, getter) {
const headerKeys = getter.keys(carrier);
const relevantHeaderKey = headerKeys.find(e => {
return e.toLowerCase() === exports.AWSXRAY_TRACE_ID_HEADER;
});
if (!relevantHeaderKey) {
return api_1.INVALID_SPAN_CONTEXT;
}
const rawTraceHeader = getter.get(carrier, relevantHeaderKey);
const traceHeader = Array.isArray(rawTraceHeader)
? rawTraceHeader[0]
: rawTraceHeader;
if (!traceHeader || typeof traceHeader !== 'string') {
return api_1.INVALID_SPAN_CONTEXT;
}
let pos = 0;
let trimmedPart;
let parsedTraceId = api_1.INVALID_TRACEID;
let parsedSpanId = api_1.INVALID_SPANID;
let parsedTraceFlags = null;
while (pos < traceHeader.length) {
const delimiterIndex = traceHeader.indexOf(TRACE_HEADER_DELIMITER, pos);
if (delimiterIndex >= 0) {
trimmedPart = traceHeader.substring(pos, delimiterIndex).trim();
pos = delimiterIndex + 1;
}
else {
//last part
trimmedPart = traceHeader.substring(pos).trim();
pos = traceHeader.length;
}
const equalsIndex = trimmedPart.indexOf(KV_DELIMITER);
const value = trimmedPart.substring(equalsIndex + 1);
if (trimmedPart.startsWith(TRACE_ID_KEY)) {
parsedTraceId = AWSXRayPropagator._parseTraceId(value);
}
else if (trimmedPart.startsWith(PARENT_ID_KEY)) {
parsedSpanId = AWSXRayPropagator._parseSpanId(value);
}
else if (trimmedPart.startsWith(SAMPLED_FLAG_KEY)) {
parsedTraceFlags = AWSXRayPropagator._parseTraceFlag(value);
}
}
if (parsedTraceFlags === null) {
return api_1.INVALID_SPAN_CONTEXT;
}
const resultSpanContext = {
traceId: parsedTraceId,
spanId: parsedSpanId,
traceFlags: parsedTraceFlags,
isRemote: true,
};
if (!(0, api_1.isSpanContextValid)(resultSpanContext)) {
return api_1.INVALID_SPAN_CONTEXT;
}
return resultSpanContext;
}
static _parseTraceId(xrayTraceId) {
// Check length of trace id
if (xrayTraceId.length !== TRACE_ID_LENGTH) {
return api_1.INVALID_TRACEID;
}
// Check version trace id version
if (!xrayTraceId.startsWith(TRACE_ID_VERSION)) {
return api_1.INVALID_TRACEID;
}
// Check delimiters
if (xrayTraceId.charAt(TRACE_ID_DELIMITER_INDEX_1) !== TRACE_ID_DELIMITER ||
xrayTraceId.charAt(TRACE_ID_DELIMITER_INDEX_2) !== TRACE_ID_DELIMITER) {
return api_1.INVALID_TRACEID;
}
const epochPart = xrayTraceId.substring(TRACE_ID_DELIMITER_INDEX_1 + 1, TRACE_ID_DELIMITER_INDEX_2);
const uniquePart = xrayTraceId.substring(TRACE_ID_DELIMITER_INDEX_2 + 1, TRACE_ID_LENGTH);
const resTraceId = epochPart + uniquePart;
// Check the content of trace id
if (!(0, api_1.isValidTraceId)(resTraceId)) {
return api_1.INVALID_TRACEID;
}
return resTraceId;
}
static _parseSpanId(xrayParentId) {
return (0, api_1.isValidSpanId)(xrayParentId) ? xrayParentId : api_1.INVALID_SPANID;
}
static _parseTraceFlag(xraySampledFlag) {
if (xraySampledFlag === NOT_SAMPLED) {
return api_1.TraceFlags.NONE;
}
if (xraySampledFlag === IS_SAMPLED) {
return api_1.TraceFlags.SAMPLED;
}
return null;
}
}
exports.AWSXRayPropagator = AWSXRayPropagator;
//# sourceMappingURL=AWSXRayPropagator.js.map