@uoa/lambda-tracing
Version:
Library for logging & distributed tracing in UoA Lambda projects
155 lines (154 loc) • 6.6 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.UoaB3Propagator = exports.B3_PARENT_SPAN_ID_KEY = exports.B3_TRACE_ID_LENGTH_KEY = exports.B3_INFO_KEY = exports.B3_DEBUG_FLAG_KEY = exports.X_B3_INFO = exports.X_B3_FLAGS = exports.X_B3_PARENT_SPAN_ID = exports.X_B3_SAMPLED = exports.X_B3_SPAN_ID = exports.X_B3_TRACE_ID = void 0;
/**
* Class extended from OpenTelemetry B3MultiPropagator so that we can:
* 1. Propagate & log the X-B3-Info header
* 2. Propagate the correct X-B3-ParentSpanId
*
* See {@link https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/opentelemetry-propagator-b3/src/B3MultiPropagator.ts}
*/
const api_1 = require("@opentelemetry/api");
const core_1 = require("@opentelemetry/core");
const tracing_1 = require("./tracing");
const crypto = require('crypto');
exports.X_B3_TRACE_ID = 'x-b3-traceid';
exports.X_B3_SPAN_ID = 'x-b3-spanid';
exports.X_B3_SAMPLED = 'x-b3-sampled';
exports.X_B3_PARENT_SPAN_ID = 'x-b3-parentspanid';
exports.X_B3_FLAGS = 'x-b3-flags';
exports.X_B3_INFO = 'x-b3-info';
exports.B3_DEBUG_FLAG_KEY = (0, api_1.createContextKey)('B3 Debug Flag');
exports.B3_INFO_KEY = (0, api_1.createContextKey)('B3 Info Header');
exports.B3_TRACE_ID_LENGTH_KEY = (0, api_1.createContextKey)('B3 TraceId Header Length');
exports.B3_PARENT_SPAN_ID_KEY = (0, api_1.createContextKey)('B3 Parent Span Id');
const VALID_SAMPLED_VALUES = new Set([true, 'true', 'True', '1', 1]);
const VALID_UNSAMPLED_VALUES = new Set([false, 'false', 'False', '0', 0]);
function parseHeader(header) {
return Array.isArray(header) ? header[0] : header;
}
function getHeaderValue(carrier, getter, key) {
const header = getter.get(carrier, key);
return parseHeader(header);
}
function getTraceIdLength(carrier, getter) {
const traceId = getHeaderValue(carrier, getter, exports.X_B3_TRACE_ID);
if (typeof traceId === 'string') {
if (traceId.length <= 16 || traceId.length > 32) {
return 16;
}
else {
return 32;
}
}
return 16;
}
function getTraceId(carrier, getter) {
const traceId = getHeaderValue(carrier, getter, exports.X_B3_TRACE_ID);
if (typeof traceId === 'string') {
return traceId.padStart(32, '0');
}
return '';
}
function getSpanId(carrier, getter) {
const spanId = getHeaderValue(carrier, getter, exports.X_B3_SPAN_ID);
if (typeof spanId === 'string') {
return spanId;
}
return '';
}
function getParentSpanId(carrier, getter) {
const parentSpanId = getHeaderValue(carrier, getter, exports.X_B3_PARENT_SPAN_ID);
if (typeof parentSpanId === 'string') {
return parentSpanId;
}
return '';
}
function getDebug(carrier, getter) {
const debug = getHeaderValue(carrier, getter, exports.X_B3_FLAGS);
return debug === '1' ? '1' : undefined;
}
function getTraceFlags(carrier, getter) {
const traceFlags = getHeaderValue(carrier, getter, exports.X_B3_SAMPLED);
const debug = getDebug(carrier, getter);
if (debug === '1' || VALID_SAMPLED_VALUES.has(traceFlags)) {
return api_1.TraceFlags.SAMPLED;
}
if (traceFlags === undefined || VALID_UNSAMPLED_VALUES.has(traceFlags)) {
return api_1.TraceFlags.NONE;
}
// This indicates to isValidSampledValue that this is not valid
return;
}
function getInfo(carrier, getter) {
const info = getHeaderValue(carrier, getter, exports.X_B3_INFO);
if (typeof info === 'string') {
return info;
}
return undefined;
}
class UoaB3Propagator {
inject(context, carrier, setter) {
const spanContext = api_1.trace.getSpanContext(context);
if (!spanContext ||
!(0, api_1.isSpanContextValid)(spanContext) ||
(0, core_1.isTracingSuppressed)(context))
return;
const debug = context.getValue(exports.B3_DEBUG_FLAG_KEY);
const traceIdLength = context.getValue(exports.B3_TRACE_ID_LENGTH_KEY);
setter.set(carrier, exports.X_B3_TRACE_ID, spanContext.traceId.slice(spanContext.traceId.length - traceIdLength));
setter.set(carrier, exports.X_B3_SPAN_ID, crypto.randomBytes(8).toString('hex'));
setter.set(carrier, exports.X_B3_PARENT_SPAN_ID, spanContext.spanId);
const info = context.getValue(exports.B3_INFO_KEY);
if ((0, tracing_1.getTraceInfoHeader)()) {
setter.set(carrier, exports.X_B3_INFO, (0, tracing_1.getTraceInfoHeader)());
}
else if (info && typeof info === 'string') {
setter.set(carrier, exports.X_B3_INFO, info.toString());
}
// According to the B3 spec, if the debug flag is set,
// the sampled flag shouldn't be propagated as well.
if (debug === '1') {
setter.set(carrier, exports.X_B3_FLAGS, debug);
}
else if (spanContext.traceFlags !== undefined) {
// We set the header only if there is an existing sampling decision.
// Otherwise we will omit it => Absent.
setter.set(carrier, exports.X_B3_SAMPLED, (api_1.TraceFlags.SAMPLED & spanContext.traceFlags) === api_1.TraceFlags.SAMPLED
? '1'
: '0');
}
}
extract(context, carrier, getter) {
const traceIdLength = getTraceIdLength(carrier, getter);
context = context.setValue(exports.B3_TRACE_ID_LENGTH_KEY, traceIdLength);
const traceId = getTraceId(carrier, getter);
const spanId = getSpanId(carrier, getter);
const parentSpanId = getParentSpanId(carrier, getter);
context = context.setValue(exports.B3_PARENT_SPAN_ID_KEY, parentSpanId);
const traceFlags = getTraceFlags(carrier, getter);
const debug = getDebug(carrier, getter);
const info = getInfo(carrier, getter);
context = context.setValue(exports.B3_DEBUG_FLAG_KEY, debug);
if (info) {
context = context.setValue(exports.B3_INFO_KEY, info);
}
return api_1.trace.setSpanContext(context, {
traceId,
spanId,
isRemote: true,
traceFlags,
});
}
fields() {
return [
exports.X_B3_TRACE_ID,
exports.X_B3_SPAN_ID,
exports.X_B3_FLAGS,
exports.X_B3_SAMPLED,
exports.X_B3_PARENT_SPAN_ID,
exports.X_B3_INFO
];
}
}
exports.UoaB3Propagator = UoaB3Propagator;
;