Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Propagate b3 parentspanid and debug flag #1346

Merged
137 changes: 111 additions & 26 deletions packages/opentelemetry-core/src/context/propagation/B3Propagator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,19 @@ import { getParentSpanContext, setExtractedSpanContext } from '../context';
export const X_B3_TRACE_ID = 'x-b3-traceid';
export const X_B3_SPAN_ID = 'x-b3-spanid';
export const X_B3_SAMPLED = 'x-b3-sampled';
export const X_B3_PARENT_SPAN_ID = 'x-b3-parentspanid';
export const X_B3_FLAGS = 'x-b3-flags';
export const PARENT_SPAN_ID_KEY = Context.createKey(
'OpenTelemetry Context Key B3 Parent Span Id'
);
export const DEBUG_FLAG_KEY = Context.createKey(
'OpenTelemetry Context Key B3 Debug Flag'
);
const VALID_TRACEID_REGEX = /^([0-9a-f]{16}){1,2}$/i;
const VALID_SPANID_REGEX = /^[0-9a-f]{16}$/i;
const INVALID_ID_REGEX = /^0+$/i;
const VALID_SAMPLED_VALUES = new Set([true, 'true', 'True', '1', 1]);
const VALID_UNSAMPLED_VALUES = new Set([false, 'false', 'False', '0', 0]);

function isValidTraceId(traceId: string): boolean {
return VALID_TRACEID_REGEX.test(traceId) && !INVALID_ID_REGEX.test(traceId);
Expand All @@ -38,6 +48,77 @@ function isValidSpanId(spanId: string): boolean {
return VALID_SPANID_REGEX.test(spanId) && !INVALID_ID_REGEX.test(spanId);
}

function isValidParentSpanID(spanId: string | undefined): boolean {
return spanId === undefined || isValidSpanId(spanId);
}

function isValidSampledValue(sampled: TraceFlags | undefined): boolean {
return sampled === TraceFlags.SAMPLED || sampled === TraceFlags.NONE;
}

function parseHeader(header: unknown) {
return Array.isArray(header) ? header[0] : header;
}

function getHeaderValue(carrier: unknown, getter: GetterFunction, key: string) {
const header = getter(carrier, key);
return parseHeader(header);
}

function getTraceId(carrier: unknown, getter: GetterFunction): string {
const traceId = getHeaderValue(carrier, getter, X_B3_TRACE_ID);
if (typeof traceId === 'string') {
return traceId.padStart(32, '0');
}
return '';
}

function getSpanId(carrier: unknown, getter: GetterFunction): string {
const spanId = getHeaderValue(carrier, getter, X_B3_SPAN_ID);
if (typeof spanId === 'string') {
return spanId;
}
return '';
}

function getParentSpanId(
carrier: unknown,
getter: GetterFunction
): string | undefined {
const spanId = getHeaderValue(carrier, getter, X_B3_PARENT_SPAN_ID);
if (typeof spanId === 'string') {
return spanId;
}
return;
}

function getDebug(
carrier: unknown,
getter: GetterFunction
): string | undefined {
const debug = getHeaderValue(carrier, getter, X_B3_FLAGS);
return debug === '1' ? '1' : undefined;
}

function getTraceFlags(
carrier: unknown,
getter: GetterFunction
): TraceFlags | undefined {
const traceFlags = getHeaderValue(carrier, getter, X_B3_SAMPLED);
const debug = getDebug(carrier, getter);
if (debug === '1' || VALID_SAMPLED_VALUES.has(traceFlags)) {
return TraceFlags.SAMPLED;
}
if (VALID_UNSAMPLED_VALUES.has(traceFlags)) {
return TraceFlags.NONE;
}
if (traceFlags === undefined) {
srjames90 marked this conversation as resolved.
Show resolved Hide resolved
return TraceFlags.NONE;
}
// This indicates to isValidSampledValue that this is not valid
return;
}

/**
* Propagator for the B3 HTTP header format.
* Based on: https://github.com/openzipkin/b3-propagation
Expand All @@ -46,17 +127,27 @@ export class B3Propagator implements HttpTextPropagator {
inject(context: Context, carrier: unknown, setter: SetterFunction) {
const spanContext = getParentSpanContext(context);
if (!spanContext) return;

const parentSpanId = context.getValue(PARENT_SPAN_ID_KEY) as
| undefined
| string;
if (
isValidTraceId(spanContext.traceId) &&
isValidSpanId(spanContext.spanId)
isValidSpanId(spanContext.spanId) &&
isValidParentSpanID(parentSpanId)
) {
const debug = context.getValue(DEBUG_FLAG_KEY);
setter(carrier, X_B3_TRACE_ID, spanContext.traceId);
setter(carrier, X_B3_SPAN_ID, spanContext.spanId);

// We set the header only if there is an existing sampling decision.
// Otherwise we will omit it => Absent.
if (spanContext.traceFlags !== undefined) {
if (parentSpanId) {
setter(carrier, X_B3_PARENT_SPAN_ID, parentSpanId);
}
// According to the B3 spec, if the debug flag is set,
// the sampled flag shouldn't be propagated as well.
if (debug === '1') {
setter(carrier, 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(
carrier,
X_B3_SAMPLED,
Expand All @@ -69,31 +160,25 @@ export class B3Propagator implements HttpTextPropagator {
}

extract(context: Context, carrier: unknown, getter: GetterFunction): Context {
const traceIdHeader = getter(carrier, X_B3_TRACE_ID);
const spanIdHeader = getter(carrier, X_B3_SPAN_ID);
const sampledHeader = getter(carrier, X_B3_SAMPLED);

const traceIdHeaderValue = Array.isArray(traceIdHeader)
? traceIdHeader[0]
: traceIdHeader;
const spanId = Array.isArray(spanIdHeader) ? spanIdHeader[0] : spanIdHeader;

const options = Array.isArray(sampledHeader)
? sampledHeader[0]
: sampledHeader;

if (typeof traceIdHeaderValue !== 'string' || typeof spanId !== 'string') {
return context;
}

const traceId = traceIdHeaderValue.padStart(32, '0');
const traceId = getTraceId(carrier, getter);
const spanId = getSpanId(carrier, getter);
const parentSpanId = getParentSpanId(carrier, getter);
const traceFlags = getTraceFlags(carrier, getter) as TraceFlags;
const debug = getDebug(carrier, getter);

if (isValidTraceId(traceId) && isValidSpanId(spanId)) {
if (
isValidTraceId(traceId) &&
isValidSpanId(spanId) &&
isValidParentSpanID(parentSpanId) &&
isValidSampledValue(traceFlags)
) {
context = context.setValue(PARENT_SPAN_ID_KEY, parentSpanId);
context = context.setValue(DEBUG_FLAG_KEY, debug);
return setExtractedSpanContext(context, {
traceId,
spanId,
isRemote: true,
traceFlags: isNaN(Number(options)) ? TraceFlags.NONE : Number(options),
traceFlags,
srjames90 marked this conversation as resolved.
Show resolved Hide resolved
});
}
return context;
Expand Down
Loading