From a33f395ebc133f17051681060aae35266d82e6c4 Mon Sep 17 00:00:00 2001 From: Eddy Nakamura Date: Fri, 24 Jul 2020 18:26:55 -0300 Subject: [PATCH 1/7] 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 --- 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 | 193 ++++++++++++++++++ .../Trace/Propagation/TestPropagator.cs | 84 ++++++++ .../Propagation/TraceContextActivityTest.cs | 188 ----------------- .../Trace/Propagation/TraceContextTest.cs | 73 +++++-- 17 files changed, 497 insertions(+), 296 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 f3b7a8e418e..c1a8489cb74 100644 --- a/src/OpenTelemetry.Api/CHANGELOG.md +++ b/src/OpenTelemetry.Api/CHANGELOG.md @@ -4,6 +4,10 @@ * Introduced `RuntimeContext` API ([#948](https://github.com/open-telemetry/opentelemetry-dotnet/pull/948)) +* `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 a0a593a4b1d..f3839ca65c5 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); @@ -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 b949f89a843..5e09ba54f05 100644 --- a/src/OpenTelemetry.Api/Trace/SpanContext.cs +++ b/src/OpenTelemetry.Api/Trace/SpanContext.cs @@ -121,6 +121,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 f73f7784938..874e27ebdae 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 1401a4511f0..3b128f35a25 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 463a5c27bd8..dc8138a4c64 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 eb5e6e6e255..1e7fc2e6bbf 100644 --- a/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/B3FormatTest.cs +++ b/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/B3FormatTest.cs @@ -79,7 +79,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] @@ -89,7 +89,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] @@ -99,7 +99,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] @@ -109,7 +109,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] @@ -119,7 +119,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] @@ -131,7 +131,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] @@ -141,7 +141,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] @@ -151,7 +151,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] @@ -162,14 +162,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] @@ -179,7 +179,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] @@ -189,14 +189,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] @@ -225,7 +225,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] @@ -235,7 +235,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] @@ -245,7 +245,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] @@ -255,7 +255,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] @@ -265,7 +265,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] @@ -275,7 +275,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] @@ -285,7 +285,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] @@ -295,7 +295,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] @@ -306,14 +306,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] @@ -323,7 +323,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] @@ -333,14 +333,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..dd86dd2022d --- /dev/null +++ b/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/CompositePropagatorTest.cs @@ -0,0 +1,193 @@ +// +// 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) => + { + 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 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_WithTraceContext() + { + var expectedHeaders = new Dictionary + { + { TraceParent, $"00-{this.traceId}-{this.spanId}-01" }, + }; + + var compositePropagator = new CompositePropagator(new List + { + new TraceContextFormat(), + }); + + var activityContext = new ActivityContext(this.traceId, this.spanId, ActivityTraceFlags.Recorded, traceState: null); + var carrier = new Dictionary(); + compositePropagator.Inject(activityContext, carrier, Setter); + + Assert.Equal(expectedHeaders, carrier); + + var ctx = compositePropagator.Extract(activityContext, expectedHeaders, Getter); + Assert.Equal(activityContext.TraceId, ctx.TraceId); + Assert.Equal(activityContext.SpanId, ctx.SpanId); + + Assert.Empty(compositePropagator.Fields); + } + + [Fact] + public void CompositePropagator_WithTraceContextAndB3Format() + { + var expectedHeaders = new Dictionary + { + { TraceParent, $"00-{this.traceId}-{this.spanId}-01" }, + { B3Format.XB3TraceId, this.traceId.ToString() }, + { B3Format.XB3SpanId, this.spanId.ToString() }, + { B3Format.XB3Sampled, "1" }, + }; + + var compositePropagator = new CompositePropagator(new List + { + new TraceContextFormat(), + new B3Format(), + }); + + var activityContext = new ActivityContext(this.traceId, this.spanId, ActivityTraceFlags.Recorded, traceState: null); + var carrier = new Dictionary(); + compositePropagator.Inject(activityContext, carrier, Setter); + + Assert.Equal(expectedHeaders, carrier); + + var ctx = compositePropagator.Extract(activityContext, expectedHeaders, Getter); + Assert.Equal(activityContext.TraceId, ctx.TraceId); + Assert.Equal(activityContext.SpanId, ctx.SpanId); + Assert.True(ctx.IsValid()); + + bool isInjected = compositePropagator.IsInjected(carrier, Getter); + Assert.True(isInjected); + } + + [Fact] + public void CompositePropagator_B3FormatNotInjected() + { + var carrier = new Dictionary + { + { TraceParent, $"00-{this.traceId}-{this.spanId}-01" }, + }; + + var compositePropagator = new CompositePropagator(new List + { + new TraceContextFormat(), + new B3Format(), + }); + + bool isInjected = compositePropagator.IsInjected(carrier, Getter); + Assert.False(isInjected); + } + + [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() + { + var compositePropagator = new CompositePropagator(new List + { + new TestPropagator("custom-traceparent", "custom-tracestate-1"), + new TestPropagator("custom-traceparent", "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"); + + bool isInjected = compositePropagator.IsInjected(carrier, Getter); + Assert.True(isInjected); + } + + [Fact] + public void CompositePropagator_CustomAndTraceFormats() + { + var compositePropagator = new CompositePropagator(new List + { + new TestPropagator("custom-traceparent", "custom-tracestate-1"), + new TraceContextFormat(), + }); + + var activityContext = new ActivityContext(this.traceId, this.spanId, ActivityTraceFlags.Recorded, traceState: null); + var carrier = new Dictionary(); + + compositePropagator.Inject(activityContext, carrier, Setter); + Assert.Equal(2, carrier.Count); + + bool isInjected = compositePropagator.IsInjected(carrier, Getter); + Assert.True(isInjected); + + ActivityContext newContext = compositePropagator.Extract(default, carrier, Getter); + Assert.Equal(this.traceId, newContext.TraceId); + Assert.Equal(this.spanId, newContext.SpanId); + Assert.True(newContext.IsValid()); + } + } +} 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..0b624f22cbf --- /dev/null +++ b/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/TestPropagator.cs @@ -0,0 +1,84 @@ +// +// 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; + + public TestPropagator(string idHeaderName, string stateHeaderName) + { + this.idHeaderName = idHeaderName; + this.stateHeaderName = stateHeaderName; + } + + public ISet Fields => new HashSet() { this.idHeaderName, this.stateHeaderName }; + + public ActivityContext Extract(ActivityContext activityContext, T carrier, Func> getter) + { + 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) + { + var traceparent = string.Concat("00-", activityContext.TraceId.ToHexString(), "-", activityContext.SpanId.ToHexString()); + traceparent = string.Concat(traceparent, (activityContext.TraceFlags & ActivityTraceFlags.Recorded) != 0 ? "-01" : "-00"); + + 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 a8e13fa0c8c..00000000000 --- a/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/TraceContextActivityTest.cs +++ /dev/null @@ -1,188 +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); - - // TODO: when ActivityContext supports IsRemote - // 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); - - // TODO: when ActivityContext supports IsRemote - // 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()); - - // TODO: when ActivityContext supports IsRemote - // Assert.True(ctx.IsRemote); - } - - [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()); - - // TODO: when ActivityContext supports IsRemote - // Assert.True(ctx.IsRemote); - } - - [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 f9beea3b816..528cbefe31d 100644 --- a/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/TraceContextTest.cs +++ b/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/TraceContextTest.cs @@ -16,7 +16,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using OpenTelemetry.Context.Propagation; using Xunit; @@ -40,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() { @@ -50,18 +54,18 @@ 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); - // TODO: Validate IsRemote when ActivityContext supports it. + // TODO: when ActivityContext supports IsRemote // Assert.True(ctx.IsRemote); + 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] @@ -73,13 +77,13 @@ 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); Assert.True((ctx.TraceFlags & ActivityTraceFlags.Recorded) == 0); - // TODO: Validate IsRemote when ActivityContext supports it. + // TODO: when ActivityContext supports IsRemote // Assert.True(ctx.IsRemote); Assert.True(ctx.IsValid()); } @@ -90,11 +94,11 @@ 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()); - // TODO: Validate IsRemote when ActivityContext supports it. + // TODO: when ActivityContext supports IsRemote // Assert.True(ctx.IsRemote); } @@ -107,11 +111,12 @@ public void TraceContextFormat_IsBlankIfInvalid() }; var f = new TraceContextFormat(); - var ctx = f.Extract(headers, Getter); + var ctx = f.Extract(default, headers, Getter); - // TODO: Validate IsRemote when ActivityContext supports it. - // Assert.True(ctx.IsRemote); Assert.False(ctx.IsValid()); + + // TODO: when ActivityContext supports IsRemote + // Assert.True(ctx.IsRemote); } [Fact] @@ -123,9 +128,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] @@ -138,10 +143,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); + } } } From 022d28fbce33856ce67ce9526d90cf158fbbabae Mon Sep 17 00:00:00 2001 From: Eddy Nakamura Date: Mon, 3 Aug 2020 10:32:01 -0300 Subject: [PATCH 2/7] checking value --- .../Implementation/Trace/Propagation/CompositePropagatorTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/CompositePropagatorTest.cs b/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/CompositePropagatorTest.cs index dd86dd2022d..2b12b07231c 100644 --- a/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/CompositePropagatorTest.cs +++ b/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/CompositePropagatorTest.cs @@ -161,6 +161,7 @@ public void CompositePropagator_UsingSameTag() compositePropagator.Inject(activityContext, carrier, Setter); Assert.Contains(carrier, kv => kv.Key == "custom-traceparent"); + Assert.Equal($"00-{this.traceId}-{this.spanId}-01", carrier["custom-traceparent"]); bool isInjected = compositePropagator.IsInjected(carrier, Getter); Assert.True(isInjected); From 3a22faea8393efca2fef8d17df68ce05f3c4bd87 Mon Sep 17 00:00:00 2001 From: Eddy Nakamura Date: Mon, 3 Aug 2020 10:33:35 -0300 Subject: [PATCH 3/7] adding inverted test --- .../Propagation/CompositePropagatorTest.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/CompositePropagatorTest.cs b/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/CompositePropagatorTest.cs index 2b12b07231c..fce95b5c042 100644 --- a/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/CompositePropagatorTest.cs +++ b/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/CompositePropagatorTest.cs @@ -109,6 +109,38 @@ public void CompositePropagator_WithTraceContextAndB3Format() Assert.True(isInjected); } + [Fact] + public void CompositePropagator_WithB3FormatAndTraceContext() + { + var expectedHeaders = new Dictionary + { + { TraceParent, $"00-{this.traceId}-{this.spanId}-01" }, + { B3Format.XB3TraceId, this.traceId.ToString() }, + { B3Format.XB3SpanId, this.spanId.ToString() }, + { B3Format.XB3Sampled, "1" }, + }; + + var compositePropagator = new CompositePropagator(new List + { + new B3Format(), + new TraceContextFormat(), + }); + + var activityContext = new ActivityContext(this.traceId, this.spanId, ActivityTraceFlags.Recorded, traceState: null); + var carrier = new Dictionary(); + compositePropagator.Inject(activityContext, carrier, Setter); + + Assert.Equal(expectedHeaders, carrier); + + var ctx = compositePropagator.Extract(activityContext, expectedHeaders, Getter); + Assert.Equal(activityContext.TraceId, ctx.TraceId); + Assert.Equal(activityContext.SpanId, ctx.SpanId); + Assert.True(ctx.IsValid()); + + bool isInjected = compositePropagator.IsInjected(carrier, Getter); + Assert.True(isInjected); + } + [Fact] public void CompositePropagator_B3FormatNotInjected() { From 59e8cb33a39fff702bdae010df3f0c1acc725a7b Mon Sep 17 00:00:00 2001 From: Eddy Nakamura Date: Mon, 3 Aug 2020 11:06:28 -0300 Subject: [PATCH 4/7] updating tests --- .../Propagation/CompositePropagatorTest.cs | 22 ++++++++++++++++--- .../Trace/Propagation/TestPropagator.cs | 13 +++++++++-- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/CompositePropagatorTest.cs b/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/CompositePropagatorTest.cs index fce95b5c042..8fe1f8f7be1 100644 --- a/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/CompositePropagatorTest.cs +++ b/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/CompositePropagatorTest.cs @@ -29,6 +29,7 @@ public class CompositePropagatorTest 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 }; @@ -42,6 +43,8 @@ public class CompositePropagatorTest carrier[name] = value; }; + private static int count = 0; + private readonly ActivityTraceId traceId = ActivityTraceId.CreateRandom(); private readonly ActivitySpanId spanId = ActivitySpanId.CreateRandom(); @@ -182,10 +185,13 @@ public void CompositePropagator_TestPropagator() [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", "custom-tracestate-1"), - new TestPropagator("custom-traceparent", "custom-tracestate-2"), + new TestPropagator("custom-traceparent", header01, true), + new TestPropagator("custom-traceparent", header02), }); var activityContext = new ActivityContext(this.traceId, this.spanId, ActivityTraceFlags.Recorded, traceState: null); @@ -193,10 +199,20 @@ public void CompositePropagator_UsingSameTag() compositePropagator.Inject(activityContext, carrier, Setter); Assert.Contains(carrier, kv => kv.Key == "custom-traceparent"); - Assert.Equal($"00-{this.traceId}-{this.spanId}-01", carrier["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); } [Fact] diff --git a/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/TestPropagator.cs b/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/TestPropagator.cs index 0b624f22cbf..de206634c28 100644 --- a/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/TestPropagator.cs +++ b/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/TestPropagator.cs @@ -26,17 +26,24 @@ public class TestPropagator : ITextFormat { private readonly string idHeaderName; private readonly string stateHeaderName; + private readonly bool defaultContext; - public TestPropagator(string idHeaderName, string stateHeaderName) + 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) { @@ -61,8 +68,10 @@ public ActivityContext Extract(ActivityContext activityContext, T carrier, Fu 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, (activityContext.TraceFlags & ActivityTraceFlags.Recorded) != 0 ? "-01" : "-00"); + traceparent = string.Concat(traceparent, "-", headerNumber); setter(carrier, this.idHeaderName, traceparent); From d7b00c57c9e6a9253a64dd25e9d608fcc0d57f35 Mon Sep 17 00:00:00 2001 From: Eddy Nakamura Date: Mon, 3 Aug 2020 12:07:54 -0300 Subject: [PATCH 5/7] removing tracecontext and b3 tests --- .../Propagation/CompositePropagatorTest.cs | 132 ------------------ 1 file changed, 132 deletions(-) diff --git a/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/CompositePropagatorTest.cs b/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/CompositePropagatorTest.cs index 8fe1f8f7be1..8e9d26131f0 100644 --- a/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/CompositePropagatorTest.cs +++ b/test/OpenTelemetry.Tests/Implementation/Trace/Propagation/CompositePropagatorTest.cs @@ -54,114 +54,6 @@ public void CompositePropagator_NullTextFormatList() Assert.Throws(() => new CompositePropagator(null)); } - [Fact] - public void CompositePropagator_WithTraceContext() - { - var expectedHeaders = new Dictionary - { - { TraceParent, $"00-{this.traceId}-{this.spanId}-01" }, - }; - - var compositePropagator = new CompositePropagator(new List - { - new TraceContextFormat(), - }); - - var activityContext = new ActivityContext(this.traceId, this.spanId, ActivityTraceFlags.Recorded, traceState: null); - var carrier = new Dictionary(); - compositePropagator.Inject(activityContext, carrier, Setter); - - Assert.Equal(expectedHeaders, carrier); - - var ctx = compositePropagator.Extract(activityContext, expectedHeaders, Getter); - Assert.Equal(activityContext.TraceId, ctx.TraceId); - Assert.Equal(activityContext.SpanId, ctx.SpanId); - - Assert.Empty(compositePropagator.Fields); - } - - [Fact] - public void CompositePropagator_WithTraceContextAndB3Format() - { - var expectedHeaders = new Dictionary - { - { TraceParent, $"00-{this.traceId}-{this.spanId}-01" }, - { B3Format.XB3TraceId, this.traceId.ToString() }, - { B3Format.XB3SpanId, this.spanId.ToString() }, - { B3Format.XB3Sampled, "1" }, - }; - - var compositePropagator = new CompositePropagator(new List - { - new TraceContextFormat(), - new B3Format(), - }); - - var activityContext = new ActivityContext(this.traceId, this.spanId, ActivityTraceFlags.Recorded, traceState: null); - var carrier = new Dictionary(); - compositePropagator.Inject(activityContext, carrier, Setter); - - Assert.Equal(expectedHeaders, carrier); - - var ctx = compositePropagator.Extract(activityContext, expectedHeaders, Getter); - Assert.Equal(activityContext.TraceId, ctx.TraceId); - Assert.Equal(activityContext.SpanId, ctx.SpanId); - Assert.True(ctx.IsValid()); - - bool isInjected = compositePropagator.IsInjected(carrier, Getter); - Assert.True(isInjected); - } - - [Fact] - public void CompositePropagator_WithB3FormatAndTraceContext() - { - var expectedHeaders = new Dictionary - { - { TraceParent, $"00-{this.traceId}-{this.spanId}-01" }, - { B3Format.XB3TraceId, this.traceId.ToString() }, - { B3Format.XB3SpanId, this.spanId.ToString() }, - { B3Format.XB3Sampled, "1" }, - }; - - var compositePropagator = new CompositePropagator(new List - { - new B3Format(), - new TraceContextFormat(), - }); - - var activityContext = new ActivityContext(this.traceId, this.spanId, ActivityTraceFlags.Recorded, traceState: null); - var carrier = new Dictionary(); - compositePropagator.Inject(activityContext, carrier, Setter); - - Assert.Equal(expectedHeaders, carrier); - - var ctx = compositePropagator.Extract(activityContext, expectedHeaders, Getter); - Assert.Equal(activityContext.TraceId, ctx.TraceId); - Assert.Equal(activityContext.SpanId, ctx.SpanId); - Assert.True(ctx.IsValid()); - - bool isInjected = compositePropagator.IsInjected(carrier, Getter); - Assert.True(isInjected); - } - - [Fact] - public void CompositePropagator_B3FormatNotInjected() - { - var carrier = new Dictionary - { - { TraceParent, $"00-{this.traceId}-{this.spanId}-01" }, - }; - - var compositePropagator = new CompositePropagator(new List - { - new TraceContextFormat(), - new B3Format(), - }); - - bool isInjected = compositePropagator.IsInjected(carrier, Getter); - Assert.False(isInjected); - } - [Fact] public void CompositePropagator_TestPropagator() { @@ -214,29 +106,5 @@ public void CompositePropagator_UsingSameTag() // if that's true, we skipped the first one since we have a logic to for the default result Assert.Equal(2, count); } - - [Fact] - public void CompositePropagator_CustomAndTraceFormats() - { - var compositePropagator = new CompositePropagator(new List - { - new TestPropagator("custom-traceparent", "custom-tracestate-1"), - new TraceContextFormat(), - }); - - var activityContext = new ActivityContext(this.traceId, this.spanId, ActivityTraceFlags.Recorded, traceState: null); - var carrier = new Dictionary(); - - compositePropagator.Inject(activityContext, carrier, Setter); - Assert.Equal(2, carrier.Count); - - bool isInjected = compositePropagator.IsInjected(carrier, Getter); - Assert.True(isInjected); - - ActivityContext newContext = compositePropagator.Extract(default, carrier, Getter); - Assert.Equal(this.traceId, newContext.TraceId); - Assert.Equal(this.spanId, newContext.SpanId); - Assert.True(newContext.IsValid()); - } } } From 43ca7d074b45bf50f769f83a315c79075ae54630 Mon Sep 17 00:00:00 2001 From: Eddy Nakamura Date: Mon, 3 Aug 2020 12:40:07 -0300 Subject: [PATCH 6/7] testing only aspnet to merge --- .github/workflows/dotnet-core-cov.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/dotnet-core-cov.yml b/.github/workflows/dotnet-core-cov.yml index c23c36fe723..81d603a9e20 100644 --- a/.github/workflows/dotnet-core-cov.yml +++ b/.github/workflows/dotnet-core-cov.yml @@ -38,6 +38,9 @@ jobs: - name: dotnet test run: dotnet test --collect:"XPlat Code Coverage" --results-directory:"TestResults" --configuration Release --no-build -- RunConfiguration.DisableAppDomain=true + - name: dotnet test aspnet + run: dotnet test test\OpenTelemetry.Instrumentation.AspNet.Tests\OpenTelemetry.Instrumentation.AspNet.Tests.csproj --collect:"XPlat Code Coverage" --results-directory:"TestResults" --configuration Release --no-build -- RunConfiguration.DisableAppDomain=true + - name: Install report tool run: dotnet tool install -g dotnet-reportgenerator-globaltool From b0956e10c1bb2f9da5c5a35e6785e05d43c8f9b3 Mon Sep 17 00:00:00 2001 From: Eddy Nakamura Date: Mon, 3 Aug 2020 12:41:22 -0300 Subject: [PATCH 7/7] Revert "testing only aspnet to merge" This reverts commit 43ca7d074b45bf50f769f83a315c79075ae54630. --- .github/workflows/dotnet-core-cov.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/dotnet-core-cov.yml b/.github/workflows/dotnet-core-cov.yml index 81d603a9e20..c23c36fe723 100644 --- a/.github/workflows/dotnet-core-cov.yml +++ b/.github/workflows/dotnet-core-cov.yml @@ -38,9 +38,6 @@ jobs: - name: dotnet test run: dotnet test --collect:"XPlat Code Coverage" --results-directory:"TestResults" --configuration Release --no-build -- RunConfiguration.DisableAppDomain=true - - name: dotnet test aspnet - run: dotnet test test\OpenTelemetry.Instrumentation.AspNet.Tests\OpenTelemetry.Instrumentation.AspNet.Tests.csproj --collect:"XPlat Code Coverage" --results-directory:"TestResults" --configuration Release --no-build -- RunConfiguration.DisableAppDomain=true - - name: Install report tool run: dotnet tool install -g dotnet-reportgenerator-globaltool