Skip to content

Commit 75cafd5

Browse files
[OpenTelemetry] Address feedback
Address Copilot code review feedback.
1 parent 3a6d50d commit 75cafd5

2 files changed

Lines changed: 113 additions & 31 deletions

File tree

src/OpenTelemetry.Api/Context/Propagation/TraceContextPropagator.cs

Lines changed: 72 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,10 @@ public override PropagationContext Extract<T>(PropagationContext context, T carr
6969
}
7070

7171
string? tracestate = null;
72-
var tracestateCollection = getter(carrier, TraceState);
73-
if (tracestateCollection != null && HasAnyValues(tracestateCollection))
72+
TryExtractTracestate(getter(carrier, TraceState), out var extractedTracestate, out var hasTraceState);
73+
if (hasTraceState)
7474
{
75-
TryExtractTracestate(tracestateCollection, out tracestate);
75+
tracestate = extractedTracestate;
7676
}
7777

7878
return new PropagationContext(
@@ -216,23 +216,83 @@ internal static bool TryExtractTraceparent(string traceparent, out ActivityTrace
216216
return true;
217217
}
218218

219-
internal static bool TryExtractTracestate(string[] tracestateCollection, out string tracestateResult)
220-
=> TryExtractTracestate((IEnumerable<string>)tracestateCollection, out tracestateResult);
219+
internal static bool TryExtractTracestate(string[]? tracestateCollection, out string tracestateResult)
220+
=> TryExtractTracestate((IEnumerable<string>?)tracestateCollection, out tracestateResult);
221221

222-
internal static bool TryExtractTracestate(IEnumerable<string> tracestateCollection, out string tracestateResult)
222+
internal static bool TryExtractTracestate(IEnumerable<string>? tracestateCollection, out string tracestateResult)
223+
=> TryExtractTracestate(tracestateCollection, out tracestateResult, out _);
224+
225+
private static bool TryExtractTracestate(IEnumerable<string>? tracestateCollection, out string tracestateResult, out bool hasTraceState)
223226
{
224227
tracestateResult = string.Empty;
228+
hasTraceState = false;
225229

226230
if (tracestateCollection == null)
227231
{
228232
return true;
229233
}
230234

231-
if (TryGetSingleValue(tracestateCollection, out var singleTraceState))
235+
if (tracestateCollection is IList<string> list)
236+
{
237+
if (list.Count == 0)
238+
{
239+
return true;
240+
}
241+
242+
hasTraceState = true;
243+
if (list.Count == 1)
244+
{
245+
return TryExtractSingleTracestate(list[0], out tracestateResult);
246+
}
247+
248+
return TryExtractMultipleTracestate(list, out tracestateResult);
249+
}
250+
251+
if (tracestateCollection is IReadOnlyList<string> readOnlyList)
252+
{
253+
if (readOnlyList.Count == 0)
254+
{
255+
return true;
256+
}
257+
258+
hasTraceState = true;
259+
if (readOnlyList.Count == 1)
260+
{
261+
return TryExtractSingleTracestate(readOnlyList[0], out tracestateResult);
262+
}
263+
264+
return TryExtractMultipleTracestate(readOnlyList, out tracestateResult);
265+
}
266+
267+
using var enumerator = tracestateCollection.GetEnumerator();
268+
if (!enumerator.MoveNext())
269+
{
270+
return true;
271+
}
272+
273+
hasTraceState = true;
274+
var singleTraceState = enumerator.Current;
275+
if (!enumerator.MoveNext())
232276
{
233277
return TryExtractSingleTracestate(singleTraceState, out tracestateResult);
234278
}
235279

280+
return TryExtractMultipleTracestate(EnumerateFrom(singleTraceState, enumerator), out tracestateResult);
281+
}
282+
283+
private static IEnumerable<string> EnumerateFrom(string first, IEnumerator<string> enumerator)
284+
{
285+
yield return first;
286+
287+
do
288+
{
289+
yield return enumerator.Current;
290+
}
291+
while (enumerator.MoveNext());
292+
}
293+
294+
private static bool TryExtractMultipleTracestate(IEnumerable<string> tracestateCollection, out string tracestateResult)
295+
{
236296
var keySet = new HashSet<string>();
237297
var result = new StringBuilder();
238298

@@ -268,13 +328,15 @@ internal static bool TryExtractTracestate(IEnumerable<string> tracestateCollecti
268328
{
269329
// https://github.com/w3c/trace-context/blob/master/spec/20-http_request_header_format.md#list
270330
// test_tracestate_member_count_limit
331+
tracestateResult = string.Empty;
271332
return false;
272333
}
273334

274335
var keyLength = listMember.IndexOf('=');
275336
if (keyLength == listMember.Length || keyLength == -1)
276337
{
277338
// Missing key or value in tracestate
339+
tracestateResult = string.Empty;
278340
return false;
279341
}
280342

@@ -284,20 +346,23 @@ internal static bool TryExtractTracestate(IEnumerable<string> tracestateCollecti
284346
// test_tracestate_key_illegal_characters in https://github.com/w3c/trace-context/blob/master/test/test.py
285347
// test_tracestate_key_length_limit
286348
// test_tracestate_key_illegal_vendor_format
349+
tracestateResult = string.Empty;
287350
return false;
288351
}
289352

290353
var value = listMember.Slice(keyLength + 1);
291354
if (!ValidateValue(value))
292355
{
293356
// test_tracestate_value_illegal_characters
357+
tracestateResult = string.Empty;
294358
return false;
295359
}
296360

297361
// ValidateKey() call above has ensured the key does not contain upper case letters.
298362
if (!keySet.Add(key.ToString()))
299363
{
300364
// test_tracestate_duplicated_keys
365+
tracestateResult = string.Empty;
301366
return false;
302367
}
303368

@@ -502,29 +567,6 @@ private static int GetKeyHashCode(ReadOnlySpan<char> key)
502567
#endif
503568
}
504569

505-
private static bool HasAnyValues(IEnumerable<string> values)
506-
{
507-
#if NET
508-
if (values.TryGetNonEnumeratedCount(out var count))
509-
{
510-
return count > 0;
511-
}
512-
#endif
513-
514-
if (values is ICollection<string> collection)
515-
{
516-
return collection.Count > 0;
517-
}
518-
519-
if (values is IReadOnlyCollection<string> readOnlyCollection)
520-
{
521-
return readOnlyCollection.Count > 0;
522-
}
523-
524-
using var enumerator = values.GetEnumerator();
525-
return enumerator.MoveNext();
526-
}
527-
528570
private static bool TryGetSingleValue(IEnumerable<string>? values, out string value)
529571
{
530572
value = string.Empty;

test/OpenTelemetry.Tests/Trace/Propagation/TraceContextPropagatorTests.cs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,26 @@ public void Extract_SupportsEnumerableCarrierValues()
161161
Assert.Equal("k1=v1,k2=v2", actual.ActivityContext.TraceState);
162162
}
163163

164+
[Fact]
165+
public void Extract_EnumeratesEnumerableTracestateValuesOnce()
166+
{
167+
var tracestateValues = new SingleUseEnumerableCarrierValues(" k1=v1 , k2=v2 ");
168+
var headers = new Dictionary<string, IEnumerable<string>>
169+
{
170+
[TraceParent] = new EnumerableCarrierValues($"00-{TraceId}-{SpanId}-01"),
171+
[TraceState] = tracestateValues,
172+
};
173+
174+
var target = new TraceContextPropagator();
175+
var actual = target.Extract(default, headers, static (carrier, name) =>
176+
carrier.TryGetValue(name, out var value) ? value : Empty);
177+
178+
Assert.Equal(ActivityTraceId.CreateFromString(TraceId.AsSpan()), actual.ActivityContext.TraceId);
179+
Assert.Equal(ActivitySpanId.CreateFromString(SpanId.AsSpan()), actual.ActivityContext.SpanId);
180+
Assert.Equal("k1=v1,k2=v2", actual.ActivityContext.TraceState);
181+
Assert.Equal(1, tracestateValues.EnumerationCount);
182+
}
183+
164184
[Fact]
165185
public void Extract_IgnoresMultipleEnumerableTraceparentValues()
166186
{
@@ -226,7 +246,7 @@ public void TryExtractTracestate_SingleHeaderRejectsDuplicateLongKeys()
226246
[Fact]
227247
public void TryExtractTracestate_NullCollectionReturnsEmpty()
228248
{
229-
Assert.True(TraceContextPropagator.TryExtractTracestate((IEnumerable<string>)null!, out var actual));
249+
Assert.True(TraceContextPropagator.TryExtractTracestate((IEnumerable<string>?)null, out var actual));
230250
Assert.Empty(actual);
231251
}
232252

@@ -455,4 +475,24 @@ public IEnumerator<string> GetEnumerator()
455475

456476
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
457477
}
478+
479+
private sealed class SingleUseEnumerableCarrierValues(params string[] values) : IEnumerable<string>
480+
{
481+
public int EnumerationCount { get; private set; }
482+
483+
public IEnumerator<string> GetEnumerator()
484+
{
485+
if (this.EnumerationCount++ > 0)
486+
{
487+
throw new InvalidOperationException("Sequence was enumerated multiple times.");
488+
}
489+
490+
foreach (var value in values)
491+
{
492+
yield return value;
493+
}
494+
}
495+
496+
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
497+
}
458498
}

0 commit comments

Comments
 (0)