diff --git a/src/OpenTelemetry.Extensions.AWS/CHANGELOG.md b/src/OpenTelemetry.Extensions.AWS/CHANGELOG.md
index b5bbdab22c..650d066b9e 100644
--- a/src/OpenTelemetry.Extensions.AWS/CHANGELOG.md
+++ b/src/OpenTelemetry.Extensions.AWS/CHANGELOG.md
@@ -2,6 +2,8 @@
## Unreleased
+* Add support for Lambda lineage in X-Ray propagator.
+ ([#2480](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/2480))
* Updated OpenTelemetry core component version(s) to `1.11.1`.
([#2477](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/2477))
diff --git a/src/OpenTelemetry.Extensions.AWS/Trace/AWSXRayPropagator.cs b/src/OpenTelemetry.Extensions.AWS/Trace/AWSXRayPropagator.cs
index 2a8d71b6f2..1822a2768c 100644
--- a/src/OpenTelemetry.Extensions.AWS/Trace/AWSXRayPropagator.cs
+++ b/src/OpenTelemetry.Extensions.AWS/Trace/AWSXRayPropagator.cs
@@ -8,6 +8,7 @@
using System.Globalization;
using System.Numerics;
using System.Text;
+using System.Text.RegularExpressions;
using OpenTelemetry.Context.Propagation;
namespace OpenTelemetry.Extensions.AWS.Trace;
@@ -37,6 +38,12 @@ public class AWSXRayPropagator : TextMapPropagator
private const char SampledValue = '1';
private const char NotSampledValue = '0';
+ private const string LineageKey = "Lineage";
+ private const char LineageDelimiter = ':';
+ private const int LineageHashLength = 8;
+ private const int LineageMaxCounter1 = 32767;
+ private const int LineageMaxCounter2 = 255;
+
///
public override ISet Fields => new HashSet() { AWSXRayTraceHeaderKey };
@@ -71,7 +78,7 @@ public override PropagationContext Extract(PropagationContext context, T carr
var parentHeader = parentTraceHeader.First();
- return !TryParseXRayTraceHeader(parentHeader, out var newActivityContext) ? context : new PropagationContext(newActivityContext, context.Baggage);
+ return !TryParseXRayTraceHeader(context, parentHeader, out var newActivityContext, out var baggage) ? context : new PropagationContext(newActivityContext, baggage);
}
catch (Exception ex)
{
@@ -143,10 +150,21 @@ public override void Inject(PropagationContext context, T carrier, Action traceId = default;
ReadOnlySpan parentId = default;
char traceOptions = default;
+ baggage = context.Baggage;
if (string.IsNullOrEmpty(rawHeader))
{
@@ -212,6 +231,13 @@ internal static bool TryParseXRayTraceHeader(string rawHeader, out ActivityConte
traceOptions = sampleDecision;
}
+ else if (trimmedPart.StartsWith(LineageKey.AsSpan()))
+ {
+ if (IsLineageIdValid(value.ToString()))
+ {
+ baggage = baggage.SetBaggage(LineageKey, value.ToString());
+ }
+ }
}
if (traceId.IsEmpty || parentId.IsEmpty || traceOptions == default)
@@ -306,6 +332,33 @@ internal static bool TryParseSampleDecision(ReadOnlySpan sampleDecision, o
return true;
}
+ internal static bool IsLineageIdValid(string lineageId)
+ {
+ var lineageSubstrings = lineageId.Split(LineageDelimiter);
+ if (lineageSubstrings.Length != 3)
+ {
+ return false;
+ }
+
+ if (!int.TryParse(lineageSubstrings[0], out var lineageCounter1))
+ {
+ return false;
+ }
+
+ var hashedString = lineageSubstrings[1];
+
+ if (!int.TryParse(lineageSubstrings[2], out var lineageCounter2))
+ {
+ return false;
+ }
+
+ var isValidHash = hashedString.Length == LineageHashLength && Regex.IsMatch(hashedString, "^[0-9a-fA-F]+$");
+ var isValidCounter1 = lineageCounter1 >= 0 && lineageCounter1 <= LineageMaxCounter1;
+ var isValidCounter2 = lineageCounter2 >= 0 && lineageCounter2 <= LineageMaxCounter2;
+
+ return isValidHash && isValidCounter1 && isValidCounter2;
+ }
+
internal static string ToXRayTraceIdFormat(string traceId)
{
var sb = new StringBuilder();
diff --git a/test/OpenTelemetry.Extensions.AWS.Tests/Trace/AWSXRayPropagatorTests.cs b/test/OpenTelemetry.Extensions.AWS.Tests/Trace/AWSXRayPropagatorTests.cs
index cd036b27ba..70ad46ff90 100644
--- a/test/OpenTelemetry.Extensions.AWS.Tests/Trace/AWSXRayPropagatorTests.cs
+++ b/test/OpenTelemetry.Extensions.AWS.Tests/Trace/AWSXRayPropagatorTests.cs
@@ -16,6 +16,7 @@ public class AWSXRayPropagatorTests
private const string AWSXRayTraceHeaderKey = "X-Amzn-Trace-Id";
private const string TraceId = "5759e988bd862e3fe1be46a994272793";
private const string ParentId = "53995c3f42cd8ad8";
+ private const string LineageId = "100:e3b0c442:11";
private static readonly string[] Empty = [];
@@ -114,6 +115,21 @@ public void TestInjectTraceHeaderAlreadyExistsButNotHttpRequestMessage()
Assert.Equal("Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=0", carrier[AWSXRayTraceHeaderKey]);
}
+ [Fact]
+ public void TestInjectTraceHeaderWithLineage()
+ {
+ var carrier = new Dictionary();
+ var traceId = ActivityTraceId.CreateFromString(TraceId.AsSpan());
+ var parentId = ActivitySpanId.CreateFromString(ParentId.AsSpan());
+ var traceFlags = ActivityTraceFlags.Recorded;
+ var activityContext = new ActivityContext(traceId, parentId, traceFlags);
+ var baggage = Baggage.Create(new Dictionary { { "Lineage", LineageId } });
+ this.awsXRayPropagator.Inject(new PropagationContext(activityContext, baggage), carrier, Setter);
+
+ Assert.True(carrier.ContainsKey(AWSXRayTraceHeaderKey));
+ Assert.Equal("Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1;Lineage=100:e3b0c442:11", carrier[AWSXRayTraceHeaderKey]);
+ }
+
[Fact]
public void TestExtractTraceHeader()
{
@@ -159,6 +175,53 @@ public void TestExtractTraceHeaderDifferentOrder()
Assert.Equal(new PropagationContext(activityContext, default), this.awsXRayPropagator.Extract(default, carrier, Getter));
}
+ [Fact]
+ public void TestExtractTraceHeaderWithLineage()
+ {
+ var carrier = new Dictionary()
+ {
+ { AWSXRayTraceHeaderKey, "Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1;Lineage=100:e3b0c442:11" },
+ };
+ var traceId = ActivityTraceId.CreateFromString(TraceId.AsSpan());
+ var parentId = ActivitySpanId.CreateFromString(ParentId.AsSpan());
+ var traceFlags = ActivityTraceFlags.Recorded;
+ var activityContext = new ActivityContext(traceId, parentId, traceFlags, isRemote: true);
+ var baggage = Baggage.Create(new Dictionary { { "Lineage", LineageId } });
+
+ Assert.Equal(new PropagationContext(activityContext, baggage), this.awsXRayPropagator.Extract(default, carrier, Getter));
+ }
+
+ [Fact]
+ public void TestExtractTraceHeaderWithInvalidLineage()
+ {
+ var invalidLineages = new List
+ {
+ string.Empty,
+ "::",
+ "1::",
+ "1::1",
+ "1:badc0de:13",
+ ":fbadc0de:13",
+ "32768:fbadc0de:1",
+ "1:fbadc0de:256",
+ };
+
+ foreach (var invalidLineage in invalidLineages)
+ {
+ var carrier = new Dictionary()
+ {
+ { AWSXRayTraceHeaderKey, $"Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1;Lineage={invalidLineage}" },
+ };
+
+ var traceId = ActivityTraceId.CreateFromString(TraceId.AsSpan());
+ var parentId = ActivitySpanId.CreateFromString(ParentId.AsSpan());
+ var traceFlags = ActivityTraceFlags.Recorded;
+ var activityContext = new ActivityContext(traceId, parentId, traceFlags, isRemote: true);
+
+ Assert.Equal(new PropagationContext(activityContext, default), this.awsXRayPropagator.Extract(default, carrier, Getter));
+ }
+ }
+
[Fact]
public void TestExtractTraceHeaderWithEmptyValue()
{