From 4b7888930324538c026345b5c14bad96b9f70f4f Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Tue, 21 Jan 2025 12:16:24 -0800 Subject: [PATCH 1/5] add lineage to trace header injection and baggage --- .../src/AWSXRayPropagator.ts | 62 ++++++++++++++----- 1 file changed, 48 insertions(+), 14 deletions(-) diff --git a/propagators/propagator-aws-xray/src/AWSXRayPropagator.ts b/propagators/propagator-aws-xray/src/AWSXRayPropagator.ts index 06f535ad93..d1a8843148 100644 --- a/propagators/propagator-aws-xray/src/AWSXRayPropagator.ts +++ b/propagators/propagator-aws-xray/src/AWSXRayPropagator.ts @@ -27,7 +27,8 @@ import { isValidTraceId, INVALID_TRACEID, INVALID_SPANID, - INVALID_SPAN_CONTEXT, + propagation, + Baggage, } from '@opentelemetry/api'; export const AWSXRAY_TRACE_ID_HEADER = 'x-amzn-trace-id'; @@ -49,6 +50,8 @@ const SAMPLED_FLAG_KEY = 'Sampled'; const IS_SAMPLED = '1'; const NOT_SAMPLED = '0'; +const LINEAGE_KEY = "Lineage"; + /** * Implementation of the AWS X-Ray Trace Header propagation protocol. See AWS @@ -66,6 +69,8 @@ export class AWSXRayPropagator implements TextMapPropagator { const timestamp = otTraceId.substring(0, TRACE_ID_FIRST_PART_LENGTH); const randomNumber = otTraceId.substring(TRACE_ID_FIRST_PART_LENGTH); + const xrayTraceId = `${TRACE_ID_VERSION}${TRACE_ID_DELIMITER}${timestamp}${TRACE_ID_DELIMITER}${randomNumber}`; + const parentId = spanContext.spanId; const samplingFlag = (TraceFlags.SAMPLED & spanContext.traceFlags) === TraceFlags.SAMPLED @@ -73,31 +78,52 @@ export class AWSXRayPropagator implements TextMapPropagator { : NOT_SAMPLED; // TODO: Add OT trace state to the X-Ray trace header - const traceHeader = `Root=1-${timestamp}-${randomNumber};Parent=${parentId};Sampled=${samplingFlag}`; + let traceHeader = + `${TRACE_ID_KEY}` + + `${KV_DELIMITER}` + + `${xrayTraceId}` + + `${TRACE_HEADER_DELIMITER}` + + `${PARENT_ID_KEY}` + + `${KV_DELIMITER}` + + `${parentId}` + + `${TRACE_HEADER_DELIMITER}` + + `${SAMPLED_FLAG_KEY}` + + `${KV_DELIMITER}` + + `${samplingFlag}`; + + const baggage = propagation.getBaggage(context); + const lineageV2Header = baggage?.getEntry(LINEAGE_KEY)?.value; + + if (lineageV2Header) { + traceHeader += + `${TRACE_HEADER_DELIMITER}` + + `${LINEAGE_KEY}` + + `${KV_DELIMITER}` + + `${lineageV2Header}`; + } + setter.set(carrier, AWSXRAY_TRACE_ID_HEADER, traceHeader); } extract(context: Context, carrier: unknown, getter: TextMapGetter): Context { - const spanContext = this.getSpanContextFromHeader(carrier, getter); - if (!isSpanContextValid(spanContext)) return context; - - return trace.setSpan(context, trace.wrapSpanContext(spanContext)); + return this.getContextFromHeader(context, carrier, getter); } fields(): string[] { return [AWSXRAY_TRACE_ID_HEADER]; } - private getSpanContextFromHeader( + private getContextFromHeader( + context: Context, carrier: unknown, getter: TextMapGetter - ): SpanContext { + ): Context { const headerKeys = getter.keys(carrier); const relevantHeaderKey = headerKeys.find(e => { return e.toLowerCase() === AWSXRAY_TRACE_ID_HEADER; }); if (!relevantHeaderKey) { - return INVALID_SPAN_CONTEXT; + return context; } const rawTraceHeader = getter.get(carrier, relevantHeaderKey); const traceHeader = Array.isArray(rawTraceHeader) @@ -105,9 +131,11 @@ export class AWSXRayPropagator implements TextMapPropagator { : rawTraceHeader; if (!traceHeader || typeof traceHeader !== 'string') { - return INVALID_SPAN_CONTEXT; + return context; } + let baggage: Baggage = propagation.getBaggage(context) || propagation.createBaggage(); + let pos = 0; let trimmedPart: string; let parsedTraceId = INVALID_TRACEID; @@ -133,10 +161,12 @@ export class AWSXRayPropagator implements TextMapPropagator { parsedSpanId = AWSXRayPropagator._parseSpanId(value); } else if (trimmedPart.startsWith(SAMPLED_FLAG_KEY)) { parsedTraceFlags = AWSXRayPropagator._parseTraceFlag(value); + } else if (trimmedPart.startsWith(LINEAGE_KEY)) { + baggage = baggage.setEntry(LINEAGE_KEY, {value}); } } if (parsedTraceFlags === null) { - return INVALID_SPAN_CONTEXT; + return context; } const resultSpanContext: SpanContext = { traceId: parsedTraceId, @@ -144,10 +174,14 @@ export class AWSXRayPropagator implements TextMapPropagator { traceFlags: parsedTraceFlags, isRemote: true, }; - if (!isSpanContextValid(resultSpanContext)) { - return INVALID_SPAN_CONTEXT; + if (isSpanContextValid(resultSpanContext)) { + context = trace.setSpan(context, trace.wrapSpanContext(resultSpanContext)); } - return resultSpanContext; + if (baggage.getAllEntries().length > 0) { + context = propagation.setBaggage(context, baggage); + } + + return context; } private static _parseTraceId(xrayTraceId: string): string { From d73350be12f6afe40ab253fb9782b962f574e1d8 Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Tue, 21 Jan 2025 12:17:50 -0800 Subject: [PATCH 2/5] add lineage header validation --- .../src/AWSXRayPropagator.ts | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/propagators/propagator-aws-xray/src/AWSXRayPropagator.ts b/propagators/propagator-aws-xray/src/AWSXRayPropagator.ts index d1a8843148..f2f3983568 100644 --- a/propagators/propagator-aws-xray/src/AWSXRayPropagator.ts +++ b/propagators/propagator-aws-xray/src/AWSXRayPropagator.ts @@ -51,6 +51,10 @@ const IS_SAMPLED = '1'; const NOT_SAMPLED = '0'; const LINEAGE_KEY = "Lineage"; +const LINEAGE_DELIMITER = ":"; +const LINEAGE_HASH_LENGTH = 8; +const LINEAGE_MAX_REQUEST_COUNTER = 255; +const LINEAGE_MAX_LOOP_COUNTER = 32767; /** * Implementation of the AWS X-Ray Trace Header propagation protocol. See = 0 && requestCounter <= LINEAGE_MAX_REQUEST_COUNTER; + const isValidLoopCounter = loopCounter >= 0 && loopCounter <= LINEAGE_MAX_LOOP_COUNTER; + + return isValidKey && isValidRequestCounter && isValidLoopCounter; + } + private static _parseTraceFlag(xraySampledFlag: string): TraceFlags | null { if (xraySampledFlag === NOT_SAMPLED) { return TraceFlags.NONE; From 81a699581534ee064e1a1901e719f0514124a903 Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Fri, 24 Jan 2025 10:32:42 -0800 Subject: [PATCH 3/5] add unit tests for lineage --- .../test/AWSXRayPropagator.test.ts | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/propagators/propagator-aws-xray/test/AWSXRayPropagator.test.ts b/propagators/propagator-aws-xray/test/AWSXRayPropagator.test.ts index 4a6ab8db1c..7f04d73165 100644 --- a/propagators/propagator-aws-xray/test/AWSXRayPropagator.test.ts +++ b/propagators/propagator-aws-xray/test/AWSXRayPropagator.test.ts @@ -24,6 +24,7 @@ import { TraceFlags, trace, TextMapGetter, + propagation, } from '@opentelemetry/api'; import { TraceState } from '@opentelemetry/core'; @@ -33,6 +34,7 @@ describe('AWSXRayPropagator', () => { const xrayPropagator = new AWSXRayPropagator(); const TRACE_ID = '8a3c60f7d188f8fa79d48a391a778fa6'; const SPAN_ID = '53995c3f42cd8ad8'; + const LINEAGE_ID = '100:e3b0c442:11'; const SAMPLED_TRACE_FLAG = TraceFlags.SAMPLED; const NOT_SAMPLED_TRACE_FLAG = TraceFlags.NONE; @@ -119,6 +121,26 @@ describe('AWSXRayPropagator', () => { assert.deepStrictEqual(carrier, {}); }); + + it('should inject with lineage', () => { + const spanContext: SpanContext = { + traceId: TRACE_ID, + spanId: SPAN_ID, + traceFlags: SAMPLED_TRACE_FLAG, + }; + xrayPropagator.inject( + propagation.setBaggage(trace.setSpan(ROOT_CONTEXT, trace.wrapSpanContext(spanContext)), propagation.createBaggage({ + 'Lineage': { value: LINEAGE_ID } + })), + carrier, + defaultTextMapSetter + ); + + assert.deepStrictEqual( + carrier[AWSXRAY_TRACE_ID_HEADER], + 'Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad8;Sampled=1;Lineage=100:e3b0c442:11' + ); + }); }); describe('.extract()', () => { @@ -345,6 +367,36 @@ describe('AWSXRayPropagator', () => { }); }); + it('should extract lineage into baggage', () => { + carrier[AWSXRAY_TRACE_ID_HEADER] = + 'Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad8;Sampled=1;Lineage=100:e3b0c442:11'; + const extractedContext = xrayPropagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter) + + assert.deepStrictEqual(propagation.getBaggage(extractedContext)?.getEntry('Lineage'), { + "value": LINEAGE_ID + }); + }); + + const invalidLineageHeaders = [ + "", + "::", + "1::", + "1::1", + "1:badc0de:13", + ":fbadc0de:13", + "65535:fbadc0de:255" + ]; + + invalidLineageHeaders.forEach((lineageHeader) => { + it(`should ignore invalid lineage header: ${lineageHeader}`, () => { + carrier[AWSXRAY_TRACE_ID_HEADER] = + `Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad8;Sampled=1;Lineage=${lineageHeader}`; + const extractedContext = xrayPropagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter) + + assert.deepStrictEqual(propagation.getBaggage(extractedContext), undefined); + }); + }); + describe('.fields()', () => { it('should return a field with AWS X-Ray Trace ID header', () => { const expectedField = xrayPropagator.fields(); From 5f8c69ddc2baeac6eef9ace44d4dfc5a9dd5ba9b Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Fri, 24 Jan 2025 15:04:05 -0800 Subject: [PATCH 4/5] lint fixes --- .../src/AWSXRayPropagator.ts | 54 ++++++++++------- .../test/AWSXRayPropagator.test.ts | 60 ++++++++++++------- 2 files changed, 70 insertions(+), 44 deletions(-) diff --git a/propagators/propagator-aws-xray/src/AWSXRayPropagator.ts b/propagators/propagator-aws-xray/src/AWSXRayPropagator.ts index f2f3983568..2824358c05 100644 --- a/propagators/propagator-aws-xray/src/AWSXRayPropagator.ts +++ b/propagators/propagator-aws-xray/src/AWSXRayPropagator.ts @@ -50,8 +50,8 @@ const SAMPLED_FLAG_KEY = 'Sampled'; const IS_SAMPLED = '1'; const NOT_SAMPLED = '0'; -const LINEAGE_KEY = "Lineage"; -const LINEAGE_DELIMITER = ":"; +const LINEAGE_KEY = 'Lineage'; +const LINEAGE_DELIMITER = ':'; const LINEAGE_HASH_LENGTH = 8; const LINEAGE_MAX_REQUEST_COUNTER = 255; const LINEAGE_MAX_LOOP_COUNTER = 32767; @@ -82,24 +82,24 @@ export class AWSXRayPropagator implements TextMapPropagator { : NOT_SAMPLED; // TODO: Add OT trace state to the X-Ray trace header - let traceHeader = - `${TRACE_ID_KEY}` + - `${KV_DELIMITER}` + - `${xrayTraceId}` + - `${TRACE_HEADER_DELIMITER}` + - `${PARENT_ID_KEY}` + - `${KV_DELIMITER}` + - `${parentId}` + - `${TRACE_HEADER_DELIMITER}` + - `${SAMPLED_FLAG_KEY}` + - `${KV_DELIMITER}` + - `${samplingFlag}`; + let traceHeader = + `${TRACE_ID_KEY}` + + `${KV_DELIMITER}` + + `${xrayTraceId}` + + `${TRACE_HEADER_DELIMITER}` + + `${PARENT_ID_KEY}` + + `${KV_DELIMITER}` + + `${parentId}` + + `${TRACE_HEADER_DELIMITER}` + + `${SAMPLED_FLAG_KEY}` + + `${KV_DELIMITER}` + + `${samplingFlag}`; const baggage = propagation.getBaggage(context); const lineageV2Header = baggage?.getEntry(LINEAGE_KEY)?.value; if (lineageV2Header) { - traceHeader += + traceHeader += `${TRACE_HEADER_DELIMITER}` + `${LINEAGE_KEY}` + `${KV_DELIMITER}` + @@ -138,7 +138,8 @@ export class AWSXRayPropagator implements TextMapPropagator { return context; } - let baggage: Baggage = propagation.getBaggage(context) || propagation.createBaggage(); + let baggage: Baggage = + propagation.getBaggage(context) || propagation.createBaggage(); let pos = 0; let trimmedPart: string; @@ -167,7 +168,7 @@ export class AWSXRayPropagator implements TextMapPropagator { parsedTraceFlags = AWSXRayPropagator._parseTraceFlag(value); } else if (trimmedPart.startsWith(LINEAGE_KEY)) { if (AWSXRayPropagator._isValidLineageV2Header(value)) { - baggage = baggage.setEntry(LINEAGE_KEY, {value}); + baggage = baggage.setEntry(LINEAGE_KEY, { value }); } } } @@ -181,7 +182,10 @@ export class AWSXRayPropagator implements TextMapPropagator { isRemote: true, }; if (isSpanContextValid(resultSpanContext)) { - context = trace.setSpan(context, trace.wrapSpanContext(resultSpanContext)); + context = trace.setSpan( + context, + trace.wrapSpanContext(resultSpanContext) + ); } if (baggage.getAllEntries().length > 0) { context = propagation.setBaggage(context, baggage); @@ -233,17 +237,21 @@ export class AWSXRayPropagator implements TextMapPropagator { private static _isValidLineageV2Header(xrayLineageHeader: string): boolean { const lineageSubstrings = xrayLineageHeader.split(LINEAGE_DELIMITER); - if (lineageSubstrings.length != 3) { + if (lineageSubstrings.length !== 3) { return false; } const requestCounter = parseInt(lineageSubstrings[0]); const hashedResourceId = lineageSubstrings[1]; const loopCounter = parseInt(lineageSubstrings[2]); - - const isValidKey = hashedResourceId.length == LINEAGE_HASH_LENGTH && !!hashedResourceId.match(/^[0-9a-fA-F]+$/); - const isValidRequestCounter = requestCounter >= 0 && requestCounter <= LINEAGE_MAX_REQUEST_COUNTER; - const isValidLoopCounter = loopCounter >= 0 && loopCounter <= LINEAGE_MAX_LOOP_COUNTER; + + const isValidKey = + hashedResourceId.length === LINEAGE_HASH_LENGTH && + !!hashedResourceId.match(/^[0-9a-fA-F]+$/); + const isValidRequestCounter = + requestCounter >= 0 && requestCounter <= LINEAGE_MAX_REQUEST_COUNTER; + const isValidLoopCounter = + loopCounter >= 0 && loopCounter <= LINEAGE_MAX_LOOP_COUNTER; return isValidKey && isValidRequestCounter && isValidLoopCounter; } diff --git a/propagators/propagator-aws-xray/test/AWSXRayPropagator.test.ts b/propagators/propagator-aws-xray/test/AWSXRayPropagator.test.ts index 7f04d73165..4e22585abb 100644 --- a/propagators/propagator-aws-xray/test/AWSXRayPropagator.test.ts +++ b/propagators/propagator-aws-xray/test/AWSXRayPropagator.test.ts @@ -121,7 +121,7 @@ describe('AWSXRayPropagator', () => { assert.deepStrictEqual(carrier, {}); }); - + it('should inject with lineage', () => { const spanContext: SpanContext = { traceId: TRACE_ID, @@ -129,9 +129,12 @@ describe('AWSXRayPropagator', () => { traceFlags: SAMPLED_TRACE_FLAG, }; xrayPropagator.inject( - propagation.setBaggage(trace.setSpan(ROOT_CONTEXT, trace.wrapSpanContext(spanContext)), propagation.createBaggage({ - 'Lineage': { value: LINEAGE_ID } - })), + propagation.setBaggage( + trace.setSpan(ROOT_CONTEXT, trace.wrapSpanContext(spanContext)), + propagation.createBaggage({ + Lineage: { value: LINEAGE_ID }, + }) + ), carrier, defaultTextMapSetter ); @@ -370,30 +373,45 @@ describe('AWSXRayPropagator', () => { it('should extract lineage into baggage', () => { carrier[AWSXRAY_TRACE_ID_HEADER] = 'Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad8;Sampled=1;Lineage=100:e3b0c442:11'; - const extractedContext = xrayPropagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter) + const extractedContext = xrayPropagator.extract( + ROOT_CONTEXT, + carrier, + defaultTextMapGetter + ); - assert.deepStrictEqual(propagation.getBaggage(extractedContext)?.getEntry('Lineage'), { - "value": LINEAGE_ID - }); + assert.deepStrictEqual( + propagation.getBaggage(extractedContext)?.getEntry('Lineage'), + { + value: LINEAGE_ID, + } + ); }); const invalidLineageHeaders = [ - "", - "::", - "1::", - "1::1", - "1:badc0de:13", - ":fbadc0de:13", - "65535:fbadc0de:255" + '', + '::', + '1::', + '1::1', + '1:badc0de:13', + ':fbadc0de:13', + '65535:fbadc0de:255', ]; - invalidLineageHeaders.forEach((lineageHeader) => { + invalidLineageHeaders.forEach(lineageHeader => { it(`should ignore invalid lineage header: ${lineageHeader}`, () => { - carrier[AWSXRAY_TRACE_ID_HEADER] = - `Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad8;Sampled=1;Lineage=${lineageHeader}`; - const extractedContext = xrayPropagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter) - - assert.deepStrictEqual(propagation.getBaggage(extractedContext), undefined); + carrier[ + AWSXRAY_TRACE_ID_HEADER + ] = `Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad8;Sampled=1;Lineage=${lineageHeader}`; + const extractedContext = xrayPropagator.extract( + ROOT_CONTEXT, + carrier, + defaultTextMapGetter + ); + + assert.deepStrictEqual( + propagation.getBaggage(extractedContext), + undefined + ); }); }); From bfda7849acab1c02fd11e3ce5897ce7a2834808f Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Thu, 30 Jan 2025 14:12:24 -0800 Subject: [PATCH 5/5] change lineage variable names --- .../src/AWSXRayPropagator.ts | 26 +++++++++---------- .../test/AWSXRayPropagator.test.ts | 3 ++- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/propagators/propagator-aws-xray/src/AWSXRayPropagator.ts b/propagators/propagator-aws-xray/src/AWSXRayPropagator.ts index 2824358c05..b05070710f 100644 --- a/propagators/propagator-aws-xray/src/AWSXRayPropagator.ts +++ b/propagators/propagator-aws-xray/src/AWSXRayPropagator.ts @@ -53,8 +53,8 @@ const NOT_SAMPLED = '0'; const LINEAGE_KEY = 'Lineage'; const LINEAGE_DELIMITER = ':'; const LINEAGE_HASH_LENGTH = 8; -const LINEAGE_MAX_REQUEST_COUNTER = 255; -const LINEAGE_MAX_LOOP_COUNTER = 32767; +const LINEAGE_MAX_COUNTER_1 = 32767; +const LINEAGE_MAX_COUNTER_2 = 255; /** * Implementation of the AWS X-Ray Trace Header propagation protocol. See = 0 && requestCounter <= LINEAGE_MAX_REQUEST_COUNTER; - const isValidLoopCounter = - loopCounter >= 0 && loopCounter <= LINEAGE_MAX_LOOP_COUNTER; + const isValidHash = + hashedString.length === LINEAGE_HASH_LENGTH && + !!hashedString.match(/^[0-9a-fA-F]+$/); + const isValidCounter1 = + lineageCounter1 >= 0 && lineageCounter1 <= LINEAGE_MAX_COUNTER_1; + const isValidCounter2 = + lineageCounter2 >= 0 && lineageCounter2 <= LINEAGE_MAX_COUNTER_2; - return isValidKey && isValidRequestCounter && isValidLoopCounter; + return isValidHash && isValidCounter1 && isValidCounter2; } private static _parseTraceFlag(xraySampledFlag: string): TraceFlags | null { diff --git a/propagators/propagator-aws-xray/test/AWSXRayPropagator.test.ts b/propagators/propagator-aws-xray/test/AWSXRayPropagator.test.ts index 4e22585abb..68ffc792b2 100644 --- a/propagators/propagator-aws-xray/test/AWSXRayPropagator.test.ts +++ b/propagators/propagator-aws-xray/test/AWSXRayPropagator.test.ts @@ -394,7 +394,8 @@ describe('AWSXRayPropagator', () => { '1::1', '1:badc0de:13', ':fbadc0de:13', - '65535:fbadc0de:255', + '32768:fbadc0de:1', + '1:fbadc0de:256', ]; invalidLineageHeaders.forEach(lineageHeader => {