From 89484709376285df64f8d1bfe823fd8d7be709d2 Mon Sep 17 00:00:00 2001 From: Eddy Nakamura Date: Tue, 4 Aug 2020 21:26:56 -0300 Subject: [PATCH] Creating CompositePropagator (#923) * Creating CompositePropagator Adding ActivityContext to interface Adding tests Updating CompositePropagator + tests updating comments Improving performance when size is 0 updating tests updating default tracestate for empty MikeGoldsmith review adding more tests to exemplify usage Feature/composite propagator refactor (#1) * Added TestPropagator and switched a couple tests to use it. * removing extra classes Co-authored-by: Mikel Blanchard updating changelog and constructor MikeGoldsmith comments updating logic * checking value * adding inverted test * updating tests * removing tracecontext and b3 tests * testing only aspnet to merge * Revert "testing only aspnet to merge" This reverts commit 43ca7d074b45bf50f769f83a315c79075ae54630. Co-authored-by: Cijo Thomas --- src/OpenTelemetry.Api/CHANGELOG.md | 4 + .../Propagation/CompositePropagator.cs | 74 ++++++++ .../Context/Propagation/ITextFormat.cs | 3 +- .../Context/Propagation/TraceContextFormat.cs | 65 +++---- src/OpenTelemetry.Api/Trace/SpanContext.cs | 6 + .../Implementation/HttpInListener.cs | 2 +- .../Implementation/HttpInListener.cs | 6 +- .../TracerShim.cs | 5 +- .../Context/Propagation/B3Format.cs | 30 +-- .../HttpInListenerTests.cs | 2 +- .../BasicTests.cs | 2 +- .../TracerShimTests.cs | 4 +- .../Trace/Propagation/B3FormatTest.cs | 52 ++--- .../Propagation/CompositePropagatorTest.cs | 110 +++++++++++ .../Trace/Propagation/TestPropagator.cs | 93 +++++++++ .../Propagation/TraceContextActivityTest.cs | 179 ------------------ .../Trace/Propagation/TraceContextTest.cs | 63 +++++- 17 files changed, 419 insertions(+), 281 deletions(-) create mode 100644 src/OpenTelemetry.Api/Context/Propagation/CompositePropagator.cs create mode 100644 test/OpenTelemetry.Tests/Implementation/Trace/Propagation/CompositePropagatorTest.cs create mode 100644 test/OpenTelemetry.Tests/Implementation/Trace/Propagation/TestPropagator.cs delete mode 100644 test/OpenTelemetry.Tests/Implementation/Trace/Propagation/TraceContextActivityTest.cs diff --git a/src/OpenTelemetry.Api/CHANGELOG.md b/src/OpenTelemetry.Api/CHANGELOG.md index e9511d1e8e1..db15d7b076d 100644 --- a/src/OpenTelemetry.Api/CHANGELOG.md +++ b/src/OpenTelemetry.Api/CHANGELOG.md @@ -16,6 +16,10 @@ changed to match the gets removed. ([#954](https://github.com/open-telemetry/opentelemetry-dotnet/pull/954)). * HttpStatusCode in all spans attribute (http.status_code) to use int value. +* `ITextFormatActivity` got replaced by `ITextFormat` with an additional method + to be implemented (`IsInjected`) +* Added `CompositePropagator` that accepts a list of `ITextFormat` following + [specification](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/context/api-propagators.md#create-a-composite-propagator) ## 0.4.0-beta.2 diff --git a/src/OpenTelemetry.Api/Context/Propagation/CompositePropagator.cs b/src/OpenTelemetry.Api/Context/Propagation/CompositePropagator.cs new file mode 100644 index 00000000000..ef69bca4490 --- /dev/null +++ b/src/OpenTelemetry.Api/Context/Propagation/CompositePropagator.cs @@ -0,0 +1,74 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace OpenTelemetry.Context.Propagation +{ + /// + /// CompositePropagator provides a mechanism for combining multiple propagators into a single one. + /// + public class CompositePropagator : ITextFormat + { + private static readonly ISet EmptyFields = new HashSet(); + private readonly List textFormats; + + /// + /// Initializes a new instance of the class. + /// + /// List of wire context propagator. + public CompositePropagator(List textFormats) + { + this.textFormats = textFormats ?? throw new ArgumentNullException(nameof(textFormats)); + } + + /// + public ISet Fields => EmptyFields; + + /// + public ActivityContext Extract(ActivityContext activityContext, T carrier, Func> getter) + { + foreach (var textFormat in this.textFormats) + { + activityContext = textFormat.Extract(activityContext, carrier, getter); + if (activityContext.IsValid()) + { + return activityContext; + } + } + + return activityContext; + } + + /// + public void Inject(ActivityContext activityContext, T carrier, Action setter) + { + foreach (var textFormat in this.textFormats) + { + textFormat.Inject(activityContext, carrier, setter); + } + } + + /// + public bool IsInjected(T carrier, Func> getter) + { + return this.textFormats.All(textFormat => textFormat.IsInjected(carrier, getter)); + } + } +} diff --git a/src/OpenTelemetry.Api/Context/Propagation/ITextFormat.cs b/src/OpenTelemetry.Api/Context/Propagation/ITextFormat.cs index ca35d32a7d0..135a246412b 100644 --- a/src/OpenTelemetry.Api/Context/Propagation/ITextFormat.cs +++ b/src/OpenTelemetry.Api/Context/Propagation/ITextFormat.cs @@ -45,10 +45,11 @@ public interface ITextFormat /// Extracts activity context from textual representation. /// /// Type of object to extract context from. Typically HttpRequest or similar. + /// The default activity context to be used if Extract fails. /// Object to extract context from. Instance of this object will be passed to the getter. /// Function that will return string value of a key with the specified name. /// Activity context from it's text representation. - ActivityContext Extract(T carrier, Func> getter); + ActivityContext Extract(ActivityContext activityContext, T carrier, Func> getter); /// /// Tests if an activity context has been injected into a carrier. diff --git a/src/OpenTelemetry.Api/Context/Propagation/TraceContextFormat.cs b/src/OpenTelemetry.Api/Context/Propagation/TraceContextFormat.cs index ed0c53a79bb..ee1525aa573 100644 --- a/src/OpenTelemetry.Api/Context/Propagation/TraceContextFormat.cs +++ b/src/OpenTelemetry.Api/Context/Propagation/TraceContextFormat.cs @@ -30,7 +30,6 @@ public class TraceContextFormat : ITextFormat private const string TraceParent = "traceparent"; private const string TraceState = "tracestate"; - private static readonly int VersionLength = "00".Length; private static readonly int VersionPrefixIdLength = "00-".Length; private static readonly int TraceIdLength = "0af7651916cd43dd8448eb211c80319c".Length; private static readonly int VersionAndTraceIdLength = "00-0af7651916cd43dd8448eb211c80319c-".Length; @@ -74,18 +73,18 @@ public bool IsInjected(T carrier, Func> getter } /// - public ActivityContext Extract(T carrier, Func> getter) + public ActivityContext Extract(ActivityContext activityContext, T carrier, Func> getter) { if (carrier == null) { OpenTelemetryApiEventSource.Log.FailedToInjectActivityContext("null carrier"); - return default; + return activityContext; } if (getter == null) { OpenTelemetryApiEventSource.Log.FailedToExtractContext("null getter"); - return default; + return activityContext; } try @@ -95,22 +94,22 @@ public ActivityContext Extract(T carrier, Func // There must be a single traceparent if (traceparentCollection == null || traceparentCollection.Count() != 1) { - return default; + return activityContext; } var traceparent = traceparentCollection.First(); - var traceparentParsed = this.TryExtractTraceparent(traceparent, out var traceId, out var spanId, out var traceoptions); + var traceparentParsed = TryExtractTraceparent(traceparent, out var traceId, out var spanId, out var traceoptions); if (!traceparentParsed) { - return default; + return activityContext; } - string tracestate = null; + string tracestate = string.Empty; var tracestateCollection = getter(carrier, TraceState); - if (tracestateCollection != null) + if (tracestateCollection?.Any() ?? false) { - this.TryExtractTracestate(tracestateCollection.ToArray(), out tracestate); + TryExtractTracestate(tracestateCollection.ToArray(), out tracestate); } return new ActivityContext(traceId, spanId, traceoptions, tracestate, isRemote: true); @@ -121,7 +120,7 @@ public ActivityContext Extract(T carrier, Func } // in case of exception indicate to upstream that there is no parseable context from the top - return default; + return activityContext; } /// @@ -157,7 +156,7 @@ public void Inject(ActivityContext activityContext, T carrier, Action= '0') && (c <= '9')) - { - return (byte)(c - '0'); - } - - if ((c >= 'a') && (c <= 'f')) - { - return (byte)(c - 'a' + 10); - } - - if ((c >= 'A') && (c <= 'F')) - { - return (byte)(c - 'A' + 10); - } - - throw new ArgumentOutOfRangeException(nameof(c), $"Invalid character: {c}."); - } - - private bool TryExtractTracestate(string[] tracestateCollection, out string tracestateResult) + internal static bool TryExtractTracestate(string[] tracestateCollection, out string tracestateResult) { tracestateResult = string.Empty; @@ -304,5 +283,17 @@ private bool TryExtractTracestate(string[] tracestateCollection, out string trac return true; } + + private static byte HexCharToByte(char c) + { + if (((c >= '0') && (c <= '9')) + || ((c >= 'a') && (c <= 'f')) + || ((c >= 'A') && (c <= 'F'))) + { + return Convert.ToByte(c); + } + + throw new ArgumentOutOfRangeException(nameof(c), $"Invalid character: {c}."); + } } } diff --git a/src/OpenTelemetry.Api/Trace/SpanContext.cs b/src/OpenTelemetry.Api/Trace/SpanContext.cs index 895b3fc759e..b1717e6762a 100644 --- a/src/OpenTelemetry.Api/Trace/SpanContext.cs +++ b/src/OpenTelemetry.Api/Trace/SpanContext.cs @@ -120,6 +120,12 @@ public IEnumerable> TraceState } } + /// + /// Converts a into an . + /// + /// source. + public static implicit operator ActivityContext(SpanContext spanContext) => spanContext.ActivityContext; + /// /// Compare two for equality. /// diff --git a/src/OpenTelemetry.Instrumentation.AspNet/Implementation/HttpInListener.cs b/src/OpenTelemetry.Instrumentation.AspNet/Implementation/HttpInListener.cs index 9c80344b06c..954ed844609 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet/Implementation/HttpInListener.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet/Implementation/HttpInListener.cs @@ -63,7 +63,7 @@ public override void OnStartActivity(Activity activity, object payload) { // This requires to ignore the current activity and create a new one // using the context extracted using the format TextFormat supports. - var ctx = this.options.TextFormat.Extract(request, HttpRequestHeaderValuesGetter); + var ctx = this.options.TextFormat.Extract(default, request, HttpRequestHeaderValuesGetter); // Create a new activity with its parent set from the extracted context. // This makes the new activity as a "sibling" of the activity created by diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs b/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs index 284f0fe2c35..15b1f65377a 100644 --- a/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs +++ b/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs @@ -50,9 +50,7 @@ public HttpInListener(string name, AspNetCoreInstrumentationOptions options, Act public override void OnStartActivity(Activity activity, object payload) { - var context = this.startContextFetcher.Fetch(payload) as HttpContext; - - if (context == null) + if (!(this.startContextFetcher.Fetch(payload) is HttpContext context)) { AspNetCoreInstrumentationEventSource.Log.NullPayload(nameof(HttpInListener), nameof(this.OnStartActivity)); return; @@ -72,7 +70,7 @@ public override void OnStartActivity(Activity activity, object payload) // using the context extracted from w3ctraceparent header or // using the format TextFormat supports. - var ctx = this.options.TextFormat.Extract(request, HttpRequestHeaderValuesGetter); + var ctx = this.options.TextFormat.Extract(default, request, HttpRequestHeaderValuesGetter); // Create a new activity with its parent set from the extracted context. // This makes the new activity as a "sibling" of the activity created by diff --git a/src/OpenTelemetry.Shims.OpenTracing/TracerShim.cs b/src/OpenTelemetry.Shims.OpenTracing/TracerShim.cs index 7227eb59f0a..a75ed3a5f1c 100644 --- a/src/OpenTelemetry.Shims.OpenTracing/TracerShim.cs +++ b/src/OpenTelemetry.Shims.OpenTracing/TracerShim.cs @@ -81,7 +81,7 @@ IEnumerable GetCarrierKeyValue(Dictionary> s return value; } - activityContext = this.textFormat.Extract(carrierMap, GetCarrierKeyValue); + activityContext = this.textFormat.Extract(default, carrierMap, GetCarrierKeyValue); } return !activityContext.IsValid() ? null : new SpanContextShim(new Trace.SpanContext(activityContext)); @@ -115,8 +115,7 @@ public void Inject( if ((format == BuiltinFormats.TextMap || format == BuiltinFormats.HttpHeaders) && carrier is ITextMap textMapCarrier) { - // Remove comment after spanshim changes - // this.textFormat.Inject(shim.SpanContext, textMapCarrier, (instrumentation, key, value) => instrumentation.Set(key, value)); + this.textFormat.Inject(shim.SpanContext, textMapCarrier, (instrumentation, key, value) => instrumentation.Set(key, value)); } } } diff --git a/src/OpenTelemetry/Context/Propagation/B3Format.cs b/src/OpenTelemetry/Context/Propagation/B3Format.cs index 261514a3394..8ec7513826d 100644 --- a/src/OpenTelemetry/Context/Propagation/B3Format.cs +++ b/src/OpenTelemetry/Context/Propagation/B3Format.cs @@ -107,27 +107,27 @@ public bool IsInjected(T carrier, Func> getter } /// - public ActivityContext Extract(T carrier, Func> getter) + public ActivityContext Extract(ActivityContext activityContext, T carrier, Func> getter) { if (carrier == null) { OpenTelemetrySdkEventSource.Log.FailedToExtractContext("null carrier"); - return default; + return activityContext; } if (getter == null) { OpenTelemetrySdkEventSource.Log.FailedToExtractContext("null getter"); - return default; + return activityContext; } if (this.singleHeader) { - return ExtractFromSingleHeader(carrier, getter); + return ExtractFromSingleHeader(activityContext, carrier, getter); } else { - return ExtractFromMultipleHeaders(carrier, getter); + return ExtractFromMultipleHeaders(activityContext, carrier, getter); } } @@ -177,7 +177,7 @@ public void Inject(ActivityContext activityContext, T carrier, Action(T carrier, Func> getter) + private static ActivityContext ExtractFromMultipleHeaders(ActivityContext activityContext, T carrier, Func> getter) { try { @@ -195,7 +195,7 @@ private static ActivityContext ExtractFromMultipleHeaders(T carrier, Func(T carrier, Func(T carrier, Func(T carrier, Func> getter) + private static ActivityContext ExtractFromSingleHeader(ActivityContext activityContext, T carrier, Func> getter) { try { var header = getter(carrier, XB3Combined)?.FirstOrDefault(); if (string.IsNullOrWhiteSpace(header)) { - return default; + return activityContext; } var parts = header.Split(XB3CombinedDelimiter); if (parts.Length < 2 || parts.Length > 4) { - return default; + return activityContext; } var traceIdStr = parts[0]; if (string.IsNullOrWhiteSpace(traceIdStr)) { - return default; + return activityContext; } if (traceIdStr.Length == 16) @@ -258,7 +258,7 @@ private static ActivityContext ExtractFromSingleHeader(T carrier, Func(T carrier, Func(); - textFormat.Setup(m => m.Extract(It.IsAny(), It.IsAny>>())).Returns(new ActivityContext( + textFormat.Setup(m => m.Extract(It.IsAny(), It.IsAny(), It.IsAny>>())).Returns(new ActivityContext( expectedTraceId, expectedSpanId, ActivityTraceFlags.Recorded)); diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs index 918ed707d93..892fd0dc51c 100644 --- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs +++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs @@ -142,7 +142,7 @@ public async Task CustomTextFormat() var expectedSpanId = ActivitySpanId.CreateRandom(); var textFormat = new Mock(); - textFormat.Setup(m => m.Extract(It.IsAny(), It.IsAny>>())).Returns(new ActivityContext( + textFormat.Setup(m => m.Extract(It.IsAny(), It.IsAny(), It.IsAny>>())).Returns(new ActivityContext( expectedTraceId, expectedSpanId, ActivityTraceFlags.Recorded)); diff --git a/test/OpenTelemetry.Shims.OpenTracing.Tests/TracerShimTests.cs b/test/OpenTelemetry.Shims.OpenTracing.Tests/TracerShimTests.cs index 2bac270d93d..ad6ddaca3f9 100644 --- a/test/OpenTelemetry.Shims.OpenTracing.Tests/TracerShimTests.cs +++ b/test/OpenTelemetry.Shims.OpenTracing.Tests/TracerShimTests.cs @@ -148,11 +148,9 @@ public void Extract_InvalidTraceParent() Assert.Null(spanContextShim); } - [Fact(Skip = "Enable after actual spanshim")] + [Fact] public void InjectExtract_TextMap_Ok() { - var tracerMock = new Mock(); - var carrier = new TextMapCarrier(); var spanContextShim = new SpanContextShim(new SpanContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.None)); diff --git a/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/B3FormatTest.cs b/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/B3FormatTest.cs index c4075c7167e..0769b4596b2 100644 --- a/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/B3FormatTest.cs +++ b/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/B3FormatTest.cs @@ -78,7 +78,7 @@ public void ParseMissingSampledAndMissingFlag() { B3Format.XB3TraceId, TraceIdBase16 }, { B3Format.XB3SpanId, SpanIdBase16 }, }; var spanContext = new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None); - Assert.Equal(spanContext, this.b3Format.Extract(headersNotSampled, Getter)); + Assert.Equal(spanContext, this.b3Format.Extract(default, headersNotSampled, Getter)); } [Fact] @@ -88,7 +88,7 @@ public void ParseSampled() { { B3Format.XB3TraceId, TraceIdBase16 }, { B3Format.XB3SpanId, SpanIdBase16 }, { B3Format.XB3Sampled, "1" }, }; - Assert.Equal(new ActivityContext(TraceId, SpanId, TraceOptions), this.b3Format.Extract(headersSampled, Getter)); + Assert.Equal(new ActivityContext(TraceId, SpanId, TraceOptions), this.b3Format.Extract(default, headersSampled, Getter)); } [Fact] @@ -98,7 +98,7 @@ public void ParseZeroSampled() { { B3Format.XB3TraceId, TraceIdBase16 }, { B3Format.XB3SpanId, SpanIdBase16 }, { B3Format.XB3Sampled, "0" }, }; - Assert.Equal(new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None), this.b3Format.Extract(headersNotSampled, Getter)); + Assert.Equal(new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None), this.b3Format.Extract(default, headersNotSampled, Getter)); } [Fact] @@ -108,7 +108,7 @@ public void ParseFlag() { { B3Format.XB3TraceId, TraceIdBase16 }, { B3Format.XB3SpanId, SpanIdBase16 }, { B3Format.XB3Flags, "1" }, }; - Assert.Equal(new ActivityContext(TraceId, SpanId, TraceOptions), this.b3Format.Extract(headersFlagSampled, Getter)); + Assert.Equal(new ActivityContext(TraceId, SpanId, TraceOptions), this.b3Format.Extract(default, headersFlagSampled, Getter)); } [Fact] @@ -118,7 +118,7 @@ public void ParseZeroFlag() { { B3Format.XB3TraceId, TraceIdBase16 }, { B3Format.XB3SpanId, SpanIdBase16 }, { B3Format.XB3Flags, "0" }, }; - Assert.Equal(new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None), this.b3Format.Extract(headersFlagNotSampled, Getter)); + Assert.Equal(new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None), this.b3Format.Extract(default, headersFlagNotSampled, Getter)); } [Fact] @@ -130,7 +130,7 @@ public void ParseEightBytesTraceId() { B3Format.XB3SpanId, SpanIdBase16 }, { B3Format.XB3Sampled, "1" }, }; - Assert.Equal(new ActivityContext(TraceIdEightBytes, SpanId, TraceOptions), this.b3Format.Extract(headersEightBytes, Getter)); + Assert.Equal(new ActivityContext(TraceIdEightBytes, SpanId, TraceOptions), this.b3Format.Extract(default, headersEightBytes, Getter)); } [Fact] @@ -140,7 +140,7 @@ public void ParseEightBytesTraceId_NotSampledSpanContext() { { B3Format.XB3TraceId, TraceIdBase16EightBytes }, { B3Format.XB3SpanId, SpanIdBase16 }, }; - Assert.Equal(new ActivityContext(TraceIdEightBytes, SpanId, ActivityTraceFlags.None), this.b3Format.Extract(headersEightBytes, Getter)); + Assert.Equal(new ActivityContext(TraceIdEightBytes, SpanId, ActivityTraceFlags.None), this.b3Format.Extract(default, headersEightBytes, Getter)); } [Fact] @@ -150,7 +150,7 @@ public void ParseInvalidTraceId() { { B3Format.XB3TraceId, InvalidId }, { B3Format.XB3SpanId, SpanIdBase16 }, }; - Assert.Equal(default, this.b3Format.Extract(invalidHeaders, Getter)); + Assert.Equal(default, this.b3Format.Extract(default, invalidHeaders, Getter)); } [Fact] @@ -161,14 +161,14 @@ public void ParseInvalidTraceId_Size() { B3Format.XB3TraceId, InvalidSizeId }, { B3Format.XB3SpanId, SpanIdBase16 }, }; - Assert.Equal(default, this.b3Format.Extract(invalidHeaders, Getter)); + Assert.Equal(default, this.b3Format.Extract(default, invalidHeaders, Getter)); } [Fact] public void ParseMissingTraceId() { var invalidHeaders = new Dictionary { { B3Format.XB3SpanId, SpanIdBase16 }, }; - Assert.Equal(default, this.b3Format.Extract(invalidHeaders, Getter)); + Assert.Equal(default, this.b3Format.Extract(default, invalidHeaders, Getter)); } [Fact] @@ -178,7 +178,7 @@ public void ParseInvalidSpanId() { { B3Format.XB3TraceId, TraceIdBase16 }, { B3Format.XB3SpanId, InvalidId }, }; - Assert.Equal(default, this.b3Format.Extract(invalidHeaders, Getter)); + Assert.Equal(default, this.b3Format.Extract(default, invalidHeaders, Getter)); } [Fact] @@ -188,14 +188,14 @@ public void ParseInvalidSpanId_Size() { { B3Format.XB3TraceId, TraceIdBase16 }, { B3Format.XB3SpanId, InvalidSizeId }, }; - Assert.Equal(default, this.b3Format.Extract(invalidHeaders, Getter)); + Assert.Equal(default, this.b3Format.Extract(default, invalidHeaders, Getter)); } [Fact] public void ParseMissingSpanId() { var invalidHeaders = new Dictionary { { B3Format.XB3TraceId, TraceIdBase16 } }; - Assert.Equal(default, this.b3Format.Extract(invalidHeaders, Getter)); + Assert.Equal(default, this.b3Format.Extract(default, invalidHeaders, Getter)); } [Fact] @@ -224,7 +224,7 @@ public void ParseMissingSampledAndMissingFlag_SingleHeader() { B3Format.XB3Combined, $"{TraceIdBase16}-{SpanIdBase16}" }, }; var spanContext = new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None); - Assert.Equal(spanContext, this.b3FormatSingleHeader.Extract(headersNotSampled, Getter)); + Assert.Equal(spanContext, this.b3FormatSingleHeader.Extract(default, headersNotSampled, Getter)); } [Fact] @@ -234,7 +234,7 @@ public void ParseSampled_SingleHeader() { { B3Format.XB3Combined, $"{TraceIdBase16}-{SpanIdBase16}-1" }, }; - Assert.Equal(new ActivityContext(TraceId, SpanId, TraceOptions), this.b3FormatSingleHeader.Extract(headersSampled, Getter)); + Assert.Equal(new ActivityContext(TraceId, SpanId, TraceOptions), this.b3FormatSingleHeader.Extract(default, headersSampled, Getter)); } [Fact] @@ -244,7 +244,7 @@ public void ParseZeroSampled_SingleHeader() { { B3Format.XB3Combined, $"{TraceIdBase16}-{SpanIdBase16}-0" }, }; - Assert.Equal(new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None), this.b3FormatSingleHeader.Extract(headersNotSampled, Getter)); + Assert.Equal(new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None), this.b3FormatSingleHeader.Extract(default, headersNotSampled, Getter)); } [Fact] @@ -254,7 +254,7 @@ public void ParseFlag_SingleHeader() { { B3Format.XB3Combined, $"{TraceIdBase16}-{SpanIdBase16}-1" }, }; - Assert.Equal(new ActivityContext(TraceId, SpanId, TraceOptions), this.b3FormatSingleHeader.Extract(headersFlagSampled, Getter)); + Assert.Equal(new ActivityContext(TraceId, SpanId, TraceOptions), this.b3FormatSingleHeader.Extract(default, headersFlagSampled, Getter)); } [Fact] @@ -264,7 +264,7 @@ public void ParseZeroFlag_SingleHeader() { { B3Format.XB3Combined, $"{TraceIdBase16}-{SpanIdBase16}-0" }, }; - Assert.Equal(new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None), this.b3FormatSingleHeader.Extract(headersFlagNotSampled, Getter)); + Assert.Equal(new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None), this.b3FormatSingleHeader.Extract(default, headersFlagNotSampled, Getter)); } [Fact] @@ -274,7 +274,7 @@ public void ParseEightBytesTraceId_SingleHeader() { { B3Format.XB3Combined, $"{TraceIdBase16EightBytes}-{SpanIdBase16}-1" }, }; - Assert.Equal(new ActivityContext(TraceIdEightBytes, SpanId, TraceOptions), this.b3FormatSingleHeader.Extract(headersEightBytes, Getter)); + Assert.Equal(new ActivityContext(TraceIdEightBytes, SpanId, TraceOptions), this.b3FormatSingleHeader.Extract(default, headersEightBytes, Getter)); } [Fact] @@ -284,7 +284,7 @@ public void ParseEightBytesTraceId_NotSampledSpanContext_SingleHeader() { { B3Format.XB3Combined, $"{TraceIdBase16EightBytes}-{SpanIdBase16}" }, }; - Assert.Equal(new ActivityContext(TraceIdEightBytes, SpanId, ActivityTraceFlags.None), this.b3FormatSingleHeader.Extract(headersEightBytes, Getter)); + Assert.Equal(new ActivityContext(TraceIdEightBytes, SpanId, ActivityTraceFlags.None), this.b3FormatSingleHeader.Extract(default, headersEightBytes, Getter)); } [Fact] @@ -294,7 +294,7 @@ public void ParseInvalidTraceId_SingleHeader() { { B3Format.XB3Combined, $"{InvalidId}-{SpanIdBase16}" }, }; - Assert.Equal(default, this.b3FormatSingleHeader.Extract(invalidHeaders, Getter)); + Assert.Equal(default, this.b3FormatSingleHeader.Extract(default, invalidHeaders, Getter)); } [Fact] @@ -305,14 +305,14 @@ public void ParseInvalidTraceId_Size_SingleHeader() { B3Format.XB3Combined, $"{InvalidSizeId}-{SpanIdBase16}" }, }; - Assert.Equal(default, this.b3FormatSingleHeader.Extract(invalidHeaders, Getter)); + Assert.Equal(default, this.b3FormatSingleHeader.Extract(default, invalidHeaders, Getter)); } [Fact] public void ParseMissingTraceId_SingleHeader() { var invalidHeaders = new Dictionary { { B3Format.XB3Combined, $"-{SpanIdBase16}" } }; - Assert.Equal(default, this.b3FormatSingleHeader.Extract(invalidHeaders, Getter)); + Assert.Equal(default, this.b3FormatSingleHeader.Extract(default, invalidHeaders, Getter)); } [Fact] @@ -322,7 +322,7 @@ public void ParseInvalidSpanId_SingleHeader() { { B3Format.XB3Combined, $"{TraceIdBase16}-{InvalidId}" }, }; - Assert.Equal(default, this.b3FormatSingleHeader.Extract(invalidHeaders, Getter)); + Assert.Equal(default, this.b3FormatSingleHeader.Extract(default, invalidHeaders, Getter)); } [Fact] @@ -332,14 +332,14 @@ public void ParseInvalidSpanId_Size_SingleHeader() { { B3Format.XB3Combined, $"{TraceIdBase16}-{InvalidSizeId}" }, }; - Assert.Equal(default, this.b3FormatSingleHeader.Extract(invalidHeaders, Getter)); + Assert.Equal(default, this.b3FormatSingleHeader.Extract(default, invalidHeaders, Getter)); } [Fact] public void ParseMissingSpanId_SingleHeader() { var invalidHeaders = new Dictionary { { B3Format.XB3Combined, $"{TraceIdBase16}-" } }; - Assert.Equal(default, this.b3FormatSingleHeader.Extract(invalidHeaders, Getter)); + Assert.Equal(default, this.b3FormatSingleHeader.Extract(default, invalidHeaders, Getter)); } [Fact] diff --git a/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/CompositePropagatorTest.cs b/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/CompositePropagatorTest.cs new file mode 100644 index 00000000000..8e9d26131f0 --- /dev/null +++ b/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/CompositePropagatorTest.cs @@ -0,0 +1,110 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using OpenTelemetry.Context.Propagation; +using Xunit; + +namespace OpenTelemetry.Tests.Implementation.Trace.Propagation +{ + public class CompositePropagatorTest + { + private const string TraceParent = "traceparent"; + private static readonly string[] Empty = new string[0]; + private static readonly Func, string, IEnumerable> Getter = (headers, name) => + { + count++; + if (headers.TryGetValue(name, out var value)) + { + return new[] { value }; + } + + return Empty; + }; + + private static readonly Action, string, string> Setter = (carrier, name, value) => + { + carrier[name] = value; + }; + + private static int count = 0; + + private readonly ActivityTraceId traceId = ActivityTraceId.CreateRandom(); + private readonly ActivitySpanId spanId = ActivitySpanId.CreateRandom(); + + [Fact] + public void CompositePropagator_NullTextFormatList() + { + Assert.Throws(() => new CompositePropagator(null)); + } + + [Fact] + public void CompositePropagator_TestPropagator() + { + var compositePropagator = new CompositePropagator(new List + { + new TestPropagator("custom-traceparent-1", "custom-tracestate-1"), + new TestPropagator("custom-traceparent-2", "custom-tracestate-2"), + }); + + var activityContext = new ActivityContext(this.traceId, this.spanId, ActivityTraceFlags.Recorded, traceState: null); + var carrier = new Dictionary(); + + compositePropagator.Inject(activityContext, carrier, Setter); + Assert.Contains(carrier, kv => kv.Key == "custom-traceparent-1"); + Assert.Contains(carrier, kv => kv.Key == "custom-traceparent-2"); + + bool isInjected = compositePropagator.IsInjected(carrier, Getter); + Assert.True(isInjected); + } + + [Fact] + public void CompositePropagator_UsingSameTag() + { + const string header01 = "custom-tracestate-01"; + const string header02 = "custom-tracestate-02"; + + var compositePropagator = new CompositePropagator(new List + { + new TestPropagator("custom-traceparent", header01, true), + new TestPropagator("custom-traceparent", header02), + }); + + var activityContext = new ActivityContext(this.traceId, this.spanId, ActivityTraceFlags.Recorded, traceState: null); + var carrier = new Dictionary(); + + compositePropagator.Inject(activityContext, carrier, Setter); + Assert.Contains(carrier, kv => kv.Key == "custom-traceparent"); + + // checking if the latest propagator is the one with the data. So, it will replace the previous one. + Assert.Equal($"00-{this.traceId}-{this.spanId}-{header02.Split('-').Last()}", carrier["custom-traceparent"]); + + bool isInjected = compositePropagator.IsInjected(carrier, Getter); + Assert.True(isInjected); + + // resetting counter + count = 0; + ActivityContext newContext = compositePropagator.Extract(default, carrier, Getter); + + // checking if we accessed only two times: header/headerstate options + // if that's true, we skipped the first one since we have a logic to for the default result + Assert.Equal(2, count); + } + } +} diff --git a/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/TestPropagator.cs b/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/TestPropagator.cs new file mode 100644 index 00000000000..de206634c28 --- /dev/null +++ b/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/TestPropagator.cs @@ -0,0 +1,93 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using OpenTelemetry.Context.Propagation; + +namespace OpenTelemetry.Tests.Implementation.Trace.Propagation +{ + public class TestPropagator : ITextFormat + { + private readonly string idHeaderName; + private readonly string stateHeaderName; + private readonly bool defaultContext; + + public TestPropagator(string idHeaderName, string stateHeaderName, bool defaultContext = false) + { + this.idHeaderName = idHeaderName; + this.stateHeaderName = stateHeaderName; + this.defaultContext = defaultContext; + } + + public ISet Fields => new HashSet() { this.idHeaderName, this.stateHeaderName }; + + public ActivityContext Extract(ActivityContext activityContext, T carrier, Func> getter) + { + if (this.defaultContext) + { + return activityContext; + } + + IEnumerable id = getter(carrier, this.idHeaderName); + if (id.Count() <= 0) + { + return activityContext; + } + + var traceparentParsed = TraceContextFormat.TryExtractTraceparent(id.First(), out var traceId, out var spanId, out var traceoptions); + if (!traceparentParsed) + { + return activityContext; + } + + string tracestate = string.Empty; + IEnumerable tracestateCollection = getter(carrier, this.stateHeaderName); + if (tracestateCollection?.Any() ?? false) + { + TraceContextFormat.TryExtractTracestate(tracestateCollection.ToArray(), out tracestate); + } + + return new ActivityContext(traceId, spanId, traceoptions, tracestate); + } + + public void Inject(ActivityContext activityContext, T carrier, Action setter) + { + string headerNumber = this.stateHeaderName.Split('-').Last(); + + var traceparent = string.Concat("00-", activityContext.TraceId.ToHexString(), "-", activityContext.SpanId.ToHexString()); + traceparent = string.Concat(traceparent, "-", headerNumber); + + setter(carrier, this.idHeaderName, traceparent); + + string tracestateStr = activityContext.TraceState; + if (tracestateStr?.Length > 0) + { + setter(carrier, this.stateHeaderName, tracestateStr); + } + } + + public bool IsInjected(T carrier, Func> getter) + { + var traceparentCollection = getter(carrier, this.idHeaderName); + + // There must be a single traceparent + return traceparentCollection != null && traceparentCollection.Count() == 1; + } + } +} diff --git a/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/TraceContextActivityTest.cs b/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/TraceContextActivityTest.cs deleted file mode 100644 index 0e904ab5590..00000000000 --- a/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/TraceContextActivityTest.cs +++ /dev/null @@ -1,179 +0,0 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -using System; -using System.Collections.Generic; -using System.Diagnostics; -using OpenTelemetry.Context.Propagation; -using Xunit; - -namespace OpenTelemetry.Impl.Trace.Propagation -{ - public class TraceContextActivityTest - { - private const string TraceParent = "traceparent"; - private const string TraceState = "tracestate"; - private const string TraceId = "0af7651916cd43dd8448eb211c80319c"; - private const string SpanId = "b9c7c989f97918e1"; - - private static readonly string[] Empty = new string[0]; - private static readonly Func, string, IEnumerable> Getter = (headers, name) => - { - if (headers.TryGetValue(name, out var value)) - { - return new[] { value }; - } - - return Empty; - }; - - private static readonly Action, string, string> Setter = (carrier, name, value) => - { - carrier[name] = value; - }; - - [Fact] - public void TraceContextFormatCanParseExampleFromSpec() - { - var headers = new Dictionary - { - { TraceParent, $"00-{TraceId}-{SpanId}-01" }, - { TraceState, $"congo=lZWRzIHRoNhcm5hbCBwbGVhc3VyZS4,rojo=00-{TraceId}-00f067aa0ba902b7-01" }, - }; - - var f = new TraceContextFormat(); - var ctx = f.Extract(headers, Getter); - - Assert.Equal(ActivityTraceId.CreateFromString(TraceId.AsSpan()), ctx.TraceId); - Assert.Equal(ActivitySpanId.CreateFromString(SpanId.AsSpan()), ctx.SpanId); - - Assert.True(ctx.IsRemote); - Assert.True(ctx.IsValid()); - Assert.True((ctx.TraceFlags & ActivityTraceFlags.Recorded) != 0); - - Assert.Equal($"congo=lZWRzIHRoNhcm5hbCBwbGVhc3VyZS4,rojo=00-{TraceId}-00f067aa0ba902b7-01", ctx.TraceState); - } - - [Fact] - public void TraceContextFormatNotSampled() - { - var headers = new Dictionary - { - { TraceParent, $"00-{TraceId}-{SpanId}-00" }, - }; - - var f = new TraceContextFormat(); - var ctx = f.Extract(headers, Getter); - - Assert.Equal(ActivityTraceId.CreateFromString(TraceId.AsSpan()), ctx.TraceId); - Assert.Equal(ActivitySpanId.CreateFromString(SpanId.AsSpan()), ctx.SpanId); - Assert.True((ctx.TraceFlags & ActivityTraceFlags.Recorded) == 0); - - Assert.True(ctx.IsRemote); - Assert.True(ctx.IsValid()); - } - - [Fact] - public void TraceContextFormat_IsBlankIfNoHeader() - { - var headers = new Dictionary(); - - var f = new TraceContextFormat(); - var ctx = f.Extract(headers, Getter); - - Assert.False(ctx.IsValid()); - } - - [Fact] - public void TraceContextFormat_IsBlankIfInvalid() - { - var headers = new Dictionary - { - { TraceParent, $"00-xyz7651916cd43dd8448eb211c80319c-{SpanId}-01" }, - }; - - var f = new TraceContextFormat(); - var ctx = f.Extract(headers, Getter); - - Assert.False(ctx.IsValid()); - } - - [Fact] - public void TraceContextFormat_TracestateToStringEmpty() - { - var headers = new Dictionary - { - { TraceParent, $"00-{TraceId}-{SpanId}-01" }, - }; - - var f = new TraceContextFormat(); - var ctx = f.Extract(headers, Getter); - - Assert.Empty(ctx.TraceState); - } - - [Fact] - public void TraceContextFormat_TracestateToString() - { - var headers = new Dictionary - { - { TraceParent, $"00-{TraceId}-{SpanId}-01" }, - { TraceState, "k1=v1,k2=v2,k3=v3" }, - }; - - var f = new TraceContextFormat(); - var ctx = f.Extract(headers, Getter); - - Assert.Equal("k1=v1,k2=v2,k3=v3", ctx.TraceState); - } - - [Fact] - public void TraceContextFormat_Inject_NoTracestate() - { - var traceId = ActivityTraceId.CreateRandom(); - var spanId = ActivitySpanId.CreateRandom(); - var expectedHeaders = new Dictionary - { - { TraceParent, $"00-{traceId}-{spanId}-01" }, - }; - - var activityContext = new ActivityContext(traceId, spanId, ActivityTraceFlags.Recorded, traceState: null); - var carrier = new Dictionary(); - var f = new TraceContextFormat(); - f.Inject(activityContext, carrier, Setter); - - Assert.Equal(expectedHeaders, carrier); - } - - [Fact] - public void TraceContextFormat_Inject_WithTracestate() - { - var traceId = ActivityTraceId.CreateRandom(); - var spanId = ActivitySpanId.CreateRandom(); - var expectedHeaders = new Dictionary - { - { TraceParent, $"00-{traceId}-{spanId}-01" }, - { TraceState, $"congo=lZWRzIHRoNhcm5hbCBwbGVhc3VyZS4,rojo=00-{traceId}-00f067aa0ba902b7-01" }, - }; - - var activityContext = new ActivityContext(traceId, spanId, ActivityTraceFlags.Recorded, expectedHeaders[TraceState]); - var carrier = new Dictionary(); - var f = new TraceContextFormat(); - f.Inject(activityContext, carrier, Setter); - - Assert.Equal(expectedHeaders, carrier); - } - } -} diff --git a/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/TraceContextTest.cs b/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/TraceContextTest.cs index 6f7a8ed9820..c5b757f32cb 100644 --- a/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/TraceContextTest.cs +++ b/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/TraceContextTest.cs @@ -39,6 +39,11 @@ public class TraceContextTest return Empty; }; + private static readonly Action, string, string> Setter = (carrier, name, value) => + { + carrier[name] = value; + }; + [Fact] public void TraceContextFormatCanParseExampleFromSpec() { @@ -49,7 +54,7 @@ public void TraceContextFormatCanParseExampleFromSpec() }; var f = new TraceContextFormat(); - var ctx = f.Extract(headers, Getter); + var ctx = f.Extract(default, headers, Getter); Assert.Equal(ActivityTraceId.CreateFromString(TraceId.AsSpan()), ctx.TraceId); Assert.Equal(ActivitySpanId.CreateFromString(SpanId.AsSpan()), ctx.SpanId); @@ -58,8 +63,7 @@ public void TraceContextFormatCanParseExampleFromSpec() Assert.True(ctx.IsValid()); Assert.True((ctx.TraceFlags & ActivityTraceFlags.Recorded) != 0); - Assert.NotNull(ctx.TraceState); - Assert.Equal(headers[TraceState], ctx.TraceState); + Assert.Equal($"congo=lZWRzIHRoNhcm5hbCBwbGVhc3VyZS4,rojo=00-{TraceId}-00f067aa0ba902b7-01", ctx.TraceState); } [Fact] @@ -71,7 +75,7 @@ public void TraceContextFormatNotSampled() }; var f = new TraceContextFormat(); - var ctx = f.Extract(headers, Getter); + var ctx = f.Extract(default, headers, Getter); Assert.Equal(ActivityTraceId.CreateFromString(TraceId.AsSpan()), ctx.TraceId); Assert.Equal(ActivitySpanId.CreateFromString(SpanId.AsSpan()), ctx.SpanId); @@ -87,7 +91,7 @@ public void TraceContextFormat_IsBlankIfNoHeader() var headers = new Dictionary(); var f = new TraceContextFormat(); - var ctx = f.Extract(headers, Getter); + var ctx = f.Extract(default, headers, Getter); Assert.False(ctx.IsValid()); } @@ -101,9 +105,12 @@ public void TraceContextFormat_IsBlankIfInvalid() }; var f = new TraceContextFormat(); - var ctx = f.Extract(headers, Getter); + var ctx = f.Extract(default, headers, Getter); Assert.False(ctx.IsValid()); + + // TODO: when ActivityContext supports IsRemote + // Assert.True(ctx.IsRemote); } [Fact] @@ -115,9 +122,9 @@ public void TraceContextFormat_TracestateToStringEmpty() }; var f = new TraceContextFormat(); - var ctx = f.Extract(headers, Getter); + var ctx = f.Extract(default, headers, Getter); - Assert.NotNull(ctx.TraceState); + Assert.Empty(ctx.TraceState); } [Fact] @@ -130,10 +137,46 @@ public void TraceContextFormat_TracestateToString() }; var f = new TraceContextFormat(); - var ctx = f.Extract(headers, Getter); + var ctx = f.Extract(default, headers, Getter); - Assert.NotNull(ctx.TraceState); Assert.Equal("k1=v1,k2=v2,k3=v3", ctx.TraceState); } + + [Fact] + public void TraceContextFormat_Inject_NoTracestate() + { + var traceId = ActivityTraceId.CreateRandom(); + var spanId = ActivitySpanId.CreateRandom(); + var expectedHeaders = new Dictionary + { + { TraceParent, $"00-{traceId}-{spanId}-01" }, + }; + + var activityContext = new ActivityContext(traceId, spanId, ActivityTraceFlags.Recorded, traceState: null); + var carrier = new Dictionary(); + var f = new TraceContextFormat(); + f.Inject(activityContext, carrier, Setter); + + Assert.Equal(expectedHeaders, carrier); + } + + [Fact] + public void TraceContextFormat_Inject_WithTracestate() + { + var traceId = ActivityTraceId.CreateRandom(); + var spanId = ActivitySpanId.CreateRandom(); + var expectedHeaders = new Dictionary + { + { TraceParent, $"00-{traceId}-{spanId}-01" }, + { TraceState, $"congo=lZWRzIHRoNhcm5hbCBwbGVhc3VyZS4,rojo=00-{traceId}-00f067aa0ba902b7-01" }, + }; + + var activityContext = new ActivityContext(traceId, spanId, ActivityTraceFlags.Recorded, expectedHeaders[TraceState]); + var carrier = new Dictionary(); + var f = new TraceContextFormat(); + f.Inject(activityContext, carrier, Setter); + + Assert.Equal(expectedHeaders, carrier); + } } }