Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/OpenTelemetry.Extensions.AWS/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand Down
57 changes: 55 additions & 2 deletions src/OpenTelemetry.Extensions.AWS/Trace/AWSXRayPropagator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

/// <inheritdoc/>
public override ISet<string> Fields => new HashSet<string>() { AWSXRayTraceHeaderKey };

Expand Down Expand Up @@ -71,7 +78,7 @@ public override PropagationContext Extract<T>(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)
{
Expand Down Expand Up @@ -143,10 +150,21 @@ public override void Inject<T>(PropagationContext context, T carrier, Action<T,
sb.Append(KeyValueDelimiter);
sb.Append((context.ActivityContext.TraceFlags & ActivityTraceFlags.Recorded) != 0 ? SampledValue : NotSampledValue);

Baggage baggage = context.Baggage;
string? lineageV2Header = baggage.GetBaggage(LineageKey);

if (lineageV2Header != null)
{
sb.Append(TraceHeaderDelimiter);
sb.Append(LineageKey);
sb.Append(KeyValueDelimiter);
sb.Append(lineageV2Header);
}

setter(carrier, AWSXRayTraceHeaderKey, sb.ToString());
}

internal static bool TryParseXRayTraceHeader(string rawHeader, out ActivityContext activityContext)
internal static bool TryParseXRayTraceHeader(PropagationContext context, string rawHeader, out ActivityContext activityContext, out Baggage baggage)
{
// from https://docs.aws.amazon.com/xray/latest/devguide/xray-concepts.html#xray-concepts-tracingheader
// rawHeader format: Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1
Expand All @@ -155,6 +173,7 @@ internal static bool TryParseXRayTraceHeader(string rawHeader, out ActivityConte
ReadOnlySpan<char> traceId = default;
ReadOnlySpan<char> parentId = default;
char traceOptions = default;
baggage = context.Baggage;

if (string.IsNullOrEmpty(rawHeader))
{
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -306,6 +332,33 @@ internal static bool TryParseSampleDecision(ReadOnlySpan<char> 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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [];

Expand Down Expand Up @@ -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<string, string>();
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<string, string> { { "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()
{
Expand Down Expand Up @@ -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<string, string>()
{
{ 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<string, string> { { "Lineage", LineageId } });

Assert.Equal(new PropagationContext(activityContext, baggage), this.awsXRayPropagator.Extract(default, carrier, Getter));
}

[Fact]
public void TestExtractTraceHeaderWithInvalidLineage()
{
var invalidLineages = new List<string>
{
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<string, string>()
{
{ 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()
{
Expand Down