Skip to content
Merged
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 OpenTelemetry.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@
<Project Path="src/OpenTelemetry/OpenTelemetry.csproj" />
<Project Path="test/Benchmarks/Benchmarks.csproj" />
<Project Path="test/OpenTelemetry.AotCompatibility.TestApp/OpenTelemetry.AotCompatibility.TestApp.csproj" />
<Project Path="test/OpenTelemetry.Api.FuzzTests/OpenTelemetry.Api.FuzzTests.csproj" />
<Project Path="test/OpenTelemetry.Api.ProviderBuilderExtensions.Tests/OpenTelemetry.Api.ProviderBuilderExtensions.Tests.csproj" />
<Project Path="test/OpenTelemetry.Api.Tests/OpenTelemetry.Api.Tests.csproj" />
<Project Path="test/OpenTelemetry.Exporter.Console.Tests/OpenTelemetry.Exporter.Console.Tests.csproj" />
Expand All @@ -228,6 +229,7 @@
<Project Path="test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests.csproj" />
<Project Path="test/OpenTelemetry.Exporter.Zipkin.Tests/OpenTelemetry.Exporter.Zipkin.Tests.csproj" />
<Project Path="test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetry.Extensions.Hosting.Tests.csproj" />
<Project Path="test/OpenTelemetry.Extensions.Propagators.FuzzTests/OpenTelemetry.Extensions.Propagators.FuzzTests.csproj" />
<Project Path="test/OpenTelemetry.Extensions.Propagators.Tests/OpenTelemetry.Extensions.Propagators.Tests.csproj" />
<Project Path="test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/OpenTelemetry.Instrumentation.W3cTraceContext.Tests.csproj" />
<Project Path="test/OpenTelemetry.Shims.OpenTracing.Tests/OpenTelemetry.Shims.OpenTracing.Tests.csproj" />
Expand Down
6 changes: 6 additions & 0 deletions src/OpenTelemetry.Api/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ Notes](../../RELEASENOTES.md).

## Unreleased

