55
66package io .opentelemetry .contrib .awsxray .propagator ;
77
8+ import static io .opentelemetry .api .internal .OtelEncodingUtils .isValidBase16String ;
9+
810import io .opentelemetry .api .baggage .Baggage ;
911import io .opentelemetry .api .baggage .BaggageBuilder ;
10- import io .opentelemetry .api .baggage .BaggageEntry ;
1112import io .opentelemetry .api .internal .StringUtils ;
1213import io .opentelemetry .api .trace .Span ;
1314import io .opentelemetry .api .trace .SpanContext ;
2122import io .opentelemetry .context .propagation .TextMapSetter ;
2223import java .util .Collections ;
2324import java .util .List ;
24- import java .util .function . BiConsumer ;
25+ import java .util .Set ;
2526import java .util .logging .Logger ;
2627import javax .annotation .Nullable ;
2728
@@ -68,6 +69,15 @@ public final class AwsXrayPropagator implements TextMapPropagator {
6869 private static final char IS_SAMPLED = '1' ;
6970 private static final char NOT_SAMPLED = '0' ;
7071
72+ private static final String LINEAGE_KEY = "Lineage" ;
73+ private static final char LINEAGE_DELIMITER = ':' ;
74+ private static final int LINEAGE_MAX_LENGTH = 18 ;
75+ private static final int LINEAGE_MIN_LENGTH = 12 ;
76+ private static final int LINEAGE_HASH_LENGTH = 8 ;
77+ private static final int LINEAGE_MAX_LOOP_COUNTER = 32767 ;
78+ private static final int LINEAGE_MAX_REQUEST_COUNTER = 255 ;
79+ private static final int LINEAGE_MIN_COUNTER = 0 ;
80+
7181 private static final List <String > FIELDS = Collections .singletonList (TRACE_HEADER_KEY );
7282
7383 private static final AwsXrayPropagator INSTANCE = new AwsXrayPropagator ();
@@ -127,34 +137,19 @@ public <C> void inject(Context context, @Nullable C carrier, TextMapSetter<C> se
127137 .append (samplingFlag );
128138
129139 Baggage baggage = Baggage .fromContext (context );
130- // Truncate baggage to 256 chars per X-Ray spec.
131- baggage .forEach (
132- new BiConsumer <String , BaggageEntry >() {
133-
134- private int baggageWrittenBytes ;
135-
136- @ Override
137- public void accept (String key , BaggageEntry entry ) {
138- if (key .equals (TRACE_ID_KEY )
139- || key .equals (PARENT_ID_KEY )
140- || key .equals (SAMPLED_FLAG_KEY )) {
141- return ;
142- }
143- // Size is key/value pair, excludes delimiter.
144- int size = key .length () + entry .getValue ().length () + 1 ;
145- if (baggageWrittenBytes + size > 256 ) {
146- return ;
147- }
148- traceHeader
149- .append (TRACE_HEADER_DELIMITER )
150- .append (key )
151- .append (KV_DELIMITER )
152- .append (entry .getValue ());
153- baggageWrittenBytes += size ;
154- }
155- });
156-
157- setter .set (carrier , TRACE_HEADER_KEY , traceHeader .toString ());
140+ String lineageV2Header = baggage .getEntryValue (LINEAGE_KEY );
141+
142+ if (lineageV2Header != null ) {
143+ traceHeader
144+ .append (TRACE_HEADER_DELIMITER )
145+ .append (LINEAGE_KEY )
146+ .append (KV_DELIMITER )
147+ .append (lineageV2Header );
148+ }
149+
150+ // add 256 character truncation
151+ String truncatedTraceHeader = traceHeader .substring (0 , Math .min (traceHeader .length (), 256 ));
152+ setter .set (carrier , TRACE_HEADER_KEY , truncatedTraceHeader );
158153 }
159154
160155 @ Override
@@ -183,10 +178,20 @@ private static <C> Context getContextFromHeader(
183178
184179 String traceId = TraceId .getInvalid ();
185180 String spanId = SpanId .getInvalid ();
181+ String lineageV2Header ;
186182 Boolean isSampled = false ;
187183
188- BaggageBuilder baggage = null ;
189- int baggageReadBytes = 0 ;
184+ Baggage contextBaggage = Baggage .fromContext (context );
185+ BaggageBuilder baggageBuilder = Baggage .builder ();
186+ Set <String > baggageMap = contextBaggage .asMap ().keySet ();
187+
188+ // Copying baggage over to new Baggage object to add Lineage key
189+ for (String baggageKey : baggageMap ) {
190+ String baggageValue = contextBaggage .getEntryValue (baggageKey );
191+ if (baggageValue != null ) {
192+ baggageBuilder .put (baggageKey , baggageValue );
193+ }
194+ }
190195
191196 int pos = 0 ;
192197 while (pos < traceHeader .length ()) {
@@ -215,12 +220,11 @@ private static <C> Context getContextFromHeader(
215220 spanId = parseSpanId (value );
216221 } else if (trimmedPart .startsWith (SAMPLED_FLAG_KEY )) {
217222 isSampled = parseTraceFlag (value );
218- } else if (baggageReadBytes + trimmedPart .length () <= 256 ) {
219- if (baggage == null ) {
220- baggage = Baggage .builder ();
223+ } else if (trimmedPart .startsWith (LINEAGE_KEY )) {
224+ lineageV2Header = parseLineageV2Header (value );
225+ if (isValidLineage (lineageV2Header )) {
226+ baggageBuilder .put (LINEAGE_KEY , lineageV2Header );
221227 }
222- baggage .put (trimmedPart .substring (0 , equalsIndex ), value );
223- baggageReadBytes += trimmedPart .length ();
224228 }
225229 }
226230 if (isSampled == null ) {
@@ -243,12 +247,17 @@ private static <C> Context getContextFromHeader(
243247 spanId ,
244248 isSampled ? TraceFlags .getSampled () : TraceFlags .getDefault (),
245249 TraceState .getDefault ());
250+
246251 if (spanContext .isValid ()) {
247252 context = context .with (Span .wrap (spanContext ));
248253 }
249- if (baggage != null ) {
250- context = context .with (baggage .build ());
254+
255+ Baggage baggage = baggageBuilder .build ();
256+
257+ if (!baggage .isEmpty ()) {
258+ context = context .with (baggage );
251259 }
260+
252261 return context ;
253262 }
254263
@@ -316,6 +325,37 @@ private static String parseSpanId(String xrayParentId) {
316325 return xrayParentId ;
317326 }
318327
328+ private static String parseLineageV2Header (String xrayLineageHeader ) {
329+ long numOfDelimiters = xrayLineageHeader .chars ().filter (ch -> ch == LINEAGE_DELIMITER ).count ();
330+
331+ if (xrayLineageHeader .length () < LINEAGE_MIN_LENGTH
332+ || xrayLineageHeader .length () > LINEAGE_MAX_LENGTH
333+ || numOfDelimiters != 2 ) {
334+ return AwsXrayPropagator .getInvalidLineageV2Header ();
335+ }
336+
337+ return xrayLineageHeader ;
338+ }
339+
340+ private static boolean isValidLineage (String key ) {
341+ String [] split = key .split (":" );
342+ String hash = split [1 ];
343+ int loopCounter = parseIntOrReturnNegative (split [0 ]);
344+ int requestCounter = parseIntOrReturnNegative (split [2 ]);
345+
346+ boolean isHashValid = hash .length () == LINEAGE_HASH_LENGTH && isValidBase16String (hash );
347+ boolean isValidRequestCounter =
348+ requestCounter <= LINEAGE_MAX_REQUEST_COUNTER && requestCounter >= LINEAGE_MIN_COUNTER ;
349+ boolean isValidLoopCounter =
350+ loopCounter <= LINEAGE_MAX_LOOP_COUNTER && loopCounter >= LINEAGE_MIN_COUNTER ;
351+
352+ return isHashValid && isValidRequestCounter && isValidLoopCounter ;
353+ }
354+
355+ private static String getInvalidLineageV2Header () {
356+ return "-1:11111111:0" ;
357+ }
358+
319359 @ Nullable
320360 private static Boolean parseTraceFlag (String xraySampledFlag ) {
321361 if (xraySampledFlag .length () != SAMPLED_FLAG_LENGTH ) {
@@ -332,4 +372,12 @@ private static Boolean parseTraceFlag(String xraySampledFlag) {
332372 return null ;
333373 }
334374 }
375+
376+ private static int parseIntOrReturnNegative (String num ) {
377+ try {
378+ return Integer .parseInt (num );
379+ } catch (NumberFormatException e ) {
380+ return -1 ;
381+ }
382+ }
335383}
0 commit comments