@opentelemetry/propagator-aws-xray
Version:
OpenTelemetry AWS Xray propagator provides context propagation for systems that are using AWS X-Ray format.
163 lines • 6.83 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.
*/
import { trace, TraceFlags, isSpanContextValid, isValidSpanId, isValidTraceId, INVALID_TRACEID, INVALID_SPANID, INVALID_SPAN_CONTEXT, } from '@opentelemetry/api';
export const 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
*/
export class AWSXRayPropagator {
inject(context, carrier, setter) {
const spanContext = trace.getSpan(context)?.spanContext();
if (!spanContext || !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 = (TraceFlags.SAMPLED & spanContext.traceFlags) === 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, AWSXRAY_TRACE_ID_HEADER, traceHeader);
}
extract(context, carrier, getter) {
const spanContext = this.getSpanContextFromHeader(carrier, getter);
if (!isSpanContextValid(spanContext))
return context;
// If a previous propagator already set the trace state, ensure it's propagated
const existingSpan = trace.getSpan(context);
const existingTraceState = existingSpan?.spanContext()?.traceState;
if (existingTraceState) {
spanContext.traceState = existingTraceState;
}
return trace.setSpan(context, trace.wrapSpanContext(spanContext));
}
fields() {
return [AWSXRAY_TRACE_ID_HEADER];
}
getSpanContextFromHeader(carrier, getter) {
const headerKeys = getter.keys(carrier);
const relevantHeaderKey = headerKeys.find(e => {
return e.toLowerCase() === AWSXRAY_TRACE_ID_HEADER;
});
if (!relevantHeaderKey) {
return INVALID_SPAN_CONTEXT;
}
const rawTraceHeader = getter.get(carrier, relevantHeaderKey);
const traceHeader = Array.isArray(rawTraceHeader)
? rawTraceHeader[0]
: rawTraceHeader;
if (!traceHeader || typeof traceHeader !== 'string') {
return INVALID_SPAN_CONTEXT;
}
let pos = 0;
let trimmedPart;
let parsedTraceId = INVALID_TRACEID;
let parsedSpanId = 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 INVALID_SPAN_CONTEXT;
}
const resultSpanContext = {
traceId: parsedTraceId,
spanId: parsedSpanId,
traceFlags: parsedTraceFlags,
isRemote: true,
};
if (!isSpanContextValid(resultSpanContext)) {
return INVALID_SPAN_CONTEXT;
}
return resultSpanContext;
}
static _parseTraceId(xrayTraceId) {
// Check length of trace id
if (xrayTraceId.length !== TRACE_ID_LENGTH) {
return INVALID_TRACEID;
}
// Check version trace id version
if (!xrayTraceId.startsWith(TRACE_ID_VERSION)) {
return 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 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 (!isValidTraceId(resTraceId)) {
return INVALID_TRACEID;
}
return resTraceId;
}
static _parseSpanId(xrayParentId) {
return isValidSpanId(xrayParentId) ? xrayParentId : INVALID_SPANID;
}
static _parseTraceFlag(xraySampledFlag) {
if (xraySampledFlag === NOT_SAMPLED) {
return TraceFlags.NONE;
}
if (xraySampledFlag === IS_SAMPLED) {
return TraceFlags.SAMPLED;
}
return null;
}
}
//# sourceMappingURL=AWSXRayPropagator.js.map