* Fix baggage and trace headers not respecting the maximum length in some cases.
([#7061](https://github.com/open-telemetry/opentelemetry-dotnet/pull/7061))

* Improve efficiency of parsing of baggage and B3 propagation headers.
([#7061](https://github.com/open-telemetry/opentelemetry-dotnet/pull/7061))

## 1.15.2

Released 2026-Apr-08
Expand Down
132 changes: 89 additions & 43 deletions src/OpenTelemetry.Api/Context/Propagation/B3Propagator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -195,65 +195,111 @@ private static PropagationContext ExtractFromSingleHeader<T>(PropagationContext
{
try
{
var header = getter(carrier, XB3Combined)?.FirstOrDefault();
if (string.IsNullOrWhiteSpace(header))
var headers = getter(carrier, XB3Combined);
if (headers == null)
{
return context;
}

var parts =
#if NET
header.Split(XB3CombinedDelimiter);
#else
header!.Split(XB3CombinedDelimiter);
#endif
var header = headers.FirstOrDefault();

if (parts.Length is < 2 or > 4)
{
return context;
}
return string.IsNullOrWhiteSpace(header)
? context
: !TryExtractSingleHeaderContext(header, out var traceId, out var spanId, out var traceOptions)
? context
: new PropagationContext(
new ActivityContext(traceId, spanId, traceOptions, isRemote: true),
context.Baggage);
}
catch (Exception e)
{
OpenTelemetryApiEventSource.Log.ActivityContextExtractException(nameof(B3Propagator), e);
return context;
}
}

var traceIdStr = parts[0];
if (string.IsNullOrWhiteSpace(traceIdStr))
{
return context;
}
private static bool TryExtractSingleHeaderContext(
string header,
out ActivityTraceId traceId,
out ActivitySpanId spanId,
out ActivityTraceFlags traceOptions)
{
traceId = default;
spanId = default;
traceOptions = ActivityTraceFlags.None;

var headerValue = header.AsSpan();
var position = 0;
var traceIdStr = ReadNextPart(headerValue, position, out position);
if (position >= headerValue.Length || traceIdStr.IsEmpty)
{
return false;
}

var spanIdStr = ReadNextPart(headerValue, position, out position);
if (spanIdStr.IsEmpty)
{
return false;
}

if (traceIdStr.Length == 16)
ReadOnlySpan<char> traceFlagsStr = default;
if (position < headerValue.Length)
{
traceFlagsStr = ReadNextPart(headerValue, position, out position);
if (position < headerValue.Length)
{
// This is an 8-byte traceID.
traceIdStr = UpperTraceId + traceIdStr;
_ = ReadNextPart(headerValue, position, out position);
if (position < headerValue.Length)
{
return false;
}
}
}

var traceId = ActivityTraceId.CreateFromString(traceIdStr.AsSpan());
traceId = CreateTraceId(traceIdStr);
spanId = ActivitySpanId.CreateFromString(spanIdStr);

var spanIdStr = parts[1];
if (string.IsNullOrWhiteSpace(spanIdStr))
{
return context;
}
if (IsSampledValue(traceFlagsStr) ||
traceFlagsStr.Equals(FlagsValue.AsSpan(), StringComparison.Ordinal))
{
traceOptions |= ActivityTraceFlags.Recorded;
}

var spanId = ActivitySpanId.CreateFromString(spanIdStr.AsSpan());
return true;
}

var traceOptions = ActivityTraceFlags.None;
if (parts.Length > 2)
{
var traceFlagsStr = parts[2];
if (SampledValues.Contains(traceFlagsStr)
|| FlagsValue.Equals(traceFlagsStr, StringComparison.Ordinal))
{
traceOptions |= ActivityTraceFlags.Recorded;
}
}
private static bool IsSampledValue(ReadOnlySpan<char> value) =>
value.Equals(SampledValue.AsSpan(), StringComparison.Ordinal) ||
value.Equals(LegacySampledValue.AsSpan(), StringComparison.Ordinal);

return new PropagationContext(
new ActivityContext(traceId, spanId, traceOptions, isRemote: true),
context.Baggage);
private static ActivityTraceId CreateTraceId(ReadOnlySpan<char> traceId)
{
if (traceId.Length == 16)
{
Span<char> fullTraceId = stackalloc char[UpperTraceId.Length + 16];

UpperTraceId.AsSpan().CopyTo(fullTraceId);
traceId.CopyTo(fullTraceId.Slice(UpperTraceId.Length));

return ActivityTraceId.CreateFromString(fullTraceId);
}
catch (Exception e)

return ActivityTraceId.CreateFromString(traceId);
}

private static ReadOnlySpan<char> ReadNextPart(ReadOnlySpan<char> header, int position, out int nextPosition)
{
var remaining = header.Slice(position);
var separatorIndex = remaining.IndexOf(XB3CombinedDelimiter);
if (separatorIndex < 0)
{
OpenTelemetryApiEventSource.Log.ActivityContextExtractException(nameof(B3Propagator), e);
return context;
nextPosition = header.Length;
var part = remaining;
return part;
}

var result = remaining.Slice(0, separatorIndex);
nextPosition = position + separatorIndex + 1;
return result;
}
}
92 changes: 67 additions & 25 deletions src/OpenTelemetry.Api/Context/Propagation/BaggagePropagator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
// SPDX-License-Identifier: Apache-2.0

#if NET
#if NET9_0_OR_GREATER
using System.Buffers;
#endif
using System.Diagnostics.CodeAnalysis;
#endif
using System.Net;
Expand All @@ -20,8 +23,9 @@ public class BaggagePropagator : TextMapPropagator
private const int MaxBaggageLength = 8192;
private const int MaxBaggageItems = 180;

private static readonly char[] EqualSignSeparator = ['='];
private static readonly char[] CommaSignSeparator = [','];
#if NET9_0_OR_GREATER
private static readonly SearchValues<char> DecodeHints = SearchValues.Create('%', '+');
#endif

/// <inheritdoc/>
public override ISet<string> Fields => new HashSet<string> { BaggageHeaderName };
Expand Down Expand Up @@ -50,9 +54,9 @@ public override PropagationContext Extract<T>(PropagationContext context, T carr
try
{
var baggageCollection = getter(carrier, BaggageHeaderName);
if (baggageCollection?.Any() ?? false)
if (baggageCollection is not null)
{
if (TryExtractBaggage([.. baggageCollection], out var baggageItems))
if (TryExtractBaggage(baggageCollection, out var baggageItems))
{
Baggage baggage =
#if NET
Expand Down Expand Up @@ -104,16 +108,40 @@ public override void Inject<T>(PropagationContext context, T carrier, Action<T,
continue;
}

baggage.Append(WebUtility.UrlEncode(item.Key)).Append('=').Append(WebUtility.UrlEncode(item.Value)).Append(',');
var encodedKey = WebUtility.UrlEncode(item.Key);
var encodedValue = WebUtility.UrlEncode(item.Value);
var baggageItemLength = encodedKey.Length + encodedValue.Length + 1;

if (baggage.Length > 0)
{
baggageItemLength++;
}

if (baggage.Length + baggageItemLength > MaxBaggageLength)
{
break;
}

if (baggage.Length > 0)
{
baggage.Append(',');
}

baggage.Append(encodedKey)
.Append('=')
.Append(encodedValue);
}
while (e.MoveNext() && ++itemCount < MaxBaggageItems);

if (baggage.Length > 0)
{
setter(carrier, BaggageHeaderName, baggage.ToString());
}
while (e.MoveNext() && ++itemCount < MaxBaggageItems && baggage.Length < MaxBaggageLength);
baggage.Remove(baggage.Length - 1, 1);
setter(carrier, BaggageHeaderName, baggage.ToString());
}
}

internal static bool TryExtractBaggage(
string[] baggageCollection,
IEnumerable<string> baggageCollection,
#if NET
[NotNullWhen(true)]
#endif
Expand All @@ -135,8 +163,10 @@ internal static bool TryExtractBaggage(
continue;
}

foreach (var pair in item.Split(CommaSignSeparator))
var remaining = item.AsSpan();
while (!remaining.IsEmpty)
{
var pair = ReadNextSegment(ref remaining, ',');
baggageLength += pair.Length + 1; // pair and comma

if (baggageLength >= MaxBaggageLength || baggageDictionary?.Count >= MaxBaggageItems)
Expand All @@ -145,36 +175,48 @@ internal static bool TryExtractBaggage(
break;
}

#if NET
if (pair.IndexOf('=', StringComparison.Ordinal) < 0)
#else
if (pair.IndexOf('=') < 0)
#endif
{
continue;
}

var parts = pair.Split(EqualSignSeparator, 2);
if (parts.Length != 2)
var separatorIndex = pair.IndexOf('=');
if (separatorIndex < 0)
{
continue;
}

var key = WebUtility.UrlDecode(parts[0]);
var value = WebUtility.UrlDecode(parts[1]);
var key = DecodeIfNeeded(pair.Slice(0, separatorIndex));
var value = DecodeIfNeeded(pair.Slice(separatorIndex + 1));

if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(value))
{
continue;
}

baggageDictionary ??= [];

baggageDictionary ??= new(StringComparer.Ordinal);
baggageDictionary[key] = value;
}
}

baggage = baggageDictionary;
return baggageDictionary != null;
}

private static ReadOnlySpan<char> ReadNextSegment(ref ReadOnlySpan<char> remaining, char separator)
{
var separatorIndex = remaining.IndexOf(separator);
if (separatorIndex < 0)
{
var segment = remaining;
remaining = [];
return segment;
}

var result = remaining.Slice(0, separatorIndex);
remaining = remaining.Slice(separatorIndex + 1);
return result;
}

private static string DecodeIfNeeded(ReadOnlySpan<char> value) =>
#if NET9_0_OR_GREATER
value.ContainsAny(DecodeHints) ? WebUtility.UrlDecode(value.ToString()) : value.ToString();
#else
value.IndexOfAny('%', '+') < 0 ? value.ToString() : WebUtility.UrlDecode(value.ToString());
#endif
}
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,15 @@ public override void Inject<T>(PropagationContext context, T carrier, Action<T,
var tracestateStr = context.ActivityContext.TraceState;
if (tracestateStr?.Length > 0)
{
setter(carrier, TraceState, tracestateStr);
var tracestateEntries = new List<KeyValuePair<string, string>>();
if (TraceStateUtils.AppendTraceState(tracestateStr, tracestateEntries))
{
var normalizedTraceState = TraceStateUtils.GetString(tracestateEntries);
if (normalizedTraceState.Length > 0)
{
setter(carrier, TraceState, normalizedTraceState);
}
}
}
}

Expand Down
Loading
Loading